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.
