Hi andriy,

Indeed after omitting the Attachment::id property, which dropped the
Content-ID header, it worked.
However I couldn't get rid of the Content-Transfer-Encoding header, but
the server seems to have no problem with it.

Below the API method my client uses:
        public Response createMessage(String xCorrelationId,String
idempotencyKey,MessageToSend messageToSend,DataSource upfile1,DataSource
upfile2,DataSource upfile3)
        throws GeneralSecurityException, AuthorizationException {
                //Create a multipartbody
                List<Attachment> attachments = new ArrayList<>();
                attachments.add(new AttachmentBuilder()
                                .object(messageToSend)
                                .contentDisposition(new
ContentDisposition("form-data; name=\"messageToSend\""))
                                .mediaType("application/json")
                                .build());
                
                if (upfile1 != null) {
                        attachments.add(new AttachmentBuilder()
                                        .dataHandler(new
DataHandler(upfile1))
                                        .contentDisposition(new
ContentDisposition("form-data; name=\"upfile1\";
filename=\""+upfile1.getName()+"\""))
                                        .build());
                }
                if (upfile2 != null) {
                        attachments.add(new AttachmentBuilder()
                                        .dataHandler(new
DataHandler(upfile2))
                                        .contentDisposition(new
ContentDisposition("form-data; name=\"upfile2\";
filename=\""+upfile2.getName()+"\""))
                                        .build());
                }
                if (upfile3 != null) {
                        attachments.add(new AttachmentBuilder()
                                        .dataHandler(new
DataHandler(upfile3))
                                        .contentDisposition(new
ContentDisposition("form-data; name=\"upfile3\";
filename=\""+upfile3.getName()+"\""))
                                        .build());
                }
                MultipartBody body = new
MultipartBody(attachments,MediaType.MULTIPART_FORM_DATA_TYPE,false);
                
                //Authorize API client
                Client client = WebClient.client(getApiProxy());
        
authorizationHandler.authorize(client,resourceClientId,consumerClientId,Ma
gdadocService.MAGDADOC_SCOPES,consumerPrivKey);
                
                WebClient wc = WebClient.fromClient(client,true)
                                .header("x-correlation-id",
xCorrelationId)
                                .header("Idempotency-Key", idempotencyKey)
                                .path("/messages")
                                .accept(MediaType.APPLICATION_JSON_TYPE);
                
                return
wc.post(Entity.entity(body,MediaType.MULTIPART_FORM_DATA_TYPE));
        }


-----Oorspronkelijk bericht-----
Van: Andriy Redko <[email protected]>
Verzonden: maandag 19 mei 2025 17:06
Aan: Jean Pierre URKENS <[email protected]>;
[email protected]
Onderwerp: Re: CXF JAX-RS: working with multipart form-data

Hi Jean,

Looked into it, so the Content-ID comes from Attachment::id property, the
good news -
this property is not required and could be omitted. Once omitted, the
Content-ID
header will not be sent. I am wondering how do you construct MultipartBody
/Attachment
instance that is being submitted to the service? Thank you.

Best Regards,
    Andriy Redko


> Hi Andriy,

