Hi Jean,
 
I would expect the bean validation to work, I will take a look closely into it 
this week.
 
Regarding JAXRSClientFactoryBean / Proxy and WebClient, these serve two
different purposes: WebClient is generic HTTP client whereas 
JAXRSClientFactoryBean 
creates a typed proxy with the contract (interface). WebClient does not 
introspect 
anything and could only use base address (from client proxy), as far as I know.

Thanks.
 
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 <jean-pierre.urk...@devoteam.com>
> Verzonden: maandag 18 november 2024 10:20
> Aan: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org'
> <dev@cxf.apache.org>
> 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 <jean-pierre.urk...@devoteam.com>
> Verzonden: maandag 18 november 2024 10:02
> Aan: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org'
> <dev@cxf.apache.org>
> 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 <drr...@gmail.com>
> Verzonden: maandag 18 november 2024 0:07
> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
> dev@cxf.apache.org
> 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 <drr...@gmail.com>
>> Verzonden: vrijdag 15 november 2024 21:02
>> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>> dev@cxf.apache.org
>> 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 <jean-pierre.urk...@devoteam.com>
>>> Verzonden: donderdag 14 november 2024 11:29
>>> Aan: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org'
>>> <dev@cxf.apache.org>
>>> 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 <drr...@gmail.com>
>>> Verzonden: woensdag 13 november 2024 23:42
>>> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>> dev@cxf.apache.org
>>> 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 <drr...@gmail.com>
>>>> Verzonden: vrijdag 4 oktober 2024 2:52
>>>> Aan: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>> dev@cxf.apache.org
>>>> 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 <drr...@gmail.com>
>>>>> Sent: donderdag 3 oktober 2024 1:01
>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>> dev@cxf.apache.org
>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data

>>>>> Hi Jean,

>>>>>> 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 <jean-pierre.urk...@devoteam.com>
>>>>>> Sent: vrijdag 5 juli 2024 13:04
>>>>>> To: 'Andriy Redko' <drr...@gmail.com>; 'dev@cxf.apache.org'
>>>>>> <dev@cxf.apache.org>
>>>>>> 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 <drr...@gmail.com>
>>>>>> Sent: vrijdag 5 juli 2024 2:16
>>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>>> dev@cxf.apache.org
>>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data

>>>>>> Hi Jean,

>>>>>> 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 <drr...@gmail.com>
>>>>>>> Sent: donderdag 4 juli 2024 4:44
>>>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>>>> dev@cxf.apache.org
>>>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data

>>>>>>> Hi Jean,

>>>>>>> Interesting, I was experimenting with different ways to express
> what
>>>>>>> you need, but no luck so far, I will try to spend a bit more time
> on
>>>>>>> that this week since OAS 3.x does support multipart [1] but we may
>>>>>>> indeed hit the
>>>>>>> limitation(s) of this particular Swagger Core version. Thank you.

>>>>>>> [1]
> https://swagger.io/docs/specification/describing-request-body/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 <drr...@gmail.com>
>>>>>>>> Sent: woensdag 3 juli 2024 5:57
>>>>>>>> To: Jean Pierre URKENS <jean-pierre.urk...@devoteam.com>;
>>>>>>>> dev@cxf.apache.org
>>>>>>>> Subject: Re: CXF JAX-RS: working with multipart form-data Hi Jean
>>>>>>>> Pierre, I suspect the @Multipart annotation is coming from CXF
>>>>>>>> (org.apache.cxf.jaxrs.ext.multipart.Multipart), right? If yes,
> this
>>>>>>>> is not a part of JAX-RS specification but CXF specific extension.
>>>>>>>> You may need to add Swagger API annotation to the parameters in
>>>>>>>> question:
>>>>>>>>    @Parameter(schema = @Schema(type = "string", format =
> "binary"))
>>>>>>>> Hope it helps.
>>>>>>>> Thank you.
>>>>>>>> Best Regards,
>>>>>>>>     Andriy Redko
>>>>>>>> Monday, July 1, 2024, 12:09:17 PM, you wrote:
>>>>>>>>> Hi all,
>>>>>>>>> I am having problems to correctly annotate service methods which
>>>>>>>>> consumes multipart/form-data that contains attachments next to
>>>>>>>>> other model objects.
>>>>>>>>> I've an openapi specification that contains following requestBody
>>>>>>>>> definition:
>>>>>>>>> /messages:
>>>>>>>>>     post:
>>>>>>>>>       tags:
>>>>>>>>>         - "messages"
>>>>>>>>>       summary: "Send a message, using a channel (email, paper
>>>>>>>>> mail,
>>>>>>>>> ebox) and delivery method (registered or normal) of your choice.
>>>>>>>>> More than 6 upfiles only supported for PAPER delivery."
>>>>>>>>>       operationId: createMessage
>>>>>>>>>       parameters:
>>>>>>>>>         - $ref: '#/components/parameters/CorrelationId'
>>>>>>>>>         - $ref: '#/components/parameters/Idempotency-Key'
>>>>>>>>>       requestBody:
>>>>>>>>>         content:
>>>>>>>>>           multipart/form-data:
>>>>>>>>>             schema:
>>>>>>>>>               type: object
>>>>>>>>>               required:
>>>>>>>>>                 - messageToSend
>>>>>>>>>               properties:
>>>>>>>>>                 messageToSend:
>>>>>>>>>                   $ref: '#/components/schemas/MessageToSend'
>>>>>>>>>                 upfile1:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile2:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile3:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile4:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile5:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile6:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile7:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile8:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile9:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile10:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile11:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile12:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile13:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile14:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile15:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile16:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile17:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile18:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile19:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 upfile20:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>                 qrfile:
>>>>>>>>>                   type: string
>>>>>>>>>                   format: binary
>>>>>>>>>                   nullable: true
>>>>>>>>>         required: true
>>>>>>>>> When using the openapi-generator-maven-plugin v7.6.0 it generates
>>>>>>>>> following method signature:
>>>>>>>>>         @POST
>>>>>>>>>         @Path("/messages")
>>>>>>>>>         @Consumes("multipart/form-data")
>>>>>>>>>         @Produces({ "application/json" })
>>>>>>>>>         @Operation(
>>>>>>>>>                         summary = "Send a message, using a
> channel

>>>>>>>>> (email, paper mail, ebox) and delivery method (registered or
>>>>>>>>> normal) of your choice. More than 6 upfiles only supported for
>>>>>>>>> PAPER delivery.",
>>>>>>>>>                         tags = {"messages" },
>>>>>>>>>                         operationId="createMessage",
>>>>>>>>> security=@SecurityRequirement(name="BearerAuthentication"),
>>>>>>>>>                         responses= {
>>>>>>>>>                                         @ApiResponse(

>>>>>>>>> responseCode = "201",
>>>>>>>>> description = "Created",
>>>>>>>>>                                                         content =
> @Content(mediaType=MediaType.APPLICATION_JSON,array=@ArraySchema(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.


Reply via email to