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 <[email protected]>
JPU> Sent: donderdag 3 oktober 2024 1:01
JPU> To: Jean Pierre URKENS <[email protected]>;
[email protected]
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 <[email protected]>
JPU>> Sent: vrijdag 5 juli 2024 13:04
JPU>> To: 'Andriy Redko' <[email protected]>; '[email protected]'
JPU>> <[email protected]>
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 <[email protected]>
JPU>> Sent: vrijdag 5 juli 2024 2:16
JPU>> To: Jean Pierre URKENS <[email protected]>;
JPU>> [email protected]
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 <[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/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 <[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(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.