> Indeed, according to https://www.rfc-editor.org/rfc/rfc7578 -
section-4.8
> only the headers Content-Type, Content-Disposition, and
> Content-Transfer-Encoding (in specific cases) are supported.
> Actually (cf. https://www.rfc-editor.org/rfc/rfc7578#section-4.7) the
use
> of Content-Transfer-Encoding is deprecated. It is not clear to me where
> this header is set in CXF when constructing/streaming the parts of the
> multipart/formdata.

> Regards,

> J.P. Urkens



> -----Oorspronkelijk bericht-----
> Van: Andriy Redko <[email protected]>
> Verzonden: zaterdag 17 mei 2025 3:45
> Aan: Jean Pierre URKENS <[email protected]>;
> [email protected]
> Onderwerp: Re: CXF JAX-RS: working with multipart form-data

> Hi Jean,

> Ah interesting, I briefly checked [1], [2] and indeed Content-ID is
> not mentioned there. I will try to look over the weekend what is
> happening,
> thank you.

> [1] https://www.rfc-editor.org/rfc/rfc2388.html
> [2] https://www.rfc-editor.org/rfc/rfc7578

> Best Regards,
>     Andriy Redko

>> Hi Andriy,

>> It seems that the reason is the content-id header in the different
>> multiparts.
>> When I get the MessageToSend object passed along with 'Content-ID:
> <null>'
>> thus having the header like e.g.:

>>         --uuid:0b5e2920-24d8-44fb-ac8f-672731573a3a
>>         Content-Type: application/json
>>         Content-Transfer-Encoding: binary
>>         Content-ID: <null>
>>         Content-Disposition: form-data; name="messageToSend"

>> then it works.


>> -----Oorspronkelijk bericht-----
>> Van: Andriy Redko <[email protected]>
>> Verzonden: vrijdag 16 mei 2025 3:41
>> Aan: Jean Pierre URKENS <[email protected]>;
>> [email protected]
>> Onderwerp: Re: CXF JAX-RS: working with multipart form-data

>> Hi Jean,

>> On the first glance (comparing the example request and the real request
>> you are sending), I see no issues: there is one attachment in both of
> them

>> "upfile1". It is difficult to say what the cause is, but would you be
> able

>> to debug it by sending the POST request manually (Postman or even
curl)?
>> (if that is possible). Since you have both payloads (+ headers), we
> could
>> try to pinpoint the problematic part(s). What do you think?

>> Thank you.

>> Best Regards,
>>     Andriy Redko

>>> Hi Andriy,

>>> Now that I am starting to test with the test server of the service
>>> provider its reporting me an '400 BAD REQUEST' with the message:

>>>         "Number of attachments specified is different from the number
> of
>>> provided MultiPart files"

>>> It appears to me that the server is expecting 3 attachments (multipart
>>> files) and one object (messagetoSend).
>>> However, looking at the
> org.apache.cxf.jaxrs.ext.multipart.MultipartBody
>>> class, I can only add attachments to the multipartbody.
>>> It is not clear to me:
>>>         - what exactly the difference is between what the server
> expects
>>> and what I send (see below)
>>>         - how I can/should combine objects and files together in one
>>> MultipartBody of content-type mutlipart/form-data

>>> I am new to multipart messages, especially when it regards different
>>> content parts having different content-types, so any clarification
>>> would be helpful.

>>> Regards,

>>> J.P. Urkens

>>> The service provider provides following example as a valid request:
>>>         POST /api/v1/messages/messages
>>>         Authorization: Bearer oPilB4*************
>>>         X-Correlation-ID: 7cb999b8-5e2a-450a-ae30-c2be9c1ba01c
>>>         idempotency-key: fc1deb23-3869-4ccd-8afc-f0e40ad2b751
>>>         Accept: */*
>>>         Content-Type: multipart/form-data;
>>> boundary=--------------------------451703456417497490651597
>>>         ----------------------------451703456417497490651597
>>>         Content-Disposition: form-data; name="messageToSend"
>>>         {
>>>             "delivery": "EBOX",
>>>             "eboxDeliveryData": {
>>>                 "recipient": {
>>>                     "eboxType": "CITIZEN",
>>>                     "eboxIdValue": "92042816483"
>>>                 },
>>>                 "subject": {
>>>                     "nl": "Onderwerp van het bericht",
>>>                     "fr": "Sujet du message",
>>>                     "de": "Test onderwerp in DE"
>>>                 },
>>>                 "messageTypeId":
> "139b65e2-12cf-4ef4-9023-641d0637f294",
>>>                 "senderOrganizationId": "0316380841",
>>>                 "senderApplicationId":
>>> "7d96bab6-e365-4c62-804a-aca5e07e2f57",
>>>                 "registeredMail": false,
>>>                 "bodyMainContent": false,
>>>                 "replyAuthorized": false,
>>>                 "attachments": [{
>>>                     "httpPartName": "upfile1",
>>>                     "mainContent": true,
>>>                     "attachmentSigned": false
>>>                 }]
>>>             }
>>>         }
>>>         ----------------------------451703456417497490651597
>>>         Content-Disposition: form-data; name="upfile1";
>>> filename="dummy.pdf"
>>>         <dummy.pdf>
>>>         ----------------------------451703456417497490651597--

>>> While my code is sending something like:
>>>         [MAGDADOC] 2025-05-06 09:25:28,222 [main] INFO  $--$
>>> (org.apache.cxf.ext.logging.slf4j.Slf4jEventSender:84) - REQ_OUT
>>>             Address:
>>> https://iv.api.tni-vlaanderen.be/api/v1/messages/messages
>>>             HttpMethod: POST
>>>             Content-Type: multipart/form-data;
>>> boundary="uuid:eb20df03-1120-4e02-b5c5-cb2f050ea975"
>>>             ExchangeId: 0e03de2a-b3a8-41cb-98e2-7d3771e5dd7e
>>>             Headers: {Authorization=Bearer
>>> 5XDR-xWf77BP5Pq3Xyt4Q5HWhzdAEu2Pmrz1zhzjMsM, Accept=application/json,
>>> Idempotency-Key=18ae8f19-9feb-4ff4-9ec3-52995964b90d,
>>> x-correlation-id=42623224-2755-45b2-abf5-5631ed247324}
>>>             Payload:
>>>         --uuid:eb20df03-1120-4e02-b5c5-cb2f050ea975
>>>         Content-Type: application/json
>>>         Content-Transfer-Encoding: binary
>>>         Content-ID: <messageToSend>
>>>         Content-Disposition: form-data; name="messageToSend";

>>>         {
>>>           "delivery" : "EBOX",
>>>           "eboxDeliveryData" : {
>>>             "recipient" : {
>>>               "eboxType" : "ENTERPRISE",
>>>               "enterpriseNumber" : "0887693124",
>>>               "eboxIdValue" : "0887693124"
>>>             },
>>>             "subject" : {
>>>               "nl" : "ProjectOnontvankelijkMail Project: 2025-EP-0001"
>>>             },
>>>             "registeredMail" : false,
>>>             "attachments" : [ {
>>>               "httpPartName" : "upfile1",
>>>               "mainContent" : true,
>>>               "attachmentSigned" : false
>>>             }],
>>>             "bodyMainContent" : false,
>>>             "businessDataList" : [ ],
>>>             "replyAuthorized" : false,
>>>             "messageActions" : [ ]
>>>           },
>>>           "businessData" : [ ]
>>>         }
>>>         --uuid:eb20df03-1120-4e02-b5c5-cb2f050ea975
>>>         Content-Type: application/pdf
>>>         Content-Transfer-Encoding: binary
>>>         Content-ID: <upfile1>
>>>         Content-Disposition: form-data; name="upfile1";

>>>         <...I skipped the pdf-data content...>

>>>         --uuid:eb20df03-1120-4e02-b5c5-cb2f050ea975--



>>> -----Oorspronkelijk bericht-----
>>> Van: Jean Pierre URKENS <[email protected]>
>>> Verzonden: maandag 24 maart 2025 10:31
>>> Aan: 'Andriy Redko' <[email protected]>
>>> Onderwerp: RE: CXF JAX-RS: working with multipart form-data

>>> Hi andriy,

>>> The project was put on hold for some time. Now it kicks off and I had
a
>>> look at it again.

>>> Debugging the validation showed that method validation failed because

>> the
>>> required header parameters where null.
>>> The method signature includes two header parameters, so in my client I
>> had
>>> to add the headers:

>>>         WebClient wc = WebClient.fromClient(client)
>>>                 .headers(headers)
>>>                 .path("/messages")
>>>                 .accept(MediaType.APPLICATION_JSON_TYPE);

>>> And initially I made the mistake to add the headers with a wrong
header
>>> name. I added them using the 'parameter name' in the method signature
>>> whereas I should have used the name indicated in the @HeaderParam
>>> annotation.

>>>                 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") @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/pdf", required = false) InputStream
>>> upfile1Detail,
>>>                         @FormDataParam(value="upfile2")
>> @Parameter(schema
>>> = @Schema(type = "string", format = "binary")) @Multipart(value =
>>> "upfile2", type="application/pdf", required = false) InputStream
>>> upfile2Detail,
>>>                         @FormDataParam(value="upfile3")
>> @Parameter(schema
>>> = @Schema(type = "string", format = "binary")) @Multipart(value =
>>> "upfile3", type="application/pdf", required = false) InputStream
>>> upfile3Detail)
>>>                 throws GeneralSecurityException,
> AuthorizationException;

>>> I.e. I added a header with name 'xCorrelationId' whereas it should be
'
>>> x-correlation-id'.

>>> Fixing this also passed the validation (as otherwise the header params
>>> would be null and they are listed as required).
>>> So problem was at my side.

>>> Regards,

>>> J.P. Urkens

>>> -----Oorspronkelijk bericht-----
>>> Van: Andriy Redko <[email protected]>
>>> Verzonden: maandag 25 november 2024 3:33
>>> Aan: Jean Pierre URKENS <[email protected]>;
>>> [email protected]
>>> Onderwerp: Re: CXF JAX-RS: working with multipart form-data

>>> Hi Jean,

>>> My apologies for the delay, took me a bit longer to find the time. So

>> I've
>>> added
>>> another test case to the existing pull request [1], that includes
>>> multipart and
>>> bean validation, no issues and the validation works as expected. Could
>> you
>>> help
>>> me out and point what I am doing differently (one difference is that
> the
>>> CXF tests
>>> use Hibernate Validator under the hood but it should not matter I
>> believe,
>>> in any
>>> case I will try to run the tests with the library you are using).

>>> Thanks.

>>> [1] https://github.com/apache/cxf/pull/2152

>>> Best Regards,
>>>     Andriy Redko

>>>> If I deactivate the bean validation feature in

>> createMagdadocServer(...)
>>>> it works.

>>>> Some further questions:

>>>> 1. Setting the target address when using WebClient directly
>>>> ================================================

>>>> Currently I create my client using JAXRSClientFactoryBean
refereincing

>>> my
>>>> service class as follows (<PT> is either my service interface class
or
>>>> some interface extending on it):
>>>>         protected synchronized PT getApiProxy() {
>>>>                 if (apiProxy == null) {
>>>>                         //Get the base address of the service
endpoint
>>>>                         String baseAddress =
>
Configuration.getInstance().getItem("magdadocumentendienst.service.base.ur
>>>> i");
>>>>                         apiProxy = getThreadsafeProxy(baseAddress);
>>>>                 }
>>>>                 return apiProxy;
>>>>         }
>>>>         private PT 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(getPTClass());
>>>>                 factory.setProviders(providers);
>>>>                 factory.setThreadSafe(true);
>>>>                 //only for testing the sending of attachments as
>>>> multipart/form-data
>>>>                 LoggingFeature feature = new LoggingFeature();
>>>>                 feature.setLogMultipart(true);
>>>>                 feature.setLogBinary(true);
>>>>                 feature.setLimit(128*1000);
> feature.addBinaryContentMediaTypes(MediaType.APPLICATION_OCTET_STREAM);
>>>>
feature.addBinaryContentMediaTypes("application/pdf");
>>>>                 factory.setFeatures(Arrays.asList(feature));

>>>>                 PT api = (PT) factory.create(getPTClass());
>>>>                 ClientConfiguration config =
WebClient.getConfig(api);
>>>>                 addTLSClientParameters(config.getHttpConduit());

>>>>                 return api;
>>>>         }

>>>> When invoking a method on it (e.g. getApiProxy().createMessage(...))
> it

>>> is
>>>> handled by cxf the class ClientProxyImpl. This uses the method

>> signature
>>>> and all annotated 'path' declarations to determine the target
address.

>>>> This doesn't happen when I invoke the call through a WebClient, e.g.

>> (as
>>>> to your suggestion):

>>>>                 MultipartBody body = new
>>>> MultipartBody(attachments,MediaType.MULTIPART_FORM_DATA_TYPE,false);
>>>>                 WebClient wc = WebClient.fromClient(client)
>>>>                                 .path("/messages")

>>> .accept(MediaType.APPLICATION_JSON_TYPE);
>>>>                 return
>>>> wc.post(Entity.entity(body,MediaType.MULTIPART_FORM_DATA_TYPE));

>>>> Here I have to set the path manually. Is there a way to construct it
>>>> similar to what ClientProxyImpl does?

>>>> 2.Bean validation
>>>> ==============
>>>> I guess there are different bean validation frameworks/libraries. I'd

>>> like
>>>> to keep bean validation active any suggestions on how to
>>>> resolve/circumvent this issue, e.g.:
>>>> - using another bean validation library
>>>> - disabling validation for a specific method (is this possible?)
>>>> - ....


>>>> Regards,

>>>> J.P. Urkens

>>>> -----Oorspronkelijk bericht-----
>>>> Van: Jean Pierre URKENS <[email protected]>
>>>> Verzonden: maandag 18 november 2024 10:20
>>>> Aan: 'Andriy Redko' <[email protected]>; '[email protected]'
>>>> <[email protected]>
>>>> Onderwerp: RE: CXF JAX-RS: working with multipart form-data

>>>> Validation is done using apache library bval-jsr-2.0.5.jar. So this

>>> might
>>>> be well out-of-scope of CXF.



>>>> -----Oorspronkelijk bericht-----
>>>> Van: Jean Pierre URKENS <[email protected]>
>>>> Verzonden: maandag 18 november 2024 10:02
>>>> Aan: 'Andriy Redko' <[email protected]>; '[email protected]'
>>>> <[email protected]>
>>>> Onderwerp: RE: CXF JAX-RS: working with multipart form-data

>>>> Hi Andriy,

>>>> Thanks for the example. I tried something similar last week but since

>> my
>>>> method was annotated with @Parameter(required=true,schema =
>>>> @Schema(implementation=MessageToSend.class)) request parameter
>>> validation
>>>> failed on the server side (see server trace below).
>>>> I have a JAXRSBeanValidationInInterceptor provider active at the
> server
>>>> side and somehow matching the multipart request body to the method
>>>> signature fails.
>>>> So does it mean that validating request parameters takes place before
>>>> handling the multipart body translating each part to a method
>> parameter?

>>>> My actual method signature looked like:
>>>>         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") @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/pdf", required = false) InputStream
>>>> upfile1Detail,
>>>>                         @FormDataParam(value="upfile2")
>>> @Parameter(schema
>>>> = @Schema(type = "string", format = "binary")) @Multipart(value =
>>>> "upfile2", type="application/pdf", required = false) InputStream
>>>> upfile2Detail,
>>>>                         @FormDataParam(value="upfile3")
>>> @Parameter(schema
>>>> = @Schema(type = "string", format = "binary")) @Multipart(value =
>>>> "upfile3", type="application/pdf", required = false) InputStream
>>>> upfile3Detail)
>>>>         throws GeneralSecurityException, AuthorizationException;


>>>> Even if I reduce the method signature to:
>>>>         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") @NotNull
>>> @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",
>>>> type="application/json", required= true) MessageToSend messageToSend,
>>>>                         @Multipart(value = "upfile1",
>>>> type="application/pdf", required = false) InputStream upfile1Detail,
>>>>                         @Multipart(value = "upfile2",
>>>> type="application/pdf", required = false) InputStream upfile2Detail,
>>>>                         @Multipart(value = "upfile3",
>>>> type="application/pdf", required = false) InputStream upfile3Detail)
>>>>         throws GeneralSecurityException, AuthorizationException;

>>>> I am still getting a '500 server error' saying that arg0 of

>>> createMessage
>>>> may not be null.

>>>> Here are some extracts from the code:

>>>> 1.API interface (only createMessage shown)
>>>> ====================================
>>>>         @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(schema=@S
>>>> chema(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(implementatio
>>>> n=ErrorMessage.class))),
>>>>                         @ApiResponse(
>>>>                                         responseCode = "401",
>>>>                                         description = "Invalid
>>>> authorization",
>>>>                                         content =
>
@Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implementatio
>>>> n=ErrorMessage.class))),
>>>>                         @ApiResponse(
>>>>                                         responseCode = "500",
>>>>                                         description = "Unexpected
>> Server
>>>> Error",
>>>>                                         content =
>
@Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implementatio
>>>> n=ErrorMessage.class))),
>>>>                         @ApiResponse(
>>>>                                         responseCode = "502",
>>>>                                         description = "Bad Gateway",
>>>>                                         content =
>
@Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implementatio
>>>> n=ErrorMessage.class))),
>>>>                         @ApiResponse(
>>>>                                         responseCode = "503",
>>>>                                         description = "Service
>>>> unavailable",
>>>>                                         content =
>
@Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implementatio
>>>> n=ErrorMessage.class))),
>>>>                         @ApiResponse(
>>>>                                         responseCode = "504",
>>>>                                         description = "Gateway
>> Timeout",
>>>>                                         content =
>
@Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implementatio
>>>> n=ErrorMessage.class)))
>>>>         })
>>>>         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") @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/pdf", required = false) InputStream
>>>> upfile1Detail,
>>>>                         @FormDataParam(value="upfile2")
>>> @Parameter(schema
>>>> = @Schema(type = "string", format = "binary")) @Multipart(value =
>>>> "upfile2", type="application/pdf", required = false) InputStream
>>>> upfile2Detail,
>>>>                         @FormDataParam(value="upfile3")
>>> @Parameter(schema
>>>> = @Schema(type = "string", format = "binary")) @Multipart(value =
>>>> "upfile3", type="application/pdf", required = false) InputStream
>>>> upfile3Detail)
>>>>         throws GeneralSecurityException, AuthorizationException;


>>>> 2. Client Invocation (only createMessage)
>>>> =================================
>>>>         public Response createMessage(String xCorrelationId,String
>>>> idempotencyKey,MessageToSend messageToSend,
>>>>                         InputStream upfile1,
>>>>                         InputStream upfile2,
>>>>                         InputStream upfile3)
>>>>         throws GeneralSecurityException, AuthorizationException {
>>>>                 //Create a multipartbody
>>>>                 List<Attachment> attachments = new ArrayList<>();
>>>>                 attachments.add(new
>
AttachmentBuilder().id("messageToSend").object(messageToSend).contentDispo
>>>> sition(new ContentDisposition("form-data;
>>>> name=\"messageToSend\";")).mediaType("application/json").build());
>>>>                 if (upfile1 != null) {
>>>>                         attachments.add(new AttachmentBuilder()
>>>>                                         .id("upfile1")
>>>>                                         .dataHandler(new
>> DataHandler(new
>>>> InputStreamDataSource(upfile1,"application/pdf","upfile1")))
>>>>                                         .contentDisposition(new
>>>> ContentDisposition("form-data; name=\"upfile1\";"))
>>>>                                         .build());
>>>>                 }
>>>>                 if (upfile2 != null) {
>>>>                         attachments.add(new AttachmentBuilder()
>>>>                                         .id("upfile2")
>>>>                                         .dataHandler(new
>> DataHandler(new
>>>> InputStreamDataSource(upfile2,"application/pdf","upfile2")))
>>>>                                         .contentDisposition(new
>>>> ContentDisposition("form-data; name=\"upfile1\";"))
>>>>                                         .build());
>>>>                 }
>>>>                 if (upfile3 != null) {
>>>>                         attachments.add(new AttachmentBuilder()
>>>>                                         .id("upfile3")
>>>>                                         .dataHandler(new
>> DataHandler(new
>>>> InputStreamDataSource(upfile3,"application/pdf","upfile3")))
>>>>                                         .contentDisposition(new
>>>> ContentDisposition("form-data; name=\"upfile1\";"))
>>>>                                         .build());
>>>>                 }
>>>>                 MultipartBody body = new
>>>> MultipartBody(attachments,MediaType.MULTIPART_FORM_DATA_TYPE,false);
>>>>                 //Authorize API client
>>>>                 Client client = WebClient.client(getApiProxy());
>
authorizationHandler.authorize(client,resourceClientId,consumerClientId,nu
>>>> ll,consumerPrivKey);
>>>>                 WebClient wc = WebClient.fromClient(client)
>>>>                                 .path("/messages") /*UJ: Is there a
> way
>>> to
>>>> construct the path as is done in ClientProxyImpl making use of the
> path
>>>> annotations on the method. */

>>> .accept(MediaType.APPLICATION_JSON_TYPE);
>>>>                 return
>>>> wc.post(Entity.entity(body,MediaType.MULTIPART_FORM_DATA_TYPE));
>>>>         }

