MTOM Attachments with JAXB - 6.2

Talend ESB Service Developer Guide

EnrichVersion
6.2
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

MTOM is a standard which allows your services to transfer binary data efficiently and conveniently. Many frameworks have support for MTOM - Axis2, JAX-WS RI, JBoss WS, XFire, Microsoft's WCF, and more.

If the binary is part of the XML document, it needs to be base64 encoded - taking CPU time and increasing the payload size. When MTOM is enabled on a service, it takes binary data which might normally be part of the XML document, and creates an attachment for it.

Enabling MTOM is a rather simple process. First, you must annotate your schema type or POJO to let JAXB know that a particular field could be a candidate for MTOM optimization. Second, you just tell CXF that you wish to enable MTOM.

This page tells you how to activate MTOM for JAXB. MTOM is also supported in Aegis.

1) Annotating the Message

1a) Modifying your schema for MTOM

Lets say we have a Picture schema type like this:

<schema targetNamespace="http://pictures.com"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <element name="Picture">
      <complexType>
         <sequence>
            <element name="Title" type="xsd:string"/>
            <element name="ImageData" type="xsd:base64Binary"/>
         </sequence>
      </complexType>
   </element>
</schema>

In this case the ImageData element is something we would like to have transferred as an attachment. To do this we just need to add an xmime:expectedContentTypes annotation:

<schema targetNamespace="http://pictures.com" 
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:xmime="http://www.w3.org/2005/05/xmlmime">
   <element name="Picture">
      <complexType>
         <sequence>
            <element name="Title" type="xsd:string"/>
            <element name="ImageData" type="xsd:base64Binary"
               xmime:expectedContentTypes="application/octet-stream"/>
         </sequence>
      </complexType>
   </element>
</schema>

This tells JAXB (which WSDL2Java uses to generate POJOs for your service) that this field could be of any content type. Instead of creating a byte[] array for the base64Binary element, it will create a DataHandler instead which can be used to stream the data.

1b) Annotation your JAXB beans to enable MTOM

If you're doing code first, you need to add an annotation to your POJO to tell JAXB that the field is a candidate for MTOM optimization. Lets say we have a Picture class with has Title and ImageData fields, then it might look like this:

@XmlType
public class Picture {
   private String title;

   @XmlMimeType("application/octet-stream")
   private DataHandler imageData;

   public String getTitle() { return title; }
   public void setTitle(String title) { this.title = title; }

   public DataHandler getImageData() { return imageData; }
   public void setImageData(DataHandler imageData) 
      { this.imageData = imageData; }
}

Note the use of 'application/octet-stream'. According to the standard, you should be able to use any MIME type you like, in order to specify the actual content of the attachment. However, due to a defect in the JAX-B reference implementation, this won't work.

2) Enable MTOM on your service

If you've used JAX-WS to publish your endpoint you can enable MTOM like so:

import javax.xml.ws.Endpoint;
import javax.xml.ws.soap.SOAPBinding;

Endpoint ep = Endpoint.publish("http://localhost/myService", 
   new MyService());
SOAPBinding binding = (SOAPBinding) ep.getBinding();
binding.setMTOMEnabled(true);

Or, if you used XML to publish your endpoint:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:jaxws="http://cxf.apache.org/jaxws"
   xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://cxf.apache.org/jaxws
      http://cxf.apache.org/schema/jaxws.xsd">

   <jaxws:endpoint 
      id="helloWorld" 
      implementor="demo.spring.HelloWorldImpl" 
      address="http://localhost/HelloWorld">
      <jaxws:properties>
         <entry key="mtom-enabled" value="true"/>
      </jaxws:properties>
   </jaxws:endpoint>

</beans>

If you're using the simple frontend you can set the mtom-enabled property on your ServerFactoryBean or ClientProxyFactoryBean:

Map<String,Object> props = new HashMap<String, Object>();
// Boolean.TRUE or "true" will work as the property value here
props.put("mtom-enabled", Boolean.TRUE); 

ClientProxyFactoryBean pf = new ClientProxyFactoryBean();
pf.setPropertyies(props);
...
YourClient client = (YourClient) pf.create();

ServerFactoryBean sf = new ServerFactoryBean();
sf.setPropertyies(props);
...
sf.create();

Similarly, you can use the XML configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:simple="http://cxf.apache.org/simple"
   xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://cxf.apache.org/simple 
      http://cxf.apache.org/schema/simple.xsd">

   <simple:server
      id="helloWorld" 
      serviceClass="demo.spring.HelloWorldImpl" 
      address="http://localhost/HelloWorld">
      <simple:properties>
         <entry key="mtom-enabled" value="true"/>
      </simple:properties>
   </simple:server>

   <simple:client
      id="helloWorldClient" 
      serviceClass="demo.spring.HelloWorldImpl" 
      address="http://localhost/HelloWorld">
      <simple:properties>
         <entry key="mtom-enabled" value="true"/>
      </simple:properties>
   </simple:client>

</beans>

Using DataHandlers

Once you've got the above done, its time to start writing your logic. DataHandlers are easy to use and create. To consume a DataHandler:

Picture picture = ...;
DataHandler handler = picture.getImageData();
InputStream is = handler.getInputStream();

There are many ways to create DataHandlers. You can use a FileDataSource, ByteArrayDataSource, or write your own DataSource:

DataSource source = new ByteArrayDataSource(new byte[] {...}, 
   "content/type");
DataSource source = new FileDataSource(new File("my/file"));

Picture picture = new Picture();
picture.setImageData(new DataHandler(source));