Hi Jean,
I believe the rfc-7578 [1] deprecates Content-Transfer-Encoding (section 4.7),
so
it should be safe to remove it. Please feel free to file an issue [2], it
should be an
easy fix. Thank you!
[1] https://www.rfc-editor.org/rfc/rfc7578
[2] https://issues.apache.org/jira/browse/
Best Regards,
Andriy Redko
> 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.