Sergey recently implemented multipart support for CXF JAXRS implementation which is available in 2.2-SNAPSHOT. He talks more about the feature in his blog here. It certainly provides a simpler programming model like any other CXF frontends, which I always liked with the CXF project and prime reason for its growing popularity among developers. It supports multipart/related, multipart/alternative, multipart/mixed and multipart/form-data out-of-the-box. I gave it a spin and programming multiparts in CXF was like a piece of cake and you would know why by looking at the code shortly. The ability to reuse your code across SOAP and REST is the key differentiator for CXF when compared to Jersey and RESTEasy. I will leave this discussion for another blog post.

I will be reusing the same example which I used in my earlier blog entry for describing Jersey multiparts.

In this sample, I used the brand new HttpClient 4.0 API as the REST client for invoking restful services, as CXF does not ship one yet. HttpClient 4.0 API is completely redesigned and addresses most of the architectural shortcomings from its previous releases, and this version is not backwards compatible.

Here is my maven dependencies:

    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.0-beta2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.0-beta2</version>
        </dependency>
    </dependencies>

Here is the Multipart resource, which processes two body parts and returns the entity with the rank set on the Project bean.

import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;

import javax.activation.DataHandler;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import java.io.InputStream;
import java.util.List;

@Path("/multipart")
public class MultipartResource {
    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_XML)
    public Response processMultiparts(MultipartBody multipartBody) {
        List<Attachment> attachments = multipartBody.getAllAttachments();
        DataHandler dataHandler1 = attachments.get(0).getDataHandler();
        DataHandler dataHandler2 = attachments.get(1).getDataHandler();
        Project project = null;
        try {
            project = getProjectFromInputStream(dataHandler1.getInputStream());
            System.out.println("Processing Attachment 1 ...");
            System.out.println("name : " + project.getName());
            System.out.println("description : " + project.getDescription());
            System.out.println("license : " + project.getLicense());
            System.out.println("SVN URL : " + project.getSvnURL());
            System.out.println("homepage : " + project.getHomepage());

            System.out.println("Processing Attachment 2 ...");
            String comment = getStringFromInputStream(dataHandler2.getInputStream());
            System.out.println("Comment : " + comment);

        } catch (Exception e) {
            throw new WebApplicationException(500);
        }
        return Response.ok(project).build();
    }

    public static Project getProjectFromInputStream(InputStream is) throws Exception {
        JAXBContext c = JAXBContext.newInstance(new Class[]{Project.class});
        Unmarshaller u = c.createUnmarshaller();
        Project project = (Project) u.unmarshal(is);
        project.setRank(1);
        return project;
    }

    public static String getStringFromInputStream(InputStream in) throws Exception {
        CachedOutputStream bos = new CachedOutputStream();
        IOUtils.copy(in, bos);
        in.close();
        bos.close();
        return bos.getOut().toString();
    }

}

Start the Jetty based CXF JAXRS server which deploys the multipart resource as a singleton.

public class CXFRestServer {
    public static void main(String[] args) {
        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(MultipartResource.class);
        sf.setResourceProvider(MultipartResource.class,
                new SingletonResourceProvider(new MultipartResource()));
        sf.setAddress("http://localhost:8081/");
        sf.create();
        System.out.println("Server started.");
    }
}

HttpClient APIs are used to POST the multipart entites to the CXF multipart resource. The first part is a XML File and the second part is a String content. Finally, it processes the response received from the resource.

public class TestClient {

    public static void main(String[] args) throws Exception {
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost("http://localhost:8081/multipart");
        FileBody bin = new FileBody(new File(TestClient.class.getResource("/project_summary.xml").getFile()));
        StringBody comment = new StringBody("Project summary.");

        MultipartEntity reqEntity = new MultipartEntity();
        reqEntity.addPart("project", bin);
        reqEntity.addPart("comment", comment);

        httppost.setEntity(reqEntity);

        System.out.println("executing request " + httppost.getRequestLine());
        HttpResponse response = httpclient.execute(httppost);
        HttpEntity resEntity = response.getEntity();

        System.out.println("---------------------------------------------------------------");
        System.out.println(response.getStatusLine());
        if (resEntity != null) {
            System.out.println("Response content length: " + resEntity.getContentLength());
            System.out.println("Response content type: " + resEntity.getContentType().getValue());
            System.out.println("Project Rank : " + MultipartResource.getProjectFromInputStream(resEntity.getContent()).getRank());
        }
        if (resEntity != null) {            
            resEntity.consumeContent();
        }
    }
}

