Hi Jean,
So I have been able to spend some time on the issue and it seems like you
might not be using the client properly (hence getting the exceptions), just a
hypothesis.
Here I have crafted a version of the API:
@POST
@Path("/multipart")
@Consumes("multipart/form-data")
@Produces("text/xml")
public Response addParts(@Multipart(value = "messageToSend",
type="application/xml") MessageToSend messageToSend,
@Multipart("upfile1Detail") Attachment a1,
@Multipart("upfile2Detail") Attachment a2,
@Multipart("upfile3Detail") Attachment a3)
...
}
And the client invocation sequence:
final Client client = ClientBuilder.newClient();
final MultipartBody builder = new MultipartBody(Arrays.asList(
new AttachmentBuilder()
.mediaType("application/xml")
.id("messageToSend")
.object(new MessageToSend())
.build(),
new AttachmentBuilder()
.id("upfile1Detail")
.dataHandler(new DataHandler(new
InputStreamDataSource(getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/attachmentData"),
"text/xml")))
.contentDisposition(new ContentDisposition("form-data;
name=\"field1\";"))
.build(),
new AttachmentBuilder()
.id("upfile2Detail")
.dataHandler(new DataHandler(new InputStreamDataSource(new
ByteArrayInputStream(new byte[0]), "text/xml")))
.contentDisposition(new ContentDisposition("form-data;
name=\"field2\";"))
.build(),
new AttachmentBuilder()
.id("upfile3Detail")
.dataHandler(new DataHandler(new InputStreamDataSource(new
ByteArrayInputStream(new byte[0]), "text/xml")))
.contentDisposition(new ContentDisposition("form-data;
name=\"field3\";"))
.build()));
final Response response = client
.target(address)
.request("text/xml")
.post(Entity.entity(builder, "multipart/form-data"));
It works perfectly when the unified Attachment body part is used. I also
crafted the test case over
here [1], to help you out to get it working or point me out if there is a gap
here that I missed.
Thank you.
[1] https://github.com/apache/cxf/pull/2152
Best Regards,
Andriy Redko
> Hi Andriy,
> The option to use a List<Attachment> or a MultipartBody does work, I've
> testcases to confirm this.
> But it somehow breaks the original spec since trying to do a round trip
code->>>spec),
> the spec generated from the code (based on annotations) no longer
> reflects the input spec.
> What I find unexpected is that for multipart bodies all input parameters
> are attempted to be wrapped into Attachment objects,
> (cf. method
> org.apache.cxf.jaxrs.client.ClientProxyImpl#handleMultipart(MultivaluedMap
> <ParameterType, Parameter> map,OperationResourceInfo ori,Object[]
> params)).
> So why doesn't the stack allow to mix request body parameters that are
> either @Multipart annotated or are Attachment itself.
> Now you can't mix them since
> org.apache.cxf.jaxrs.client.ClientProxyImpl#getParametersInfo(Method
> m,Object[] params, OperationResourceInfo ori) will fail
> with error "SINGLE_BODY_ONLY". It wouldn't be hard to support the mixture
> of both, or even an @Multipart annotated Attachment parameter (you could
> just combine what is specified in the annotation with what is already
> present in the Attachment, giving priority to one of both in case of
> overlapping parameters).
> Further, if 'Content-Disposition' is obligatory (at least by openAPI spec,
> however don't know whether this is the industry reference) why doesn't the
> @Multipart
> annotation allow to specify it? Why i.o. setting header Content-ID=<value>
> isn't the header Content-Disposition=form-date:name="value" set when
> wrapping
> a @Multipart annotated object into an Attachment object?
> Strangely I don't even find a reference to the header Content-ID in
> https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers. It is described
> in
> https://www.rfc-editor.org/rfc/rfc2045#section-7,
> https://www.rfc-editor.org/rfc/rfc2392.txt and should have the form
> 'url-addr-spec according to RFC822'
> enclosed within '<>' and it is used to reference a multipartbody part in
> another part of the message. This doesn't seem to be the context in which
> it is used in
> JAX-RS messages, further the url-addr-spec actually tells me there should
> a ' @' sign in the value of the content-id header which is surely not the
> case in all examples
> I've seen sofar. So why is CXF even using Content-ID?
> Regards,
> J.P.
> -----Oorspronkelijk bericht-----
> Van: Andriy Redko <[email protected]>
> Verzonden: vrijdag 15 november 2024 21:02
> Aan: Jean Pierre URKENS <[email protected]>;
> [email protected]
> Onderwerp: Re: CXF JAX-RS: working with multipart form-data
> Hi Jean,
> Sorry for the delay, just went over through the message thread. So option
> 1-2 should
> indeed work just fine. And for 1st option, you could indeed set the
> headers manually.
> And in this case, you will need to craft the OpenAPI spec manually, it
> won't be properly
> deducted.
> I don't think adding more attributes to @Multipart would help since it is
> going to
> in conflict with File / Attachment that by itself source these attributes.
> But gimme
> some time to experiment over the weekend, I think, intuitively, that this
> could work
> (doesn't right now):
> Response testMessage1(
> @HeaderParam("x-correlation-id") @NotNull
> @Size(min = 10, max = 36)
> @Parameter(description="ID of the transaction. Use this ID for log tracing
> and incident handling.") String xCorrelationId,
> @HeaderParam("Idempotency-Key") @NotNull @Size(min
> = 10, max = 36)
> @Parameter(description="When retrying a failed call, the retry call should
> have the same Idempotency Key.") String idempotencyKey,
> @FormDataParam(value="messageToSend")
> @Parameter(required=true,schema =
> @Schema(implementation=MessageToSend.class)) @Multipart(value =
> "messageToSend", type="application/json", required= true) MessageToSend
> messageToSend,
> @FormDataParam(value="upfile1") @Parameter(schema
> = @Schema(type =
> "string", format = "binary")) Attachment upfile1Detail,
> @FormDataParam(value="upfile2") @Parameter(schema
> = @Schema(type =
> "string", format = "binary")) Attachment upfile2Detail,
> @FormDataParam(value="upfile3") @Parameter(schema
> = @Schema(type =
> "string", format = "binary")) Attachment upfile3Detail)
> If we could fold it into :
> Response testMessage1(
> @HeaderParam("x-correlation-id") @NotNull
> @Size(min = 10, max = 36)
> @Parameter(description="ID of the transaction. Use this ID for log tracing
> and incident handling.") String xCorrelationId,
> @HeaderParam("Idempotency-Key") @NotNull @Size(min
> = 10, max = 36)
> @Parameter(description="When retrying a failed call, the retry call should
> have the same Idempotency Key.") String idempotencyKey,
> @FormDataParam(value="messageToSend")
> @Parameter(required=true,schema =
> @Schema(implementation=MessageToSend.class)) @Multipart(value =
> "messageToSend", type="application/json", required= true) MessageToSend
> messageToSend,
> List<Attachment> attachments)
> This is just rough idea, but I will try look more closely into it.
> Thanks.
> Best Regards,
> Andriy Redko
>> What I found out when trying to send multipart/form-data requests with
> CXF
>> v3.5:
>> 1. You can have just one request body parameter. This can be a
>> MultipartBody, or a List<Attachment> but you'll have to set the headers
>> (Content-ID,Content-Type, Content-Disposition) yourself.
>> 2. I believe you can also have just one request body parameter
>> representing a List<Files> or a single File. Here a Content-Disposition
>> header will be set based on the filename (didn't check this, but the
> code
>> seems to reveal this).
>> 3. You can have just one request parameter annotated with @Multipart.
> This
>> will add the Content-ID and Content-Type headers based on the
> annotation,
>> but not the Content-Disposition.
>> 4. You can have multiple request body parameters but then all of them
> need
>> to be annotated with @Multipart. This will add the headers Content-Type
>> and Content-ID as specified in the annotation, but it will not add a
>> 'Content-Disposition' header.
>> So the OpenAPI specification/examples as enlisted below requires us to
>> convert everything to attachments (to get the Content-Disposition header
>> in place) and either send a request body consisting of a
> List<Attachment>
>> or MultipartBody.
>> The fact that in CXF-v3.5.x you can not:
>> * specify a Content-Disposition header for a @Multipart input parameter
>> * mix Attachment objects with @Multipart annotated objects (which by
> the
>> stack are converted to Attachment objects) as a list of input parameters
>> seems to be a short-coming. It doesn't align well with the OpenAPI
> v3.0.x
>> specification.
>> Is this a correct conclusion?
>> Regards,
>> J.P. Urkens
>> -----Oorspronkelijk bericht-----
>> Van: Jean Pierre URKENS <[email protected]>
>> Verzonden: donderdag 14 november 2024 11:29
>> Aan: 'Andriy Redko' <[email protected]>; '[email protected]'
>> <[email protected]>
>> Onderwerp: RE: CXF JAX-RS: working with multipart form-data
>> Hi Andriy,
>> Actually, I think you'll have to set the Content-Disposition yourself in
>> the Attachment object, while for File objects it will be retrieved from
>> the file name.
>> Looking at
> https://swagger.io/docs/specification/v3_0/describing-request-body/multipa
>> rt-requests/ how would you translate the request body to an method
>> signature that works with CXF (v3.5.x)?
>> The example shows the 'Content-Disposition' header to be present for all
>> multipart parts irrespective of their data type and that is something I
>> don't know how to achieve in a clean way using CXF. The
>> @org.apache.cxf.jaxrs.ext.multipart.Multipart annotation won't do the
> job
>> and CXF only adds it for File objects and for Attachment objects (if it
> is
>> present in the Attachment object).
>> Regards,
>> J.P.
>> -----Oorspronkelijk bericht-----
>> Van: Andriy Redko <[email protected]>
>> Verzonden: woensdag 13 november 2024 23:42
>> Aan: Jean Pierre URKENS <[email protected]>;
>> [email protected]
>> Onderwerp: Re: CXF JAX-RS: working with multipart form-data
>> Hi Jean,
>>> When looking at the classes MultipartProvider and JAXRSUtils (cxf
>> v3.5.9)
>>> then it shows that only object for which a 'Content-Disposition'
>> header will
>>> be written is a File Object. The problem is that my application is
>>> generating the file content on the fly, so I have it either as a
>> byte[] or
>>> InputStream.
>> I believe the 'Content-Disposition' will be written for File and
>> Attachment. Respectively,
>> it is going to be read for these multipart content parts as well. This
> is
>> why the
>> @Multipart annotation has no 'Content-Disposition' or alike (I think).
>>>> Even passing a List<Attachment> doesn't work as the
>> MultiPartProvider will
>>> loop through the list and try to create a DataHandler for an
>> Attachment
>>> object which is also not supported (throws an exception).
>> This is surprising, I will take a look shortly why it does not work.
> What
>> kind of
>> exception are you getting?
>> Thank you.
>> Best Regards,
>> Andriy Redko
>>> Hi Andriy,
>>> When looking at the classes MultipartProvider and JAXRSUtils (cxf
>> v3.5.9)
>>> then it shows that only object for which a 'Content-Disposition'
>> header will
>>> be written is a File Object. The problem is that my application is
>>> generating the file content on the fly, so I have it either as a
>> byte[] or
>>> InputStream.
>>> This surprises me as according to
> https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposit
>> ion:
>>> "a multipart/form-data body requires a Content-Disposition header to
>> provide
>>> information about each subpart of the form (e.g., for every form
>> field and
>>> any files that are part of field data)".
>>> Also the Multipart annotation class only allows to specify
>> Content-Type,
>>> Content-ID so there is no way for me to provide 'Content-Disposition'
>>> information on objects like byte[] or InputStream.
>>> Even passing a List<Attachment> doesn't work as the MultiPartProvider
>> will
>>> loop through the list and try to create a DataHandler for an
>> Attachment
>>> object which is also not supported (throws an exception).
>>> The only way I see to pass it is to construct Attachment objects for
>> each
>>> multipart part, with 'Content-Disposition' set, and add them all to a
>>> MultipartBody object and pass this as input parameter to my method
>>> signature. But then I loose all swager information for input objects
>> that
>>> are not byte[] or InputStream.
>>> Am I missing something?
>>> Regards,
>>> J.P.
>>> -----Oorspronkelijk bericht-----
>>> Van: Andriy Redko <[email protected]>
>>> Verzonden: vrijdag 4 oktober 2024 2:52
>>> Aan: Jean Pierre URKENS <[email protected]>;
>>> [email protected]
>>> Onderwerp: Re: CXF JAX-RS: working with multipart form-data
>>> Hi Jean,
>>> Yeah, I think the @Multipart + Attachment may not work, but you could
>> accept
>>> the List<Attachment> instead, right? (since you send many).
>>> The logging configuration does not seem right: you use interceptors
>> AND
>>> feature (as per snippet below).
>>> factory.getOutInterceptors().add(new
>>> LoggingOutInterceptor());
>>> factory.getInInterceptors().add(new
>>> LoggingInInterceptor());
>>> LoggingFeature feature = new LoggingFeature();
>>> feature.setLogMultipart(true);
>>> feature.setLogBinary(true);
>>> ...
>>> You only need one of those, either interceptors (please configure
>>> setLogBinary & setLogMultipart for them):
>>> factory.getOutInterceptors().add(new
>>> LoggingOutInterceptor());
>>> factory.getInInterceptors().add(new
>>> LoggingInInterceptor());
>>> Or feature:
>>> LoggingFeature feature = new LoggingFeature();
>>> feature.setLogMultipart(true);
>>> feature.setLogBinary(true);
>>> ...
>>> Hope it helps, thanks!
>>> Best Regards,
>>> Andriy Redko
>>>> Hi Andriy,
>>>> Thanks for the swift response, but I could still use some
>>> clarifications on:
>>>> 1) You mention that passing an Attachment object as service method
>>>> parameter should work.
>>>> My initial test setup did pass an Attachment object as input
>>>> parameter
>>>>> 1)API interface declaration" in my mail. However when the
>>>> client (see code below) tries to send a request with this
>>>> signature, the
>>>> JAXRSUtils.writeMessageBody(...) method that is called by the CXF
>>>> stack throws an exception on the Attachment parameter saying:
>>>> okt 03, 2024 9:46:54 AM
>>>> org.apache.cxf.jaxrs.provider.MultipartProvider
>>>> getHandlerForObject SEVERE: No message body writer found for class
>>>> : class org.apache.cxf.jaxrs.ext.multipart.Attachment.
>>>> okt 03, 2024 9:47:05 AM
>>>> org.apache.cxf.jaxrs.utils.JAXRSUtils
>>>> logMessageHandlerProblem SEVERE: Problem with writing the data,
>>>> class java.util.ArrayList, ContentType: multipart/form-data
>>>> okt 03, 2024 9:47:14 AM
>>>> org.apache.cxf.phase.PhaseInterceptorChain
>>>> doDefaultLogging WARNING: Interceptor for
>>>> {http://api.documenten.magda.common.aeo.dvtm.be/}MessagesApi has
>>>> thrown exception, unwinding now
>>>> org.apache.cxf.interceptor.Fault: Problem with
>>>> writing the data, class java.util.ArrayList, ContentType:
>>> multipart/form-data
>>>> at
> org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.doWriteBody(ClientP
>> roxyImpl.java:1142)
>>>> at
>>>> org.apache.cxf.jaxrs.client.AbstractClient$AbstractBodyWriter.handl
>>>> eMessage(AbstractClient.java:1223)
>>>> The JAXRSUtils.writeMessageBody(...) method takes an 'entity'
>>>> Object that is a List<Attachment>. The first Attachment in the list
>>>> contains an object of type MessageToSend, while the second one
>>>> contains an object of type Attachment for which 'no message body
>>> writer' could be found.
>>>> The stack creates itself an Attachment object for each parameter of
>>>> the multipart body, that is why I though that I can not pass it as
>>>> a parameter to my service method. I guess I am not allowed to
>> annotate
>>> an 'Attachment'
>>>> parameter with @Multipart annotation as currently done in the
>>>> method
>>>> signature:
>>>> @FormDataParam(value="upfile1") @Parameter(schema =
>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>> "upfile1", type="application/pdf", required = false) Attachment
>>>> upfile1Detail
>>>> However leaving the @Multipart annotation for the Attachment
>>>> parameter away leads to the error:
>>>> javax.ws.rs.ProcessingException: Resource method
>>>> be.dvtm.aeo.common.magda.documenten.api.MessagesApi.createMessage2
>>>> has more than one parameter representing a request body
>>>> I.e. now it is no longer clear that the Attachment parameter is
>>>> part of the 'multipart'. That's why I switched to using an
>>>> InputStream as parameter with @Multipart annotation but then I loose
>>> the Content-Disposition information.
>>>> The @Multipart annotation doesn't allow to specify
>>>> Content-Disposition information. Is there an alternative here?
>>>> 2) Logging of Binary Data. I create my client with:
>>>> private static MessagesApi getThreadsafeProxy(String
>>> baseAddress) {
>>>> JacksonJsonProvider jjProvider = new
>>>> JacksonJsonProvider(new CustomObjectMapper());
>>>> List<Object> providers = Arrays.asList(new
>>>> MultipartProvider(), jjProvider);
>>>> final JAXRSClientFactoryBean factory = new
>>> JAXRSClientFactoryBean();
>>>> factory.setAddress(baseAddress);
>>>> factory.setServiceClass(MessagesApi.class);
>>>> factory.setProviders(providers);
>>>> factory.getOutInterceptors().add(new
>>> LoggingOutInterceptor());
>>>> factory.getInInterceptors().add(new
>>> LoggingInInterceptor());
>>>> factory.setThreadSafe(true);
>>>> LoggingFeature feature = new LoggingFeature();
>>>> feature.setLogMultipart(true);
>>>> feature.setLogBinary(true);
>> feature.addBinaryContentMediaTypes(MediaType.APPLICATION_OCTET_STREAM);
>> feature.addBinaryContentMediaTypes("application/pdf");
>>>> factory.setFeatures(Arrays.asList(feature));
>>>> MessagesApi api = factory.create(MessagesApi.class);
>>>> ClientConfiguration config =
>> WebClient.getConfig(api);
>>>> addTLSClientParameters(config.getHttpConduit());
>>>> return api;
>>>> }
>>>> Here I do activate the logging for multipart and binary and also
>>>> added some mediatypes (although couldn't find what it actually
>>>> does). So I was expecting to see the whole message (attachments are
>>>> rather small as it is a test).
>>>> Well I used wireshark to get the full message and it doesn't show
>>>> any Content-Disposition headers for the multipart elements.
>>>> Regards,
>>>> J.P. Urkens
>>>> -----Original Message-----
>>>> From: Andriy Redko <[email protected]>
>>>> Sent: donderdag 3 oktober 2024 1:01
>>>> To: Jean Pierre URKENS <[email protected]>;
>>>> [email protected]
>>>> Subject: Re: CXF JAX-RS: working with multipart form-data
>>>> Hi Jean,
>>>>> What is the correct way to annotate a file attachment as part of a
>>>>> mutlipart/form-data content body?
>>>> We have many examples in our test suites over here [1], it really
>>>> depends on the problem at hand.
>>>>> -> Question-01: Is this the appropriate way to pass files
>>>>> (PDF documents) as attachment in a mutlipart/form-data request, or
>>>>> are
>>>> there better ways?
>>>> You would probably better of with "multipart/mixed" [2] as in you
>>>> case, you are sending different data types. But
>>>> "multipart/form-data" should be fine as well. The right answer
>>>> answer depends on how large the files are. In many cases you are
>>>> better off using chunked transfer (no need to read the whole file in
>>> memory), like you do with InputStream.
>>>>> -> Question-02: When I activate logging, I can't see
>>>>> anything about the file attachment, not is content, nor the
>>>>> appropriate Content-Disposition settings? I get '--Content
>>>> suppressed--', e.g.:
>>>> Correct, by default the logging interceptors do not log binary
>>>> data, you could checks the docs here [3].
>>>>> -> Question-03: Don't I need to set anything regarding the
>>>>> Content-Disposition header? The example here is simplified in a
>>>>> sense that I used a test setup with just one file attachment. In
>>>>> the real interface up to
>>>>> 20 attachments can be passed. Somehow I need to know which part
>>>>> relates
>>>> to
>>>>> which attachment or not?
>>>> This is probably caused by the fact you are sending the InputStream
>>>> and not an Attachment, if my understanding is correct. I am pretty
>>>> sure passing the Attachment (as you did initially) should work.
>>>>> -> Question-04: At the server implemtation side, since
>>>>> upfile1Detail is passed as a method parameter, who is going to
>>>>> close this InputStream at the server side?
>>>> The stream should be closed by the service implementation (since
>>>> the stream is expected to be consumed). The Apache CXF runtime will
>>>> try to close the stream in most cases as well but sometimes it may
>> not
>>> be able to.
>>>> Hope it answers your questions. Thanks!
>>>> [1]
>>>> https://github.com/apache/cxf/blob/main/systests/jaxrs/src/test/jav
>>>> a/org/apache/cxf/systest/jaxrs/MultipartStore.java
>>>> [2]
>>>> https://learn.microsoft.com/en-us/exchange/troubleshoot/administrat
>>>> ion/multipart-mixed-mime-message-format
>>>> [3] https://cxf.apache.org/docs/message-logging.html
>>>> Best Regards,
>>>> Andriy Redko
>>>>> Hi Andriy,
>>>>> What is the correct way to annotate a file attachment as part of a
>>>>> mutlipart/form-data content body?
>>>>> I currently have the following (only the relevant parts):
>>>>> 1)API interface declaration
>>>>> ======================
>>>>> @POST
>>>>> @Path("/messages1")
>>>>> @Consumes("multipart/form-data")
>>>>> @Produces({ "application/json" })
>>>>> @Operation(...)
>>>>> @ApiResponses(...)
>>>>> Response createMessage1(
>>>>> @HeaderParam("x-correlation-id") @NotNull
>>>>> @Size(min = 10, max = 36) @Parameter(description="ID of the
>>>>> transaction. Use this ID for log tracing and incident handling.")
>>>> String xCorrelationId,
>>>>> @HeaderParam("Idempotency-Key") @NotNull
>>>>> @Size(min = 10, max = 36) @Parameter(description="When retrying a
>>>>> failed call, the retry call should have the same Idempotency
>>>>> Key.")
>>>> String idempotencyKey,
>>>>> @FormDataParam(value="messageToSend")
>>>>> @Parameter(required=true,schema =
>>>>> @Schema(implementation=MessageToSend.class)) @Multipart(value =
>>>>> "messageToSend", type="application/json", required= true)
>>>>> MessageToSend messageToSend,
>>>>> @FormDataParam(value="upfile1")
>>>>> @Parameter(schema = @Schema(type = "string", format = "binary"))
>>>>> @Multipart(value = "upfile1", type="application/octet-stream",
>>>>> required = false) Attachment upfile1Detail);
>>>>> So I pass the file to upload as an Attachment object.
>>>>> 2)API client test code
>>>>> =================
>>>>> String xCorrelationId =
>> UUID.randomUUID().toString();
>>>>> String idempotencyKey =
>>>>> UUID.randomUUID().toString();
>>>>> //01. Get the attachment to include
>>>>> String fileName = "test.pdf";
>>>>> try (InputStream is =
>>>>> this.getClass().getClassLoader().getResourceAsStream(fileName);
>>>>> BufferedReader reader = new
>>>>> BufferedReader(new InputStreamReader(is))) {
>>>>> if (is == null) {
>>>>> Assert.fail("Couldn't load
>>>>> test.pdf
>>>> from classpath!");
>>>>> }
>>>>> DataHandler dataHandler = new
>>>>> DataHandler(is,
>>>> "application/pdf");
>>>>> ContentDisposition cd = new
>>>>> ContentDisposition("attachment;name=upfile1;filename="+fileName);
>>>>> Attachment upfile1Detail = new
>>>> AttachmentBuilder()
>>>>> .id("upfile1")
>>>>> .dataHandler(dataHandler)
>>>>> .contentDisposition(cd)
>>>>> .mediaType("application/pdf")
>>>>> .build();
>>>>> //02. create the message to send
>>>>> MessageToSend mts = new MessageToSend();
>>>>> //03. Call the server
>>>>> Response resp =
>>>>> apiClient.createMessage1(xCorrelationId, idempotencyKey, mts,
>>>>> upfile1Detail);
>>>>> When running the API client test code and getting at '03.' The
>> method:
>>>>> JAXRSUtils.writeMessageBody(List<WriterInterceptor>
>>>>> writers,Object entity,Class<?> type, Type genericType,Annotation[]
>>>>> annotations,MediaType mediaType,MultivaluedMap<String, Object>
>>>>> httpHeaders,Message message)
>>>>> is called. The 'entity' object is a list of Attachment objects
>> where:
>>>>> - the first Attachment object contains an object of type
>>>>> MessageToSend
>>>>> - the second Attachment object contains an object of type
>>>>> Attachment
>>>>> This second object leads to a fatal error within the
>>>>> JAXRSUtils.writeMessageBody(...) method:
>>>>> okt 02, 2024 1:36:02 PM
>>>>> org.apache.cxf.jaxrs.provider.MultipartProvider
>>>>> getHandlerForObject
>>>>> SEVERE: No message body writer found for class : class
>>>>> org.apache.cxf.jaxrs.ext.multipart.Attachment.
>>>>> okt 02, 2024 1:36:02 PM
>>>>> org.apache.cxf.jaxrs.utils.JAXRSUtils
>>>>> logMessageHandlerProblem
>>>>> SEVERE: Problem with writing the data, class
>>>>> java.util.ArrayList,
>>>>> ContentType: multipart/form-data
>>>>> I modified the interface and implementation classes to use
>>>>> 'InputStream upfile1Detail' as type for the input parameter of the
>>>>> service method. And in this case my 'dummy' server implementation
>>>>> can read the file and save it to disk.
>>>>> -> Question-01: Is this the appropriate way to pass files
>>>>> (PDF documents) as attachment in a mutlipart/form-data request, or
>>>>> are
>>>> there better ways?
>>>>> -> Question-02: When I activate logging, I can't see
>>>>> anything about the file attachment, not is content, nor the
>>>>> appropriate Content-Disposition settings? I get '--Content
>>>> suppressed--', e.g.:
>>>>> [MAGDADOC] 2024-10-02 14:29:08,911 [main] INFO
>>>>> $--$
>>>>> (org.apache.cxf.ext.logging.slf4j.Slf4jEventSender:84) - REQ_OUT
>>>>> Address:
>>>>> http://localhost:8091/services/magdadoc/api/v1/messages/messages1
>>>>> HttpMethod: POST
>>>>> Content-Type: multipart/form-data;
>>>>> boundary="uuid:3c3fa9c0-8470-4655-b026-3ed09f79e862"
>>>>> ExchangeId:
>> a583a695-d881-4fa7-b65a-8961cdbbd412
>>>>> Headers: {Authorization=Bearer
>>>>> f4262ccf-3250-4bcf-a1bc-7ee1bf9a56cf,
>>>>> Accept=application/json,
>>>>> Idempotency-Key=bd06c05d-9fe2-4b60-b8db-5ad1121b74dc,
>>>>> x-correlation-id=511c51ba-95fe-4f69-9443-f05c377cffab}
>>>>> Payload:
>>>>> --uuid:3c3fa9c0-8470-4655-b026-3ed09f79e862
>>>>> Content-Type: application/json
>>>>> Content-Transfer-Encoding: binary
>>>>> Content-ID: <messageToSend>
>>>>> {
>>>>> "delivery" : "AUTOMATIC",
>>>>> "eboxDeliveryData" : { /* all fine */},
>>>>> "paperDeliveryData" : {/* all fine */},
>>>>> "emailDeliveryData" : null,
>>>>> "businessData" : [ ]
>>>>> }
>>>>> --uuid:3c3fa9c0-8470-4655-b026-3ed09f79e862
>>>>> --- Content suppressed ---
>>>>> -> Question-03: Don't I need to set anything regarding the
>>>>> Content-Disposition header? The example here is simplified in a
>>>>> sense that I used a test setup with just one file attachment. In
>>>>> the real interface up to
>>>>> 20 attachments can be passed. Somehow I need to know which part
>>>>> relates
>>>> to
>>>>> which attachment or not?
>>>>> -> Question-04: At the server implemtation side, since
>>>>> upfile1Detail is passed as a method parameter, who is going to
>>>>> close this InputStream at the server side?
>>>>> Regards,
>>>>> J.P. Urkens
>>>>> P.S.: Is there a migration document describing upgrading CXF-3.5.6
>>>>> (my current version) to CXF-3.5.8 (latest JDK8 version). I'd like
>>>>> to know whether upgrading can happen without too much burden.
>>>>> -----Original Message-----
>>>>> From: Jean Pierre URKENS <[email protected]>
>>>>> Sent: vrijdag 5 juli 2024 13:04
>>>>> To: 'Andriy Redko' <[email protected]>; '[email protected]'
>>>>> <[email protected]>
>>>>> Subject: RE: CXF JAX-RS: working with multipart form-data
>>>>> Hi Andriy,
>>>>> When searching the net I came along this Jersey annotation but
>>>>> since I was not depending on 'Jersey' components I skipped it. So
>>>>> apparently swagger-core has processing for externally defined
>>>>> annotations (like this FormDataParam in Jersey), indeed inside
>>>> know-how.
>>>>> I included it in my project as
>>>>> io.swagger.v3.oas.annotations.FormDataParam,
>>>>> since it should somehow be included in the supported swagger
>>>>> annotations (maybe we should submit a request for it to the
>>>>> swagger-core team) and this indeed generates an appropriate
>>>> openapi.json spec.
>>>>> Thanks alot,
>>>>> J.P. Urkens
>>>>> -----Original Message-----
>>>>> From: Andriy Redko <[email protected]>
>>>>> Sent: vrijdag 5 juli 2024 2:16
>>>>> To: Jean Pierre URKENS <[email protected]>;
>>>>> [email protected]
>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data
>>>>> Hi Jean,
>>>>> Here is how you could make it work (there is some magic knowledge
>>>>> involved sadly). First of all, define such annotation anywhere in
>>>>> your codebase (where it dims appropriate):
>>>>> import java.lang.annotation.ElementType; import
>>>>> java.lang.annotation.Retention; import
>>>>> java.lang.annotation.RetentionPolicy;
>>>>> import java.lang.annotation.Target;
>>>>> @Target({ElementType.PARAMETER, ElementType.METHOD,
>>>>> ElementType.FIELD})
>>>>> @Retention(RetentionPolicy.RUNTIME)
>>>>> public @interface FormDataParam {
>>>>> String value();
>>>>> }
>>>>> Use this annotation on each Attachment parameter:
>>>>> /* Skipping other annotations as those are not important here */
>>>>> public Response createMessage(
>>>>> @HeaderParam("x-correlation-id") @NotNull @Size(min = 10,
>>>>> max = 36) @Parameter(description="ID of the transaction. Use this
>>>>> ID for log tracing and incident handling.") String xCorrelationId,
>>>>> @HeaderParam("Idempotency-Key") @Size(min = 10, max = 36)
>>>>> @Parameter(description="When retrying a failed call, the retry
>>>>> call should have the same Idempotency Key.") String idempotencyKey,
>>>>> @FormDataParam("upfile1") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile1", type="application/octet-stream", required = false)
>>>>> InputStream upfile1Detail,
>>>>> @FormDataParam("upfile2") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile2", type="application/octet-stream", required = false)
>>>>> InputStream upfile2Detail,
>>>>> @FormDataParam("upfile3") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile3", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile3Detail,
>>>>> @FormDataParam("upfile4") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile4", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile4Detail,
>>>>> @FormDataParam("upfile5") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile5", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile5Detail,
>>>>> @FormDataParam("upfile6") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile6", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile6Detail,
>>>>> @FormDataParam("upfile7") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile7", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile7Detail,
>>>>> @FormDataParam("upfile8") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile8", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile8Detail,
>>>>> @FormDataParam("upfile9") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile9", type="application/octet-stream", required = false)
>>>>> Attachment
>>>> upfile9Detail,
>>>>> @FormDataParam("upfile10") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile10", type="application/octet-stream", required = false)
>>>>> Attachment upfile10Detail,
>>>>> @FormDataParam("upfile11") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile11", type="application/octet-stream", required = false)
>>>>> Attachment upfile11Detail,
>>>>> @FormDataParam("upfile12") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile12", type="application/octet-stream", required = false)
>>>>> Attachment upfile12Detail,
>>>>> @FormDataParam("upfile13") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile13", type="application/octet-stream", required = false)
>>>>> Attachment upfile13Detail,
>>>>> @FormDataParam("upfile14") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile14", type="application/octet-stream", required = false)
>>>>> Attachment upfile14Detail,
>>>>> @FormDataParam("upfile15") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile15", type="application/octet-stream", required = false)
>>>>> Attachment upfile15Detail,
>>>>> @FormDataParam("upfile16") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile16", type="application/octet-stream", required = false)
>>>>> Attachment upfile16Detail,
>>>>> @FormDataParam("upfile17") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile17", type="application/octet-stream", required = false)
>>>>> Attachment upfile17Detail,
>>>>> @FormDataParam("upfile18") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile18", type="application/octet-stream", required = false)
>>>>> Attachment upfile18Detail,
>>>>> @FormDataParam("upfile19") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile19", type="application/octet-stream", required = false)
>>>>> Attachment upfile19Detail,
>>>>> @FormDataParam("upfile20") @Parameter(schema =
>>>>> @Schema(type = "string", format = "binary")) @Multipart(value =
>>>>> "upfile20", type="application/octet-stream", required = false)
>>>>> Attachment upfile20Detail,
>>>>> @FormDataParam("qrfile") @Parameter(schema = @Schema(type
>>>>> = "string", format = "binary")) @Multipart(value = "qrfile",
>>>>> type="application/octet-stream", required = false) Attachment
>>>> qrfileDetail
>>>>> ) {
>>>>> ....
>>>>> }
>>>>> With that, you will get a nice request body schema (publishing a
>>>>> bit large YAML snippet to preserve the context):
>>>>> paths:
>>>>> /sample/messages:
>>>>> post:
>>>>> tags:
>>>>> - messages
>>>>> summary: "Send a message, using a channel (email, paper
>>>>> mail,
>>>>> ebox) and delivery\
>>>>> \ method (registered or normal) of your choice. More than
>>>>> 6 upfiles only supported\
>>>>> \ for PAPER delivery."
>>>>> operationId: createMessage
>>>>> parameters:
>>>>> - name: x-correlation-id
>>>>> in: header
>>>>> description: ID of the transaction. Use this ID for log
>>>>> tracing and incident
>>>>> handling.
>>>>> required: true
>>>>> schema:
>>>>> maxLength: 36
>>>>> minLength: 10
>>>>> type: string
>>>>> - name: Idempotency-Key
>>>>> in: header
>>>>> description: "When retrying a failed call, the retry call
>>>>> should have the\
>>>>> \ same Idempotency Key."
>>>>> schema:
>>>>> maxLength: 36
>>>>> minLength: 10
>>>>> type: string
>>>>> requestBody:
>>>>> content:
>>>>> multipart/form-data:
>>>>> schema:
>>>>> type: object
>>>>> properties:
>>>>> upfile1:
>>>>> type: string
>>>>> format: binary
>>>>> upfile2:
>>>>> type: string
>>>>> format: binary
>>>>> upfile3:
>>>>> type: string
>>>>> format: binary
>>>>> upfile4:
>>>>> type: string
>>>>> format: binary
>>>>> upfile5:
>>>>> type: string
>>>>> format: binary
>>>>> upfile6:
>>>>> type: string
>>>>> format: binary
>>>>> upfile7:
>>>>> type: string
>>>>> format: binary
>>>>> upfile8:
>>>>> type: string
>>>>> format: binary
>>>>> upfile9:
>>>>> type: string
>>>>> format: binary
>>>>> upfile10:
>>>>> type: string
>>>>> format: binary
>>>>> upfile11:
>>>>> type: string
>>>>> format: binary
>>>>> upfile12:
>>>>> type: string
>>>>> format: binary
>>>>> upfile13:
>>>>> type: string
>>>>> format: binary
>>>>> upfile14:
>>>>> type: string
>>>>> format: binary
>>>>> upfile15:
>>>>> type: string
>>>>> format: binary
>>>>> upfile16:
>>>>> type: string
>>>>> format: binary
>>>>> upfile17:
>>>>> type: string
>>>>> format: binary
>>>>> upfile18:
>>>>> type: string
>>>>> format: binary
>>>>> upfile19:
>>>>> type: string
>>>>> format: binary
>>>>> upfile20:
>>>>> type: string
>>>>> format: binary
>>>>> qrfile:
>>>>> type: string
>>>>> format: binary
>>>>> The key here is @FormDataParam annotation which (originally) comes
>>>>> from Jersey but has special treatment in Swagger Core (but,
>>>>> likely, no attribution to Jersey).
>>>>> Hope it helps!
>>>>> Thank you.
>>>>> Best Regards,
>>>>> Andriy Redko
>>>>>> V2.2.22 (15/05/2024) is the latest version of io.swagger.core.v3
>>>>>> libraries.
>>>>>> I upgrade to this version to make sure I had the latest swagger
>>>>>> implementation.
>>>>>> -----Original Message-----
>>>>>> From: Andriy Redko <[email protected]>
>>>>>> Sent: donderdag 4 juli 2024 4:44
>>>>>> To: Jean Pierre URKENS <[email protected]>;
>>>>>> [email protected]
>>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data
>>>>>> Hi Jean,
>>>>>> Interesting, I was experimenting with different ways to express what
>>>>>> you need, but no luck so far, I will try to spend a bit more time on
>>>>>> that this week since OAS 3.x does support multipart [1] but we may
>>>>>> indeed hit the
>>>>>> limitation(s) of this particular Swagger Core version. Thank you.
>>>>>> [1]
>>>>>> https://swagger.io/docs/specification/describing-request-body/multip
>>>>>> a
>>>>>> r
>>>>>> t-requests/
>>>>>> Best Regards,
>>>>>> Andriy Redko
>>>>>>> Hi Andriy,
>>>>>>> I already tried this but it didn't work. E.g. for following API
>>>>>>> interface
>>>>>>> specification:
>>>>>>> /**
>>>>>>> * Send a message, using a channel (email, paper
>>>>>>> mail,
>>>>>>> ebox) and delivery method (registered or normal) of your choice.
>>>>>>> More than
>>>>>>> 6 upfiles only supported for PAPER delivery.
>>>>>>> *
>>>>>>> */
>>>>>>> @POST
>>>>>>> @Path("/messages")
>>>>>>> @Consumes("multipart/form-data")
>>>>>>> @Produces({ "application/json" })
>>>>>>> @Operation(
>>>>>>> summary
>>>>>>> = "Send a message, using a channel (email, paper mail, ebox) and
>>>>>>> delivery method (registered or normal) of your choice. More than 6
>>>>>>> upfiles only supported for PAPER delivery.",
>>>>>>> tags =
>>>>>>> {"messages" }, operationId="createMessage",
>>>>>>> security=@SecurityRequirement(name="BearerAuthentication"))
>>>>>>> @ApiResponses({ @ApiResponse( responseCode =
>>>>>>> "201", description = "Created", content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,array=@ArraySchema(sc
>>>>>>> h e m a=@Schema(implementation=SendStatusMessage.class))),
>>>>>>> headers = {@Header(
>>>>>>> name="X-Magda-Exceptions",
>>>>>>> required=false,
>>>>>>> description="Only used in the context of EBOX delivery and if there
>>>>>>> was a problem with the consent of the receiver's ebox.",
>>>>>>> schema=@Schema(implementation=MagdaExceptionList.class))
>>>>>>> }),
>>>>>>> @ApiResponse(
>>>>>>> responseCode = "400",
>>>>>>> description = "Invalid data supplied", content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem
>>>>>>> e
>>>>>>> n
>>>>>>> t
>>>>>>> ation=ErrorMessage.class))),
>>>>>>> @ApiResponse(
>>>>>>> responseCode = "401",
>>>>>>> description = "Invalid authorization", content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem
>>>>>>> e
>>>>>>> n
>>>>>>> t
>>>>>>> ation=ErrorMessage.class))),
>>>>>>> @ApiResponse(
>>>>>>> responseCode = "500",
>>>>>>> description = "Unexpected Server Error", content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem
>>>>>>> e
>>>>>>> n
>>>>>>> t
>>>>>>> ation=ErrorMessage.class))),
>>>>>>> @ApiResponse(
>>>>>>> responseCode = "502",
>>>>>>> description = "Bad Gateway",
>>>>>>> content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem
>>>>>>> e
>>>>>>> n
>>>>>>> t
>>>>>>> ation=ErrorMessage.class))),
>>>>>>> @ApiResponse(
>>>>>>> responseCode = "503",
>>>>>>> description = "Service unavailable", content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem
>>>>>>> e
>>>>>>> n
>>>>>>> t
>>>>>>> ation=ErrorMessage.class))),
>>>>>>> @ApiResponse(
>>>>>>> responseCode = "504",
>>>>>>> description = "Gateway Timeout",
>>>>>>> content =
>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem
>>>>>>> e
>>>>>>> n
>>>>>>> t
>>>>>>> ation=ErrorMessage.class)))
>>>>>>> })
>>>>>>> public Response createMessage(
>>>>>>> @HeaderParam("x-correlation-id") @NotNull @Size(min = 10, max = 36)
>>>>>>> @Parameter(description="ID of the transaction. Use this ID for log
>>>>>>> tracing and incident handling.") String xCorrelationId,
>>>>>>> @HeaderParam("Idempotency-Key") @Size(min = 10, max = 36)
>>>>>>> @Parameter(description="When retrying a failed call, the retry call
>>>>>>> should have the same Idempotency Key.") String idempotencyKey,
>>>>>>> @Parameter(required=true,schema =
>>>>>>> @Schema(implementation=MessageToSend.class)) @Multipart(value =
>>>>>>> "messageToSend", type="application/json", required= true)
>>>>>>> MessageToSend messageToSend, @Parameter(schema = @Schema(type =
>>>>>>> "string", format = "binary")) @Multipart(value = "upfile1",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile1Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile2",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile2Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile3",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile3Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile4",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile4Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile5",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile5Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile6",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile6Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile7",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile7Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile8",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile8Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile9",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile9Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile10",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile10Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile11",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile11Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile12",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile12Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile13",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile13Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile14",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile14Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile15",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile15Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile16",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile16Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile17",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile17Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile18",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile18Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile19",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile19Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "upfile20",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> upfile20Detail, @Parameter(schema = @Schema(type = "string", format
>>>>>>> =
>>>>>>> "binary")) @Multipart(value = "qrfile",
>>>>>>> type="application/octet-stream", required = false) Attachment
>>>>>>> qrfileDetail); I've attached the generated openapi specification.
>>>>>>> It only contains the 'messageToSend' as part of the
>>>>>>> multipart/form-data requestBody content, all attachments are
>> ignored.
>>>>>>> Below I've listed the libraries I've included in the project (cxf
>>>>>>> v3.5.8 and swagger v2.2.2). Which of these libraries is acutal
>>>>>>> responsible for generating the openapi.json specification from the
>>>>>>> interface description?
>>>>>>> * cxf-rt-rs-service-description-common-openapi:3.5.8
>>>>>>> * cxf-rt-rs-service-description-openapi:3.5.8
>>>>>>> * cxf-rt-rs-service-description-swagger-ui:3.5.8
>>>>>>> * swagger-core:2.2.2
>>>>>>> * swagger-annotations:2.2.2
>>>>>>> * swagger-integration:2.2.2
>>>>>>> * swagger-jaxrs2: 2.2.2
>>>>>>> * swagger-model: 2.2.2
>>>>>>> Note that I am still on JDK8, so I guess I can't upgrade to a
>>>>>>> higher version (currently our projects use cxf-v3.5.6 and swagger
>>>>>>> 2.1.13).
>>>>>>> Regards,
>>>>>>> J.P. Urkens
>>>>>>> -----Original Message-----
>>>>>>> From: Andriy Redko <[email protected]>
>>>>>>> Sent: woensdag 3 juli 2024 5:57
>>>>>>> To: Jean Pierre URKENS <[email protected]>;
>>>>>>> [email protected]
>>>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data Hi Jean
>>>>>>> Pierre, I suspect the @Multipart annotation is coming from CXF
>>>>>>> (org.apache.cxf.jaxrs.ext.multipart.Multipart), right? If yes, this
>>>>>>> is not a part of JAX-RS specification but CXF specific extension.
>>>>>>> You may need to add Swagger API annotation to the parameters in
>>>>>>> question:
>>>>>>> @Parameter(schema = @Schema(type = "string", format = "binary"))
>>>>>>> Hope it helps.
>>>>>>> Thank you.
>>>>>>> Best Regards,
>>>>>>> Andriy Redko
>>>>>>> Monday, July 1, 2024, 12:09:17 PM, you wrote:
>>>>>>>> Hi all,
>>>>>>>> I am having problems to correctly annotate service methods which
>>>>>>>> consumes multipart/form-data that contains attachments next to
>>>>>>>> other model objects.
>>>>>>>> I've an openapi specification that contains following requestBody
>>>>>>>> definition:
>>>>>>>> /messages:
>>>>>>>> post:
>>>>>>>> tags:
>>>>>>>> - "messages"
>>>>>>>> summary: "Send a message, using a channel (email, paper
>>>>>>>> mail,
>>>>>>>> ebox) and delivery method (registered or normal) of your choice.
>>>>>>>> More than 6 upfiles only supported for PAPER delivery."
>>>>>>>> operationId: createMessage
>>>>>>>> parameters:
>>>>>>>> - $ref: '#/components/parameters/CorrelationId'
>>>>>>>> - $ref: '#/components/parameters/Idempotency-Key'
>>>>>>>> requestBody:
>>>>>>>> content:
>>>>>>>> multipart/form-data:
>>>>>>>> schema:
>>>>>>>> type: object
>>>>>>>> required:
>>>>>>>> - messageToSend
>>>>>>>> properties:
>>>>>>>> messageToSend:
>>>>>>>> $ref: '#/components/schemas/MessageToSend'
>>>>>>>> upfile1:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile2:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile3:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile4:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile5:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile6:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile7:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile8:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile9:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile10:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile11:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile12:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile13:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile14:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile15:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile16:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile17:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile18:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile19:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> upfile20:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> qrfile:
>>>>>>>> type: string
>>>>>>>> format: binary
>>>>>>>> nullable: true
>>>>>>>> required: true
>>>>>>>> When using the openapi-generator-maven-plugin v7.6.0 it generates
>>>>>>>> following method signature:
>>>>>>>> @POST
>>>>>>>> @Path("/messages")
>>>>>>>> @Consumes("multipart/form-data")
>>>>>>>> @Produces({ "application/json" })
>>>>>>>> @Operation(
>>>>>>>> summary = "Send a message, using a channel
>>>>>>>> (email, paper mail, ebox) and delivery method (registered or
>>>>>>>> normal) of your choice. More than 6 upfiles only supported for
>>>>>>>> PAPER delivery.",
>>>>>>>> tags = {"messages" },
>>>>>>>> operationId="createMessage",
>>>>>>>> security=@SecurityRequirement(name="BearerAuthentication"),
>>>>>>>> responses= {
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "201",
>>>>>>>> description = "Created",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,array=@ArraySchema(s
>>>>>>>> c h em a=@Schema(implementation=SendStatusMessage.class))),
>>>>>>>> headers =
>>>>>>>> {@Header( name="X-Magda-Exceptions", required=false,
>>>>>>>> description="Only used in the context of EBOX delivery and if
>>>>>>>> there was a problem with the consent of the receiver's ebox.",
>>>>>>>> schema=@Schema(implementation=MagdaExceptionList.class))
>>>>>>>> }),
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "400",
>>>>>>>> description = "Invalid data supplied",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(imple
>>>>>>>> m
>>>>>>>> e
>>>>>>>> nt
>>>>>>>> ation=ErrorMessage.class))),
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "401",
>>>>>>>> description = "Invalid authorization",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(imple
>>>>>>>> m
>>>>>>>> e
>>>>>>>> nt
>>>>>>>> ation=ErrorMessage.class))),
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "500",
>>>>>>>> description = "Unexpected Server Error",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(imple
>>>>>>>> m
>>>>>>>> e
>>>>>>>> nt
>>>>>>>> ation=ErrorMessage.class))),
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "502",
>>>>>>>> description = "Bad Gateway",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(imple
>>>>>>>> m
>>>>>>>> e
>>>>>>>> nt
>>>>>>>> ation=ErrorMessage.class))),
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "503",
>>>>>>>> description = "Service unavailable",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(imple
>>>>>>>> m
>>>>>>>> e
>>>>>>>> nt
>>>>>>>> ation=ErrorMessage.class))),
>>>>>>>> @ApiResponse(
>>>>>>>> responseCode = "504",
>>>>>>>> description = "Gateway Timeout",
>>>>>>>> content =
>>>>>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(imple
>>>>>>>> m
>>>>>>>> e
>>>>>>>> nt
>>>>>>>> ation=ErrorMessage.class)))
>>>>>>>> })
>>>>>>>> public Response createMessage(
>>>>>>>> @HeaderParam("x-correlation-id") @NotNull
>>>>>>>> @Size(min = 10, max = 36) @Parameter(description="ID of the
>>>>>>>> transaction. Use this ID for log tracing and incident handling.")
>>>>>>>> String xCorrelationId,
>>>>>>>> @HeaderParam("Idempotency-Key") @Size(min
>>>>>>>> = 10, max = 36) @Parameter(description="When retrying a failed
>>>>>>>> call, the retry call should have the same Idempotency Key.")
>>>>>>>> String idempotencyKey,
>>>>>>>> @Multipart(value = "messageToSend",
>>>>>>>> required=
>>>>>>>> true) MessageToSend messageToSend,
>>>>>>>> @Multipart(value = "upfile1", required =
>>>>>>>> false) Attachment upfile1Detail,
>>>>>>>> @Multipart(value = "upfile2", required =
>>>>>>>> false) Attachment upfile2Detail,
>>>>>>>> @Multipart(value = "upfile3", required =
>>>>>>>> false) Attachment upfile3Detail,
>>>>>>>> @Multipart(value = "upfile4", required =
>>>>>>>> false) Attachment upfile4Detail,
>>>>>>>> @Multipart(value = "upfile5", required =
>>>>>>>> false) Attachment upfile5Detail,
>>>>>>>> @Multipart(value = "upfile6", required =
>>>>>>>> false) Attachment upfile6Detail,
>>>>>>>> @Multipart(value = "upfile7", required =
>>>>>>>> false) Attachment upfile7Detail,
>>>>>>>> @Multipart(value = "upfile8", required =
>>>>>>>> false) Attachment upfile8Detail,
>>>>>>>> @Multipart(value = "upfile9", required =
>>>>>>>> false) Attachment upfile9Detail,
>>>>>>>> @Multipart(value = "upfile10", required =
>>>>>>>> false) Attachment upfile10Detail,
>>>>>>>> @Multipart(value = "upfile11", required =
>>>>>>>> false) Attachment upfile11Detail,
>>>>>>>> @Multipart(value = "upfile12", required =
>>>>>>>> false) Attachment upfile12Detail,
>>>>>>>> @Multipart(value = "upfile13", required =
>>>>>>>> false) Attachment upfile13Detail,
>>>>>>>> @Multipart(value = "upfile14", required =
>>>>>>>> false) Attachment upfile14Detail,
>>>>>>>> @Multipart(value = "upfile15", required =
>>>>>>>> false) Attachment upfile15Detail,
>>>>>>>> @Multipart(value = "upfile16", required =
>>>>>>>> false) Attachment upfile16Detail,
>>>>>>>> @Multipart(value = "upfile17", required =
>>>>>>>> false) Attachment upfile17Detail,
>>>>>>>> @Multipart(value = "upfile18", required =
>>>>>>>> false) Attachment upfile18Detail,
>>>>>>>> @Multipart(value = "upfile19", required =
>>>>>>>> false) Attachment upfile19Detail,
>>>>>>>> @Multipart(value = "upfile20", required =
>>>>>>>> false) Attachment upfile20Detail,
>>>>>>>> @Multipart(value = "qrfile", required =
>>>>>>>> false) Attachment qrfileDetail); If I now generate the swagger
>>>>>>>> from this code (I modified the annotations in the generated code
>>>>>>>> for using OAS v3 annotations through swagger-jaxrs2 v2.1.13 and I
>>>>>>>> am using cxf-v3.5.6 having swagger-ui v4.18.2 generate the user
>>>>>>>> interface) none of the upload files appears as request parameter,
>>>>>>>> only the messageToSend is shown.
>>>>>>>> Is the above signature for the method createMessage(...)
> incorrect?
>>>>>>>> If I look at the generated openapi.json all the Attachment upFiles
>>>>>>>> are missing from the specification? So is it a
>>>>>>>> problem/short-coming(?) of the used software libraries, which
> then:
>>>>>>>> . cxf-rt-rs-service-description-common-openapi v3.5.6
>>>>>>>> this library references swagger-jaxrs2 v2.1.13 .
>> swagger-jaxrs2
>>>>>>>> v2.1.13 -> can I
>> upgradethis
>>>>>>>> to
>>>>>>>> e.g. swagger-jaxrs2 v2.2.22 (latest) while retaining cxf v3.5.6?
>>>>>>>> . ...another?
>>>>>>>> Regards,
>>>>>>>> J.P.