>>>> 3. Server Implementation
>>>> =====================
>>>>         /**
>>>>          * {@inheritDoc}
>>>>          * @see
>
be.dvtm.aeo.common.magda.documenten.api.MessagesApi#createMessage(java.lan
>>>> g.String, java.lang.String,
>>>> be.dvtm.aeo.common.magda.documenten.model.MessageToSend,
>>>> java.io.InputStream, java.io.InputStream, java.io.InputStream)
>>>>          */
>>>>         @Override
>>>>         public Response createMessage(
>>>>                         String xCorrelationId,
>>>>                         String idempotencyKey,
>>>>                         MessageToSend messageToSend,
>>>>                         InputStream upfile1,
>>>>                         InputStream upfile2,
>>>>                         InputStream upfile3) throws
>>>> GeneralSecurityException, AuthorizationException {

>>>>         //NOT REALLY RELEVANT AS IT DOESN'T GET THIS FAR due to
> failing
>>>> validation
>>>>         }

>>>> 4. Testcase
>>>> =========
>>>> //The server is setup as follows:
>>>>         private Server createMagdadocServer(String baseAddress) {
>>>>                 //Create a Server instance
>>>>                 final JAXRSServerFactoryBean factory = new
>>>> JAXRSServerFactoryBean();
>>>>                 factory.setAddress(baseAddress);

>>>>                 //01.Set root resource class and provider
> factory.setResourceClasses(MagdadocSimulatorApi.class);
> factory.setResourceProvider(MagdadocSimulatorApi.class,
>>>> new SingletonResourceProvider(new MagdadocSimulator(), true));

>>>>                 //02.set Logging feature
>>>>                 List<Feature> featureList = new ArrayList<Feature>();
>>>>                 featureList.add(new LoggingFeature());
>>>>                 factory.setFeatures(featureList);

>>>>                 //03.activate wadl generator (just too see what the

>>> server
>>>> has deployed)
>>>>                 WadlGenerator wadlGen = new WadlGenerator();
>>>>                 wadlGen.setLinkAnyMediaTypeToXmlSchema(true);