Place project_summary.xml in your root classpath.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<project>
    <name>CXF</name>
    <description>Apache CXF is an open source services framework.</description>
    <homepage>http://cxf.apache.org</homepage>
    <license>ASL 2.0</license>
    <svnURL>http://svn.apache.org/repos/asf/cxf/trunk/</svnURL>
</project>

This is just one approach of doing multiparts with CXF JAXRS implementation. You can dig into the unit tests to see all possible approaches to handle multiparts with in restful resources. HttpClient 4.0 API is cleaner and flexible and could potentially become a standard API for accessing RESTful web services in Java.

Update (4/11): The sample is updated to use the 2.2 release. The sources can be downloaded from here.

Possibly Related Posts:


9 Responses to “Handling multiparts in Restful applications using CXF”

  • Nickaj

    good blog post ! Arul , it is very useful if you can upload complete source codes for this somewhere..?

  • Tom

    Hello Arul,

    If I use curl to upload an image to cxf, the image size keep growing. Looks like it enter an infinite loop.

    Any suggestions would be much appreciated.

    DataHandler dataHandler1 = attachments.get(0).getDataHandler();

    try {
    //File outFl = new File(“c:\\tmp\\” + imgName);
    File tmpFl = new File(“/image/” + image.getF1Username());
    if (!tmpFl.exists()) {
    tmpFl.mkdirs();
    }
    File outFl = new File(“/image/” + image.getF1Username()+ “/” + image.getImageName());

    FileOutputStream flOut = new FileOutputStream(outFl);
    int anInt;
    while ((anInt = dataHandler1.getInputStream().read()) != -1) {
    flOut.write(anInt);
    }

    flOut.flush();
    flOut.close();

    cir.setCount(1);
    cir.getItems().add(image);
    cir.setDetailIncluded(true);
    cir.setStatusCode(2000);
    cir.setStatusMessage(“successfull”);
    } catch (IOException ex) {
    DigLockLogger.record(Level.SEVERE, ImageServicesResource.class.getName() + “.postImage “,
    StackTraceUtil.getStackTrace(ex));

    cir.setStatusCode(6000);
    cir.setStatusMessage(“Failed to persist the contact image: ”
    + ex.getMessage());
    }

  • Arul

    Hi Tom,

    I believe you got a bug in your code. Change your code as shown below, and it should work:

    InputStream in = dataHandler1.getInputStream();
    while ((anInt = in.read()) != -1) {
    flOut.write(anInt);
    }

    HTH,
    Arul

  • Arul

    Hi Shivakumar,

    Here is an example from CXF system tests:

    @POST
    @Path(“/books/formimage”)
    @Consumes(“multipart/form-data”)
    @Produces(“multipart/form-data”)
    public MultipartBody addBookFormImage(MultipartBody image) throws Exception {
    return image;
    }

    Is this something you were looking for?

    Cheers,
    Arul

  • Saravanan Ramamoorthy

    Hi Arul,

    I tried to send attachments from Jax-Rs.
    I need all content – headers so I have used the Attachment class and passing DataHandler and MultiValuedMap parameters.
    I set all the content headers in the MultiValuedMap.

    I am able to get all content headers exception Content-Location.
    The first attachment’s Content-Location retrieves correctly but the subsequent Content-Location ‘s are changed to lower case(content-location) and the values are set to null.

    Please suggest me.

    Regards
    Saravanan R

  • brian

    Thx! Not much documentation on multipart handling with REST clients in the books.

  • oceanz

    Hi,

    Whenever I try to get a input as mulitpart, I always get the exception:
    Caused by: javax.ws.rs.WebApplicationException
    at org.apache.cxf.jaxrs.client.AbstractClient.reportNoMessageHandler(AbstractClient.java:487)
    at org.apache.cxf.jaxrs.client.AbstractClient.writeBody(AbstractClient.java:401)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.handleMessage(ClientProxyImpl.java:514)

    My request method is :
    @Path(“user/site/plant/device/addDevice3″)
    @POST
    @Produces(“text/xml”)
    @Consumes(“multipart/form”)
    Device addDevice3(Panel p)

    Can you please help on this