Working With Attachments - 6.3

Talend ESB Service Developer Guide

EnrichVersion
6.3
EnrichProdName
Talend Data Fabric
Talend Data Services Platform
Talend ESB
Talend MDM Platform
Talend Open Studio for ESB
Talend Real-Time Big Data Platform
task
Design and Development
Installation and Upgrade
EnrichPlatform
Talend ESB

CXF JAX-RS provides a comprehensive support for reading and writing multipart messages. Regular multiparts and XOP attachments are supported.

Reading Attachments

Individual parts can be mapped to StreamSource, InputStream, DataSource or custom Java types for which message body readers are available.

For example:

@POST
@Path("/books/jaxbjson")
@Produces("text/xml")
public Response addBookJaxbJson(
   @Multipart(value = "rootPart", type = "text/xml") Book2 b1,
   @Multipart(value = "book2", type = "application/json") Book b2) 
   throws Exception {
}

Note that in this example it is expected that the root part named 'rootPart' is a text-xml Book representation, while a part named 'book2' is a Book JSON sequence.

All attachment parts can be accessed as a list of CXF JAX-RS Attachment objects with every Attachment instance providing all the information about a specific part. Similarly, the whole request body can be represented as a CXF JAX-RS MultipartBody:

@POST
public void addAttachments(MultipartBody body) throws Exception {
List<Attachment> all = body.getAllAtachments();
Attachment att = body.getRootAttachment();
}

When handling complex multipart/form-data submissions (such as those containing files) MultipartBody (and Attachment) need to be used directly.

When working with either List of Attachments or MultipartBody, one may want to process the individual parts with the help of some custom procedures. It is also possible to do the following:

@POST
public void addAttachments(MultipartBody body) throws Exception {
   Book book = body.getAttachmentObject("bookPart", Book.class);
}

@POST
public void addAttachments(List<Attachment> attachments) 
   throws Exception {
   for (Attachment attachment : attachments) {
      Book book = attachment.getObject(Book.class);
   }  
}

When reading large attachments, the "attachment-directory" and "attachment-memory-threshold" contextual properties can be used to control what folder the attachments exceeding a given threshold (in bytes) can be temporarily saved to.

Writing Attachments

It is possible to write attachments to the output stream, both on the client and server sides.

On the server side it is sufficient to update the @Produces value for a given method:

public class Resource {
   private List<Book> books; 
   @Produces("multipart/mixed;type=text/xml")
   public List<Book> getBooksAsMultipart() {
      return booksList;
   }

   @Produces("multipart/mixed;type=text/xml")
   public Book getBookAsMultipart() {
      return booksList;
   }
}

Note that a 'type' parameter of the 'multipart/mixed' media type indicates that all parts in the multiparts response should have a Content-Type header set to 'text/xml' for both getBooksAsMultipart() and getBookAsMultipart() method responses. The getBooksAsMultipart() response will have 3 parts, the first part will have its Content-ID header set to "root.message@cxf.apache.org", the next parts will have '1' and '2' ids. The getBookAsMultipart() response will have a single part only with its Content-ID header set to "root.message@cxf.apache.org".

When returning mixed multiparts containing objects of different types, you can either return a Map with the media type string value to Object pairs or MultipartBody:

public class Resource {
   private List<Book> books; 
   @Produces("multipart/mixed")
   public Map<String, Object> getBooks() {
      Map<String, Object> map = new LinkedHashMap<String, Object>();
      map.put("text/xml", new JaxbBook());
      map.put("application/json", new JSONBook());
      map.put("application/octet-stream", imageInputStream);
      return map;  
   } 

   @Produces("multipart/mixed")
   public MultipartBody getBooks2() {
      List<Attachment> atts = new LinkedList<Attachment>();
      atts.add(new Attachment("root", "application/json", 
         new JSONBook()));
      atts.add(new Attachment("image", "application/octet-stream", 
         getImageInputStream()));
      return new MultipartBody(atts, true);  
   }

}

Similarly to the method returning a list in a previous code fragment, getBooks() will have the response serialized as multiparts, where the first part will have its Content-ID header set to "root.message@cxf.apache.org", the next parts will have ids like '1', '2', etc.

In getBooks2() one can control the content ids of individual parts.

You can also control the contentId and the media type of the root attachment by using a Multipart annotation:

public class Resource {
   @Produces("multipart/form-data")
   @Multipart(value = "root", type = "application/octet-stream") 
   public File testGetImageFromForm() {
      return getClass().getResource("image.png").getFile();
   }
}

One can also have lists or maps of DataHandler, DataSource, Attachment, byte arrays or InputStreams handled as multiparts.

On the client side multiparts can be written the same way. For example:

WebClient client = WebClient.create("http://books");
client.type("multipart/mixed").accept("multipart/mixed");
List<Attachment> atts = new LinkedList<Attachment>();
atts.add(new Attachment("root", "application/json", new JSONBook()));
atts.add(new Attachment("image", "application/octet-stream", 
   getImageInputStream()));
List<Attachment> atts = client.postAndGetCollection(atts, 
   Attachment.class);