>>>>                 //04.Set Providers
>>>>                 List<Object> providers = new ArrayList<Object>();
>>>>                 providers.add(new JacksonJsonProvider(new
>>>> CustomObjectMapper()));
>>>>                 providers.add(new MultipartProvider());
>>>>                 providers.add(wadlGen);
>>>>                 factory.setProviders(providers);

>>>>                 //05. Set interceptors
>>>>                 JAXRSBeanValidationInInterceptor

>> validationInInterceptor
>>> =
>>>> new JAXRSBeanValidationInInterceptor();
>>>>                 validationInInterceptor.setProvider(new
>>>> BeanValidationProvider());
>>>>                 List<Interceptor<? extends Message>> interceptors =
> new
>>>> ArrayList<>();
>>>>                 interceptors.add(validationInInterceptor);
>>>>                 factory.setInInterceptors(interceptors);

>>>>                 return factory.create();
>>>>         }

>>>> 5.Server LOG
>>>> ===========
>>>> [MAGDADOC] 2024-11-18 09:16:10,089 [qtp681015501-59] INFO  $--$
>>>> (org.apache.cxf.ext.logging.slf4j.Slf4jEventSender:84) - REQ_IN
>>>>     Address: http://localhost:8091/services/magdadoc/messages
>>>>     HttpMethod: POST
>>>>     Content-Type: multipart/form-data;
>>>> boundary="uuid:2dc8ff1d-c69a-41c4-93ca-0f3b2bc2b190"
>>>>     ExchangeId: 1eabcefe-eb7b-4d59-af35-2c3e72c8674a
>>>>     Headers: {transfer-encoding=chunked, Accept=application/json,
>>>> Cache-Control=no-cache, User-Agent=Apache-CXF/3.5.8,
>>>> connection=keep-alive, content-type=multipart/form-data;
>>>> boundary="uuid:2dc8ff1d-c69a-41c4-93ca-0f3b2bc2b190",

>>> Host=localhost:8091,
>>>> Pragma=no-cache}
>>>>     Payload:
>>>> --uuid:2dc8ff1d-c69a-41c4-93ca-0f3b2bc2b190
>>>> Content-Type: application/json
>>>> Content-Transfer-Encoding: binary
>>>> Content-ID: <messageToSend>
>>>> Content-Disposition: form-data; name="messageToSend";

