Returning a Multipart response from a CXFRs Endpoint

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Returning a Multipart response from a CXFRs Endpoint

Xavier López
 I'm trying to get a CXFRS endpoint return a MultiPart response. The CXFRS
service is a REST-SOAP proxy, it just receives a POJO and MIME attachments,
and forwards that to a SOAP service that returns a MultiPart response with
a response POJO and more (different) attachments.

After invoking the SOAP service using a cxf:bean, I see the exchange
message holds the response POJO in the body, and the attachments as well.
However, I'm having a hard time making SimpleCxfRsBinding include the
attachments in the Multipart response.

There seems to be a difference in how DefaultCxfBinding and how
SimpleCxfRsBinding - including DefaultCxfRsBinding as well - handle
exchange mesage attachments. DefaultCxfBinding seems to honour them
regardless of method parameters/return type, making them available in the
exchange, while the CxfRs ones seem to ignore them.

With a REST SEI operation such as this:

    @POST
    @Path("/someOperation")
    @Consumes("multipart/form-data")
    @Produces("multipart/form-data")
    @Multipart(value = "jsonResponse", type = "application/json")
    public BusinessResponse someOperation(BusinessParams parameters);

And a route like:

    from("cxfrs:bean:jaxRSEndpoint?bindingStyle=SimpleConsumer")
    .setHeader("operationName", "someOperation")
    .toD("cxf:bean:someService")

I only get a few headers as a response, I think due to having a
MessageContentsList in the body. If I do
.transform().simple("${body.get(0)}") the response is effectively a
Multipart response with a single part named "jsonResponse" containing the
BusinessResponse POJO only. Found out later this is because
SimpleCxfRsBinding just does that explicitly in its buildResponse() method:

    // avoid using the request MessageContentsList as the entity; it simply
doesn't make sense
    if (base != null && !(base instanceof MessageContentsList)) {
        response.entity(base);
    }

For some reason toD("cxf:bean") is leaving a MessageContentsList there in
the body, maybe it has to do with the fact they're returning MIME
attachments apart from the BusinessResponse POJO?

Anywyay, my main concern is: How can I include the attachments into the
Multipart response, maintaining the name they have in the Messages
attachments Map?

I'd like ideally to maintain the operation signature intact (the return
type) just as the CXF endpoint can do (SOAP SEI has the same exact
signature, returning a Multipart response with MIME attachments, and
DefaultCxfBinding seems to be able to process the attachments into the
Message).

One way I've found by trying and playing with the exchange is changing the
operation signature to return a MultipartBody and just force that manually
in a Processor after invoking the SOAP service:

    .process(new Processor() {
        public void process(Exchange exchange) throws Exception {
            List<Attachment> attachments = new ArrayList<Attachment>();
            attachments.add(new Attachment("jsonResponse",
"application/json",
exchange.getIn().getBody(MessageContentsList.class).get(0)));
            for (String an :  exchange.getIn().getAttachmentNames()) {
                attachments.add(new Attachment(an,
"application/octet-stream", exchange.getIn().getAttachment(an)));
            }
            MultipartBody mpb = new MultipartBody(attachments, true);
            exchange.getIn().setBody(mpb);
        }
    })

Unfortunately this looks extremely verbose and specific for the use case
this CXFRS endpoint will have to cover. Is there a more generic way?
Possibly involving rolling my own Binding class extending
SimpleCxfRsBinding or DefaultCxfRsBinding to do something similar to what
that Processor does?

Made a quick SimpleCxfRsBinding subclass that seemed to work in an
extremely simplified case:

    public static class MyBinding extends SimpleCxfRsBinding {
        @Override
        public Object
populateCxfRsResponseFromExchange(org.apache.camel.Exchange camelExchange,
Exchange cxfExchange) throws Exception {
            Object base =
super.populateCxfRsResponseFromExchange(camelExchange, cxfExchange);
            Message m = camelExchange.hasOut() ? camelExchange.getOut() :
camelExchange.getIn();
            boolean methodAnnotatedWithMultipart = true; // This should be
looked up on method annotation
            if (methodAnnotatedWithMultipart &&
!m.getAttachments().isEmpty()) {
                List<Attachment> attachments = new ArrayList<Attachment>();
                attachments.add(new Attachment("root", "application/json",
base)); // Should take part name and MIME-Type from Method @Multipart
annotation
                for (String an :  m.getAttachmentNames()) {
                    attachments.add(new Attachment(an,
"application/octet-stream", m.getAttachment(an)));
                }
                base = new MultipartBody(attachments, true));
            }
            return buildResponse(camelExchange, base);
        }

Unfortunately, this doesn't work with a POJO response type, apparently
because CXF itself does something similar, and seems to expect a
MultipartBody response type.

Can I get any advice or feedback about this binding. Is this a sensible way
to approach the issue? Maybe it's just plain wrong trying to achieve this?
(pojo arguments operation that will include MIME attachments in the
response - it seems to be allowed just fine with the CXF component).

Thanks