When using proxies, a Multipart annotation attached to a method parameter can also be used to set the root contentId and media type. Proxies do not support at the moment multiple method parameters annotated with Multipart (as opposed to the server side) but only a single multipart parameter:

public class Resource {
   @Produces("multipart/mixed")
   @Consumes("multipart/form-data")
   @Multipart(value = "root", type = "application/octet-stream") 
   public File postGetFile(@Multipart(value = "root2", 
      type = "application/octet-stream") File file) {}
}

A method-level Multipart annotation will affect the writing on the server side and the reading on the client side. A parameter-level Multipart annotation will affect writing on the client (proxy) side and reading on the server side. You don't have to use Multipart annotations.

Uploading files

At the moment the only way to upload a file is to use a MultipartBody, Attachment or File:

WebClient client = WebClient.create("http://books");
client.type("multipart/form-data");
ContentDisposition cd = new ContentDisposition(
   "attachment;filename=image.jpg");
Attachment att = new Attachment("root", imageInputStream, cd);
client.post(new MultipartBody(att));

// or just post the attachment if it's a single part request only
client.post(att);

// or just use a file
client.post(getClass().getResource("image.png").getFile());

Using File provides a simpler way as the runtime can figure out how to create a ContentDisposition from a File.

Forms and multiparts

The Forms in HTML documents recommendation suggests that multipart/form-data requests should mainly be used to upload files.

One way to deal with multipart/form-data submissions is to deal directly with a CXF JAXRS Attachment class and get a Content-Disposition header and/or the underlying input stream. It is also possible to have individual multipart/form-data parts read by registered JAX-RS MessageBodyReaders, something that is already possible to do for types like multipart/mixed or multipart/related. For example, this payload:

--bqJky99mlBWa-ZuqjC53mG6EzbmlxB
Content-Disposition: form-data; name="bookJson"
Content-Type: application/json; charset=US-ASCII
Content-Transfer-Encoding: 8bit
Content-ID: <jsonPart>

{"Book":{"name":"CXF in Action - 1","id":123}}

--bqJky99mlBWa-ZuqjC53mG6EzbmlxB
Content-Disposition: form-data; name="bookXML"
Content-Type: application/xml
Content-Transfer-Encoding: 8bit
Content-ID: <jaxbPart>
<Book><name>CXF in Action</name></Book>
--bqJky99mlBWa-ZuqjC53mG6EzbmlxB--       

can be handled by the following method:

@POST
@Path("/books/jsonjaxbform")
@Consumes("multipart/form-data")
public Response addBookJaxbJsonForm(@Multipart("jsonPart") Book b1,
   @Multipart("bookXML") Book b2) {}

Note that once a request has more than two parts then one needs to start using @Mutipart, the values can refer to either ContentId header or to ContentDisposition/name. At the moment using @Multipart is preferred to using @FormParam unless a plain name/value submission is dealt with. The reason is that @Multipart can also specify an expected media type of the individual part and thus act similarly to a @Consume annotation.

When dealing with multiple parts one can avoid using @Multipart and just use List<Atachment>, List<Book>, etc.

Finally, multipart/form-data requests with multiple files (file uploads) can be supported. For example, this payload:

--bqJky99mlBWa-ZuqjC53mG6EzbmlxB
Content-Disposition: form-data; name="owner"
Content-Type: text/plain

Larry
--bqJky99mlBWa-ZuqjC53mG6EzbmlxB
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed; boundary=_Part_4_701508.1145579811786

--_Part_4_701508.1145579811786
Content-Disposition: form-data; name="book1"
Content-Type: application/json; charset=US-ASCII
Content-Transfer-Encoding: 8bit

{"Book":{"name":"CXF in Action - 1","id":123}}
--_Part_4_701508.1145579811786
Content-Disposition: form-data; name="book2"
Content-Type: application/json; charset=US-ASCII
Content-Transfer-Encoding: 8bit

{"Book":{"name":"CXF in Action - 2","id":124}}
--_Part_4_701508.1145579811786--
--bqJky99mlBWa-ZuqjC53mG6EzbmlxB--

can be handled by the following method:

@POST
@Path("/books/filesform")
@Produces("text/xml")
@Consumes("multipart/form-data")
public Response addBookFilesForm(@Multipart("owner") String name,
    @Multipart("files") List<Book> books) {}

If you need to know the names of the individual file parts embedded in a "files" outer part (such as "book1" and "book2"), then please use List<Attachment> instead. It is currently not possible to use a Multipart annotation to refer to such inner parts but you can easily get the names from the individual Attachment instances representing these inner parts.

Note that it is only the last request which has been structured according to the recommendation on how to upload multiple files but it is more complex than the other simpler requests linked to in this section.

Please note that using JAX-RS FormParams is recommended for dealing with plain application/www-url-encoded submissions consisting of name/value pairs only.

XOP support

CXF JAXRS clients and endpoints can support XML-binary Optimized Packaging (XOP). What it means at the practical level is that a JAXB bean containing binary data is serialized using a multipart packaging, with the root part containing non-binary data only but also linking to co-located parts containing the actual binary payloads. Next it is deserialized into a JAXB bean on the server side.

If you would like to experiment with XOP then you need to set an "mtom-enabled" property on CXF jaxrs endpoints and clients.