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 <drr...@gmail.com>
> Verzonden: vrijdag 15 november 2024 21:02
> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
> dev@cxf.apache.org
> 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 <jean-pierre.urk...@devoteam.com>
>> Verzonden: donderdag 14 november 2024 11:29
>> Aan: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org'
>> <dev@cxf.apache.org>
>> 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 <drr...@gmail.com>
>> Verzonden: woensdag 13 november 2024 23:42
>> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>> dev@cxf.apache.org
>> 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 <drr...@gmail.com>
>>> Verzonden: vrijdag 4 oktober 2024 2:52
>>> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>> dev@cxf.apache.org
>>> 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 <drr...@gmail.com>
>>>> Sent: donderdag 3 oktober 2024 1:01
>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>> dev@cxf.apache.org
>>>> 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 <jean-pierre.urk...@devoteam.com>
>>>>> Sent: vrijdag 5 juli 2024 13:04
>>>>> To: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org'
>>>>> <dev@cxf.apache.org>
>>>>> 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 <drr...@gmail.com>
>>>>> Sent: vrijdag 5 juli 2024 2:16
>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>> dev@cxf.apache.org
>>>>> 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 <drr...@gmail.com>
>>>>>> Sent: donderdag 4 juli 2024 4:44
>>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>>> dev@cxf.apache.org
>>>>>> 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 <drr...@gmail.com>
>>>>>>> Sent: woensdag 3 juli 2024 5:57
>>>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>>>> dev@cxf.apache.org
>>>>>>> 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.

Reply via email to