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.