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.