Does this also apply to mutlipart/mixed and multipart/related content types? Anyway I created https://issues.apache.org/jira/browse/CXF-9138.
Regards, J.P. -----Oorspronkelijk bericht----- Van: Andriy Redko <[email protected]> Verzonden: dinsdag 20 mei 2025 2:28 Aan: Jean Pierre URKENS <[email protected]>; [email protected] Onderwerp: Re: CXF JAX-RS: working with multipart form-data 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.