>>>> {
>>>>   "delivery" : "EBOX",
>>>>   "eboxDeliveryData" : {
>>>>     "recipient" : {
>>>>       "eboxType" : "ENTERPRISE",
>>>>       "ssin" : null,
>>>>       "enterpriseNumber" : "0123456789",
>>>>       "partition" : null,
>>>>       "eboxIdValue" : "0123456789"
>>>>     },
>>>>     "forTheAttentionOf" : null,
>>>>     "originalMessageId" : null,
>>>>     "subject" : {
>>>>       "nl" : "ProjectOnontvankelijkMail Project: 2025-EP-0001",
>>>>       "fr" : null,
>>>>       "de" : null
>>>>     },
>>>>     "messageTypeId" : null,
>>>>     "expirationDate" : null,
>>>>     "senderOrganizationId" : null,
>>>>     "senderApplicationId" : null,
>>>>     "registeredMail" : false,
>>>>     "attachments" : [ {
>>>>       "attachmentTitle" : null,
>>>>       "httpPartName" : "upfile1",
>>>>       "mainContent" : true,
>>>>       "digest" : null,
>>>>       "attachmentSigned" : false
>>>>     }, {
>>>>       "attachmentTitle" : null,
>>>>       "httpPartName" : "upfile2",
>>>>       "mainContent" : false,
>>>>       "digest" : null,
>>>>       "attachmentSigned" : false
>>>>     }, {
>>>>       "attachmentTitle" : null,
>>>>       "httpPartName" : "upfile3",
>>>>       "mainContent" : false,
>>>>       "digest" : null,
>>>>       "attachmentSigned" : false
>>>>     } ],
>>>>     "bodyMainContent" : false,
>>>>     "bodyContent" : null,
>>>>     "businessDataList" : [ ],
>>>>     "messageData" : null,
>>>>     "paymentData" : null,
>>>>     "replyAuthorized" : false,
>>>>     "replyDueDate" : null,
>>>>     "messageActions" : [ ],
>>>>     "erroneousMessageId" : null
>>>>   },
>>>>   "paperDeliveryData" : null,
>>>>   "emailDeliveryData" : null,
>>>>   "businessData" : [ ]
>>>> }
>>>> --uuid:2dc8ff1d-c69a-41c4-93ca-0f3b2bc2b190
>>>> --- Content suppressed ---

