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 JPU> Hi Andriy, JPU> Thanks for the swift response, but I could still use some clarifications on: JPU> 1) You mention that passing an Attachment object as service method parameter JPU> should work. JPU> My initial test setup did pass an Attachment object as input parameter as shown in ">> 1)API interface declaration" in my mail. However when the JPU> client (see code below) tries to send a request with this signature, the JPU> JAXRSUtils.writeMessageBody(...) method that is called by the CXF stack JPU> throws an exception on the Attachment parameter saying: JPU> okt 03, 2024 9:46:54 AM org.apache.cxf.jaxrs.provider.MultipartProvider JPU> getHandlerForObject SEVERE: No message body writer found for class : class JPU> org.apache.cxf.jaxrs.ext.multipart.Attachment. JPU> okt 03, 2024 9:47:05 AM org.apache.cxf.jaxrs.utils.JAXRSUtils JPU> logMessageHandlerProblem SEVERE: Problem with writing the data, class JPU> java.util.ArrayList, ContentType: multipart/form-data JPU> okt 03, 2024 9:47:14 AM org.apache.cxf.phase.PhaseInterceptorChain JPU> doDefaultLogging WARNING: Interceptor for JPU> {http://api.documenten.magda.common.aeo.dvtm.be/}MessagesApi has thrown JPU> exception, unwinding now JPU> org.apache.cxf.interceptor.Fault: Problem with writing the data, class JPU> java.util.ArrayList, ContentType: multipart/form-data JPU> at JPU> org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.doWriteBody(ClientProxyImpl.java:1142) JPU> at JPU> org.apache.cxf.jaxrs.client.AbstractClient$AbstractBodyWriter.handleMessage(AbstractClient.java:1223) JPU> The JAXRSUtils.writeMessageBody(...) method takes an 'entity' Object that is JPU> a List<Attachment>. The first Attachment in the list contains an object of JPU> type MessageToSend, while the second one contains an object of type JPU> Attachment for which 'no message body writer' could be found. JPU> The stack creates itself an Attachment object for each parameter of the JPU> multipart body, that is why I though that I can not pass it as a parameter JPU> to my service method. I guess I am not allowed to annotate an 'Attachment' JPU> parameter with @Multipart annotation as currently done in the method JPU> signature: JPU> @FormDataParam(value="upfile1") @Parameter(schema = @Schema(type = JPU> "string", format = "binary")) @Multipart(value = "upfile1", JPU> type="application/pdf", required = false) Attachment upfile1Detail JPU> However leaving the @Multipart annotation for the Attachment parameter away JPU> leads to the error: JPU> javax.ws.rs.ProcessingException: Resource method JPU> be.dvtm.aeo.common.magda.documenten.api.MessagesApi.createMessage2 has more JPU> than one parameter representing a request body JPU> I.e. now it is no longer clear that the Attachment parameter is part of the JPU> 'multipart'. That's why I switched to using an InputStream as parameter with JPU> @Multipart annotation but then I loose the Content-Disposition information. JPU> The @Multipart annotation doesn't allow to specify Content-Disposition JPU> information. Is there an alternative here? JPU> 2) Logging of Binary Data. I create my client with: JPU> private static MessagesApi getThreadsafeProxy(String baseAddress) { JPU> JacksonJsonProvider jjProvider = new JacksonJsonProvider(new JPU> CustomObjectMapper()); JPU> List<Object> providers = Arrays.asList(new MultipartProvider(), JPU> jjProvider); JPU> final JAXRSClientFactoryBean factory = new JAXRSClientFactoryBean(); JPU> factory.setAddress(baseAddress); JPU> factory.setServiceClass(MessagesApi.class); JPU> factory.setProviders(providers); JPU> factory.getOutInterceptors().add(new LoggingOutInterceptor()); JPU> factory.getInInterceptors().add(new LoggingInInterceptor()); JPU> factory.setThreadSafe(true); JPU> LoggingFeature feature = new LoggingFeature(); JPU> feature.setLogMultipart(true); JPU> feature.setLogBinary(true); JPU> feature.addBinaryContentMediaTypes(MediaType.APPLICATION_OCTET_STREAM); JPU> feature.addBinaryContentMediaTypes("application/pdf"); JPU> factory.setFeatures(Arrays.asList(feature)); JPU> MessagesApi api = factory.create(MessagesApi.class); JPU> ClientConfiguration config = WebClient.getConfig(api); JPU> addTLSClientParameters(config.getHttpConduit()); JPU> return api; JPU> } JPU> Here I do activate the logging for multipart and binary and also added some JPU> mediatypes (although couldn't find what it actually does). So I was JPU> expecting to see the whole message (attachments are rather small as it is a JPU> test). JPU> Well I used wireshark to get the full message and it doesn't show any JPU> Content-Disposition headers for the multipart elements. JPU> Regards, JPU> J.P. Urkens JPU> -----Original Message----- JPU> From: Andriy Redko <drr...@gmail.com> JPU> Sent: donderdag 3 oktober 2024 1:01 JPU> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>; dev@cxf.apache.org JPU> Subject: Re: CXF JAX-RS: working with multipart form-data JPU> Hi Jean, JPU>> What is the correct way to annotate a file attachment as part of a JPU>> mutlipart/form-data content body? JPU> We have many examples in our test suites over here [1], it really depends on JPU> the problem at hand. JPU>> -> Question-01: Is this the appropriate way to pass files JPU>> (PDF documents) as attachment in a mutlipart/form-data request, or are JPU> there better ways? JPU> You would probably better of with "multipart/mixed" [2] as in you case, you JPU> are sending different data types. But "multipart/form-data" should be fine JPU> as well. The right answer answer depends on how large the files are. In many JPU> cases you are better off using chunked transfer (no need to read the whole JPU> file in memory), like you do with InputStream. JPU>> -> Question-02: When I activate logging, I can't see JPU>> anything about the file attachment, not is content, nor the JPU>> appropriate Content-Disposition settings? I get '--Content JPU> suppressed--', e.g.: JPU> Correct, by default the logging interceptors do not log binary data, you JPU> could checks the docs here [3]. JPU>> -> Question-03: Don't I need to set anything regarding the JPU>> Content-Disposition header? The example here is simplified in a JPU>> sense that I used a test setup with just one file attachment. In JPU>> the real interface up to JPU>> 20 attachments can be passed. Somehow I need to know which part relates JPU> to JPU>> which attachment or not? JPU> This is probably caused by the fact you are sending the InputStream and not JPU> an Attachment, if my understanding is correct. I am pretty sure passing the JPU> Attachment (as you did initially) should work. JPU>> -> Question-04: At the server implemtation side, since JPU>> upfile1Detail is passed as a method parameter, who is going to JPU>> close this InputStream at the server side? JPU> The stream should be closed by the service implementation (since the stream JPU> is expected to be consumed). The Apache CXF runtime will try to close the JPU> stream in most cases as well but sometimes it may not be able to. JPU> Hope it answers your questions. Thanks! JPU> [1] JPU> https://github.com/apache/cxf/blob/main/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java JPU> [2] JPU> https://learn.microsoft.com/en-us/exchange/troubleshoot/administration/multipart-mixed-mime-message-format JPU> [3] https://cxf.apache.org/docs/message-logging.html JPU> Best Regards, JPU> Andriy Redko JPU>> Hi Andriy, JPU>> What is the correct way to annotate a file attachment as part of a JPU>> mutlipart/form-data content body? JPU>> I currently have the following (only the relevant parts): JPU>> 1)API interface declaration JPU>> ====================== JPU>> @POST JPU>> @Path("/messages1") JPU>> @Consumes("multipart/form-data") JPU>> @Produces({ "application/json" }) JPU>> @Operation(...) JPU>> @ApiResponses(...) JPU>> Response createMessage1( JPU>> @HeaderParam("x-correlation-id") @NotNull JPU>> @Size(min = 10, max = 36) @Parameter(description="ID of the JPU>> transaction. Use this ID for log tracing and incident handling.") JPU> String xCorrelationId, JPU>> @HeaderParam("Idempotency-Key") @NotNull JPU>> @Size(min = 10, max = 36) @Parameter(description="When retrying a JPU>> failed call, the retry call should have the same Idempotency Key.") JPU> String idempotencyKey, JPU>> @FormDataParam(value="messageToSend") JPU>> @Parameter(required=true,schema = JPU>> @Schema(implementation=MessageToSend.class)) @Multipart(value = JPU>> "messageToSend", type="application/json", required= true) JPU>> MessageToSend messageToSend, JPU>> @FormDataParam(value="upfile1") JPU>> @Parameter(schema = @Schema(type = "string", format = "binary")) JPU>> @Multipart(value = "upfile1", type="application/octet-stream", JPU>> required = false) Attachment upfile1Detail); JPU>> So I pass the file to upload as an Attachment object. JPU>> 2)API client test code JPU>> ================= JPU>> String xCorrelationId = UUID.randomUUID().toString(); JPU>> String idempotencyKey = JPU>> UUID.randomUUID().toString(); JPU>> //01. Get the attachment to include JPU>> String fileName = "test.pdf"; JPU>> try (InputStream is = JPU>> this.getClass().getClassLoader().getResourceAsStream(fileName); JPU>> BufferedReader reader = new JPU>> BufferedReader(new InputStreamReader(is))) { JPU>> if (is == null) { JPU>> Assert.fail("Couldn't load test.pdf JPU> from classpath!"); JPU>> } JPU>> DataHandler dataHandler = new DataHandler(is, JPU> "application/pdf"); JPU>> ContentDisposition cd = new JPU>> ContentDisposition("attachment;name=upfile1;filename="+fileName); JPU>> Attachment upfile1Detail = new JPU> AttachmentBuilder() JPU>> .id("upfile1") JPU>> .dataHandler(dataHandler) JPU>> .contentDisposition(cd) JPU>> .mediaType("application/pdf") JPU>> .build(); JPU>> //02. create the message to send JPU>> MessageToSend mts = new MessageToSend(); JPU>> //03. Call the server JPU>> Response resp = JPU>> apiClient.createMessage1(xCorrelationId, idempotencyKey, mts, JPU>> upfile1Detail); JPU>> When running the API client test code and getting at '03.' The method: JPU>> JAXRSUtils.writeMessageBody(List<WriterInterceptor> JPU>> writers,Object entity,Class<?> type, Type genericType,Annotation[] JPU>> annotations,MediaType mediaType,MultivaluedMap<String, Object> JPU>> httpHeaders,Message message) JPU>> is called. The 'entity' object is a list of Attachment objects where: JPU>> - the first Attachment object contains an object of type JPU>> MessageToSend JPU>> - the second Attachment object contains an object of type JPU>> Attachment JPU>> This second object leads to a fatal error within the JPU>> JAXRSUtils.writeMessageBody(...) method: JPU>> okt 02, 2024 1:36:02 PM JPU>> org.apache.cxf.jaxrs.provider.MultipartProvider JPU>> getHandlerForObject JPU>> SEVERE: No message body writer found for class : class JPU>> org.apache.cxf.jaxrs.ext.multipart.Attachment. JPU>> okt 02, 2024 1:36:02 PM JPU>> org.apache.cxf.jaxrs.utils.JAXRSUtils JPU>> logMessageHandlerProblem JPU>> SEVERE: Problem with writing the data, class JPU>> java.util.ArrayList, JPU>> ContentType: multipart/form-data JPU>> I modified the interface and implementation classes to use JPU>> 'InputStream upfile1Detail' as type for the input parameter of the JPU>> service method. And in this case my 'dummy' server implementation JPU>> can read the file and save it to disk. JPU>> -> Question-01: Is this the appropriate way to pass files JPU>> (PDF documents) as attachment in a mutlipart/form-data request, or are JPU> there better ways? JPU>> -> Question-02: When I activate logging, I can't see JPU>> anything about the file attachment, not is content, nor the JPU>> appropriate Content-Disposition settings? I get '--Content JPU> suppressed--', e.g.: JPU>> [MAGDADOC] 2024-10-02 14:29:08,911 [main] INFO JPU>> $--$ JPU>> (org.apache.cxf.ext.logging.slf4j.Slf4jEventSender:84) - REQ_OUT JPU>> Address: JPU>> http://localhost:8091/services/magdadoc/api/v1/messages/messages1 JPU>> HttpMethod: POST JPU>> Content-Type: multipart/form-data; JPU>> boundary="uuid:3c3fa9c0-8470-4655-b026-3ed09f79e862" JPU>> ExchangeId: a583a695-d881-4fa7-b65a-8961cdbbd412 JPU>> Headers: {Authorization=Bearer JPU>> f4262ccf-3250-4bcf-a1bc-7ee1bf9a56cf, JPU>> Accept=application/json, JPU>> Idempotency-Key=bd06c05d-9fe2-4b60-b8db-5ad1121b74dc, JPU>> x-correlation-id=511c51ba-95fe-4f69-9443-f05c377cffab} JPU>> Payload: JPU>> --uuid:3c3fa9c0-8470-4655-b026-3ed09f79e862 JPU>> Content-Type: application/json JPU>> Content-Transfer-Encoding: binary JPU>> Content-ID: <messageToSend> JPU>> { JPU>> "delivery" : "AUTOMATIC", JPU>> "eboxDeliveryData" : { /* all fine */}, JPU>> "paperDeliveryData" : {/* all fine */}, JPU>> "emailDeliveryData" : null, JPU>> "businessData" : [ ] JPU>> } JPU>> --uuid:3c3fa9c0-8470-4655-b026-3ed09f79e862 JPU>> --- Content suppressed --- JPU>> -> Question-03: Don't I need to set anything regarding the JPU>> Content-Disposition header? The example here is simplified in a JPU>> sense that I used a test setup with just one file attachment. In JPU>> the real interface up to JPU>> 20 attachments can be passed. Somehow I need to know which part relates JPU> to JPU>> which attachment or not? JPU>> -> Question-04: At the server implemtation side, since JPU>> upfile1Detail is passed as a method parameter, who is going to JPU>> close this InputStream at the server side? JPU>> Regards, JPU>> J.P. Urkens JPU>> P.S.: Is there a migration document describing upgrading CXF-3.5.6 JPU>> (my current version) to CXF-3.5.8 (latest JDK8 version). I'd like JPU>> to know whether upgrading can happen without too much burden. JPU>> -----Original Message----- JPU>> From: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com> JPU>> Sent: vrijdag 5 juli 2024 13:04 JPU>> To: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org' JPU>> <dev@cxf.apache.org> JPU>> Subject: RE: CXF JAX-RS: working with multipart form-data JPU>> Hi Andriy, JPU>> When searching the net I came along this Jersey annotation but JPU>> since I was not depending on 'Jersey' components I skipped it. So JPU>> apparently swagger-core has processing for externally defined JPU>> annotations (like this FormDataParam in Jersey), indeed inside JPU> know-how. JPU>> I included it in my project as JPU>> io.swagger.v3.oas.annotations.FormDataParam, JPU>> since it should somehow be included in the supported swagger JPU>> annotations (maybe we should submit a request for it to the JPU>> swagger-core team) and this indeed generates an appropriate JPU> openapi.json spec. JPU>> Thanks alot, JPU>> J.P. Urkens JPU>> -----Original Message----- JPU>> From: Andriy Redko <drr...@gmail.com> JPU>> Sent: vrijdag 5 juli 2024 2:16 JPU>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>; JPU>> dev@cxf.apache.org JPU>> Subject: Re: CXF JAX-RS: working with multipart form-data JPU>> Hi Jean, JPU>> Here is how you could make it work (there is some magic knowledge JPU>> involved sadly). First of all, define such annotation anywhere in JPU>> your codebase (where it dims appropriate): JPU>> import java.lang.annotation.ElementType; import JPU>> java.lang.annotation.Retention; import JPU>> java.lang.annotation.RetentionPolicy; JPU>> import java.lang.annotation.Target; JPU>> @Target({ElementType.PARAMETER, ElementType.METHOD, JPU>> ElementType.FIELD}) JPU>> @Retention(RetentionPolicy.RUNTIME) JPU>> public @interface FormDataParam { JPU>> String value(); JPU>> } JPU>> Use this annotation on each Attachment parameter: JPU>> /* Skipping other annotations as those are not important here */ JPU>> public Response createMessage( JPU>> @HeaderParam("x-correlation-id") @NotNull @Size(min = 10, JPU>> max = 36) @Parameter(description="ID of the transaction. Use this JPU>> ID for log tracing and incident handling.") String xCorrelationId, JPU>> @HeaderParam("Idempotency-Key") @Size(min = 10, max = 36) JPU>> @Parameter(description="When retrying a failed call, the retry call JPU>> should have the same Idempotency Key.") String idempotencyKey, JPU>> @FormDataParam("upfile1") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile1", JPU>> type="application/octet-stream", required = false) InputStream JPU>> upfile1Detail, JPU>> @FormDataParam("upfile2") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile2", JPU>> type="application/octet-stream", required = false) InputStream JPU>> upfile2Detail, JPU>> @FormDataParam("upfile3") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile3", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile3Detail, JPU>> @FormDataParam("upfile4") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile4", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile4Detail, JPU>> @FormDataParam("upfile5") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile5", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile5Detail, JPU>> @FormDataParam("upfile6") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile6", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile6Detail, JPU>> @FormDataParam("upfile7") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile7", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile7Detail, JPU>> @FormDataParam("upfile8") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile8", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile8Detail, JPU>> @FormDataParam("upfile9") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "upfile9", JPU>> type="application/octet-stream", required = false) Attachment JPU> upfile9Detail, JPU>> @FormDataParam("upfile10") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile10", type="application/octet-stream", required = false) JPU>> Attachment upfile10Detail, JPU>> @FormDataParam("upfile11") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile11", type="application/octet-stream", required = false) JPU>> Attachment upfile11Detail, JPU>> @FormDataParam("upfile12") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile12", type="application/octet-stream", required = false) JPU>> Attachment upfile12Detail, JPU>> @FormDataParam("upfile13") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile13", type="application/octet-stream", required = false) JPU>> Attachment upfile13Detail, JPU>> @FormDataParam("upfile14") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile14", type="application/octet-stream", required = false) JPU>> Attachment upfile14Detail, JPU>> @FormDataParam("upfile15") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile15", type="application/octet-stream", required = false) JPU>> Attachment upfile15Detail, JPU>> @FormDataParam("upfile16") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile16", type="application/octet-stream", required = false) JPU>> Attachment upfile16Detail, JPU>> @FormDataParam("upfile17") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile17", type="application/octet-stream", required = false) JPU>> Attachment upfile17Detail, JPU>> @FormDataParam("upfile18") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile18", type="application/octet-stream", required = false) JPU>> Attachment upfile18Detail, JPU>> @FormDataParam("upfile19") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile19", type="application/octet-stream", required = false) JPU>> Attachment upfile19Detail, JPU>> @FormDataParam("upfile20") @Parameter(schema = JPU>> @Schema(type = "string", format = "binary")) @Multipart(value = JPU>> "upfile20", type="application/octet-stream", required = false) JPU>> Attachment upfile20Detail, JPU>> @FormDataParam("qrfile") @Parameter(schema = @Schema(type JPU>> = "string", format = "binary")) @Multipart(value = "qrfile", JPU>> type="application/octet-stream", required = false) Attachment JPU> qrfileDetail JPU>> ) { JPU>> .... JPU>> } JPU>> With that, you will get a nice request body schema (publishing a JPU>> bit large YAML snippet to preserve the context): JPU>> paths: JPU>> /sample/messages: JPU>> post: JPU>> tags: JPU>> - messages JPU>> summary: "Send a message, using a channel (email, paper mail, JPU>> ebox) and delivery\ JPU>> \ method (registered or normal) of your choice. More than 6 JPU>> upfiles only supported\ JPU>> \ for PAPER delivery." JPU>> operationId: createMessage JPU>> parameters: JPU>> - name: x-correlation-id JPU>> in: header JPU>> description: ID of the transaction. Use this ID for log JPU>> tracing and incident JPU>> handling. JPU>> required: true JPU>> schema: JPU>> maxLength: 36 JPU>> minLength: 10 JPU>> type: string JPU>> - name: Idempotency-Key JPU>> in: header JPU>> description: "When retrying a failed call, the retry call JPU>> should have the\ JPU>> \ same Idempotency Key." JPU>> schema: JPU>> maxLength: 36 JPU>> minLength: 10 JPU>> type: string JPU>> requestBody: JPU>> content: JPU>> multipart/form-data: JPU>> schema: JPU>> type: object JPU>> properties: JPU>> upfile1: JPU>> type: string JPU>> format: binary JPU>> upfile2: JPU>> type: string JPU>> format: binary JPU>> upfile3: JPU>> type: string JPU>> format: binary JPU>> upfile4: JPU>> type: string JPU>> format: binary JPU>> upfile5: JPU>> type: string JPU>> format: binary JPU>> upfile6: JPU>> type: string JPU>> format: binary JPU>> upfile7: JPU>> type: string JPU>> format: binary JPU>> upfile8: JPU>> type: string JPU>> format: binary JPU>> upfile9: JPU>> type: string JPU>> format: binary JPU>> upfile10: JPU>> type: string JPU>> format: binary JPU>> upfile11: JPU>> type: string JPU>> format: binary JPU>> upfile12: JPU>> type: string JPU>> format: binary JPU>> upfile13: JPU>> type: string JPU>> format: binary JPU>> upfile14: JPU>> type: string JPU>> format: binary JPU>> upfile15: JPU>> type: string JPU>> format: binary JPU>> upfile16: JPU>> type: string JPU>> format: binary JPU>> upfile17: JPU>> type: string JPU>> format: binary JPU>> upfile18: JPU>> type: string JPU>> format: binary JPU>> upfile19: JPU>> type: string JPU>> format: binary JPU>> upfile20: JPU>> type: string JPU>> format: binary JPU>> qrfile: JPU>> type: string JPU>> format: binary JPU>> The key here is @FormDataParam annotation which (originally) comes JPU>> from Jersey but has special treatment in Swagger Core (but, likely, JPU>> no attribution to Jersey). JPU>> Hope it helps! JPU>> Thank you. JPU>> Best Regards, JPU>> 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 <drr...@gmail.com> >>> Sent: donderdag 4 juli 2024 4:44 >>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>; >>> dev@cxf.apache.org >>> 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/multipa >>> 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(sch >>>> 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(impleme >>>> n >>>> t >>>> ation=ErrorMessage.class))), >>>> @ApiResponse( >>>> responseCode = "401", >>>> description = "Invalid authorization", content = >>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(impleme >>>> n >>>> t >>>> ation=ErrorMessage.class))), >>>> @ApiResponse( >>>> responseCode = "500", >>>> description = "Unexpected Server Error", content = >>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(impleme >>>> n >>>> t >>>> ation=ErrorMessage.class))), >>>> @ApiResponse( >>>> responseCode = "502", >>>> description = "Bad Gateway", >>>> content = >>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(impleme >>>> n >>>> t >>>> ation=ErrorMessage.class))), >>>> @ApiResponse( >>>> responseCode = "503", >>>> description = "Service unavailable", content = >>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(impleme >>>> n >>>> t >>>> ation=ErrorMessage.class))), >>>> @ApiResponse( >>>> responseCode = "504", >>>> description = "Gateway Timeout", >>>> content = >>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(impleme >>>> 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 <drr...@gmail.com> >>>> Sent: woensdag 3 juli 2024 5:57 >>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>; >>>> dev@cxf.apache.org >>>> 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(sc >>>>> 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(implem >>>>> e >>>>> nt >>>>> ation=ErrorMessage.class))), >>>>> @ApiResponse( >>>>> >>>>> responseCode = "401", >>>>> description >>>>> = "Invalid authorization", >>>>> content = >>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem >>>>> e >>>>> nt >>>>> ation=ErrorMessage.class))), >>>>> @ApiResponse( >>>>> >>>>> responseCode = "500", >>>>> description >>>>> = "Unexpected Server Error", >>>>> content = >>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem >>>>> e >>>>> nt >>>>> ation=ErrorMessage.class))), >>>>> @ApiResponse( >>>>> >>>>> responseCode = "502", >>>>> description >>>>> = "Bad Gateway", >>>>> content = >>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem >>>>> e >>>>> nt >>>>> ation=ErrorMessage.class))), >>>>> @ApiResponse( >>>>> >>>>> responseCode = "503", >>>>> description >>>>> = "Service unavailable", >>>>> content = >>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem >>>>> e >>>>> nt >>>>> ation=ErrorMessage.class))), >>>>> @ApiResponse( >>>>> >>>>> responseCode = "504", >>>>> description >>>>> = "Gateway Timeout", >>>>> content = >>>>> @Content(mediaType=MediaType.APPLICATION_JSON,schema=@Schema(implem >>>>> 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.