>>>> --- Content suppressed ---

>>>> --- Content suppressed ---
>>>> [MAGDADOC] 2024-11-18 09:16:10,124 [qtp681015501-59] INFO  $--$
>>>> (org.apache.cxf.ext.logging.slf4j.Slf4jEventSender:84) - FAULT_OUT
>>>>     Content-Type: application/json
>>>>     ResponseCode: 500
>>>>     ExchangeId: 1eabcefe-eb7b-4d59-af35-2c3e72c8674a
>>>>     Headers: {}
>>>>     Payload: <ns1:XMLFault
>>>> xmlns:ns1="http://cxf.apache.org/bindings/xformat";><ns1:faultstring
>
xmlns:ns1="http://cxf.apache.org/bindings/xformat";>javax.validation.Constr
>>>> aintViolationException: createMessage.arg0: may not be null,
>>>> createMessage.arg1: may not be null</ns1:faultstring></ns1:XMLFault>



>>>> -----Oorspronkelijk bericht-----
>>>> Van: Andriy Redko <[email protected]>
>>>> Verzonden: maandag 18 november 2024 0:07
>>>> Aan: Jean Pierre URKENS <[email protected]>;
>>>> [email protected]
>>>> Onderwerp: Re: CXF JAX-RS: working with multipart form-data

>>>> 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/syst
>>>> est/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
>>>>>>> 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.

Reply via email to