Entity view (Content)

MTOM Web Services in Mule

By aabed
Jun. 29, 2016

SOAP Message Transmission Optimization Mechanism (MTOM) specifies an optimized method for sending binary data as part of a SOAP message. When MTOM is used to optimize a SOAP message, it serializes the binary data into a MIME Multipart/Related message. The base64Binary data is extracted from the SOAP message and packaged as separate binary attachments within the MIME message, in a similar manner to e-mail attachments.

Using MTOM for large payloads is very efficient because it will stream the data over the transportation channel rather than consuming the Mule server resources by loading the binary data into memory first before sending it.

In this blog we will explain how to implement two sample Mule applications. The first application is a web service provider that receives an MTOM binary attachment in the SOAP request and writes it into the file system. The second application is a web service consumer that reads a file from the file system and sends it as an MTOM attachment to the web service. Both file end-points will be streaming the data to from/to the file system so that the whole end-to-end solution will not load any binary data into memory.

In our example we will use the WSDL first technique where we will design the WSDL contract of our web service, and using a wsdl2java tool, we generate the java stubs required to implement the CXF web service in Mule.

The WSDL File:

The WSDL file below defines an upload web service with a complex input parameter compose of a String filename and a binary file content attributes. The binary data will be Base64 encoded and the "application/octet-stream" content type attribute tells the wsdl2java to generate a DataHandler implementation for this attribute which will allow stream.

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="FileStorage" targetNamespace="http://appnovation.com/mtom"
  xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://appnovation.com/mtom"
  xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:xsd1="http://appnovation.com/types/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xmime="http://www.w3.org/2005/05/xmlmime">

  <types>
    <schema targetNamespace="http://appnovation.com/types/" xmlns="http://www.w3.org/2001/XMLSchema">
      <complexType name="imageFileType">
        <sequence>
          <element name="fileName" type="xsd:string" />
          <element name="imageData" type="xsd:base64Binary" xmime:expectedContentTypes="application/octet-stream" />
        </sequence>
      </complexType>
      <element name="imageFile" type="xsd1:imageFileType" />
    </schema>
  </types>

  <message name="uploadRequest">
    <part name="file" element="xsd1:imageFile" />
  </message>
  <message name="uploadResponse">
    <part name="success" type="xsd:boolean" />
  </message>

  <portType name="imageIO">
    <operation name="upload">
      <input message="tns:uploadRequest" name="uploadRequest" />
      <output message="tns:uploadResponse" name="uploadResponse" />
    </operation>
  </portType>

  <binding name="imageIOSOAPBinding" type="tns:imageIO">
    <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <operation name="upload">
      <soap12:operation soapAction="" style="document" />
      <input name="uploadRequest">
        <soap12:body use="literal" />
      </input>
      <output name="uploadResponse">
        <soap12:body use="literal" />
      </output>
    </operation>
  </binding>

  <service name="imageIOSOAPService">
    <port name="HTTP_Port" binding="tns:imageIOSOAPBinding">
      <soap12:address location="http://localhost:8081/mtom"/>
    </port>
  </service>
</definitions>

The Web Service Provider:

The server applicaiton implementation is pretty straight forward. The Mule flow components are the http inbound endpoint, a CXF component, and a Java component that receive the SOAP request and stream the file data into the file system.

The Mule flow is shown below. Notice the mtomEnabled="true" attribute in the CXF component which will enable the MTOM streaming.

<mule ...>
    <http:listener-config name="HTTP" host="localhost" port="8081" />
    <cxf:configuration name="CXF" initializeStaticBusInstance="true" />
    <flow name="mtomFlow">
        <http:listener config-ref="HTTP" path="/mtom" parseRequest="false"/>
        <cxf:jaxws-service configuration-ref="CXF" serviceClass="com.appnovation.example.mtom.ImageIO" mtomEnabled="true" />
        <component class="com.appnovation.example.mtom.ImageIOImpl" doc:name="Java"/>
    </flow>
</mule>

To generate the java stub classes using wsdl2java you can use Mule CXF component visual editor "Generate from WSDL" as shown below. Just make sure you copy the WSDL file into the project src/main/resources folder.

The Java component implementation is shown below. Notice that it copies the http input sream into the file output stream directly without preloading any data into memory.

package com.appnovation.example.mton.mtom;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.IOUtils;

public class ImageIOImpl implements ImageIO {

	@Override
	public boolean upload(ImageFileType file) {
		try {
			String fileName = file.getFileName();
			InputStream inStream = file.getImageData().getInputStream();
			System.out.println("Uploading file: " + fileName);
			IOUtils.copy(inStream, new FileOutputStream("/tmp/mton/out/" + fileName));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

}

The Web Service Consumer: 

The client part implementation of this example is as easy as the server part. The flow contains a file inbound endpoint, a Java custom processor to create the SOAP request, a CXF component and an http outbout endpoint to stream the SOAP message through the http channel.

The Mule flow XML definition is shown below. Notice that stream is enabled in both file inbound endpoint and CXF component. Enabling streaming in file inbound endpoint will result an InputStream payload instead of the the file content.

<mule ...>
    <cxf:configuration name="CXF" enableMuleSoapHeaders="true" initializeStaticBusInstance="true" />
    <file:connector name="FILE" outputPattern="*" autoDelete="true" streaming="true" validateConnections="true" />
    <http:connector name="HTTP" validateConnections="true" ... />
    <flow name="mtomFlow">
        <file:inbound-endpoint moveToDirectory="/tmp/mtom/backup" path="/tmp/mtom/in" connector-ref="FILE" />
        <logger message="File: #[message.inboundProperties['originalFilename']]" level="INFO" />
        <custom-processor class="com.appnovation.example.processor.MtomRequestProcessor" />
        <outbound-endpoint name="outboundMtomEndpoint" address="http://localhost:8081/mtom" exchange-pattern="request-response" connector-ref="HTTP">
            <cxf:jaxws-client clientClass="com.appnovation.example.mtom.ImageIOSOAPService" operation="upload" port="HTTP_Port" wsdlLocation="classpath:example.wsdl" mtomEnabled="true" >
            </cxf:jaxws-client>
        </outbound-endpoint>
    </flow>
</mule>

To generate the java stub classes, you can use the CXF component visial editor as explained earlier. Just make sure to copy the wsdl file into the project src/main/resources.

The Java custom processor implementation is shown below. Notice the DataHandler class implementation of the binary attribute in the SOAP request. The implementaiton will link the file input stream received from the inbound endopoint to the DataHandler which will enable direct streaming of data without preloading it into memory.

package com.appnovation.example.processor;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.MimetypesFileTypeMap;

import org.mule.api.MuleEvent;
import org.mule.api.MuleMessage;
import org.mule.api.processor.MessageProcessor;

import com.appnovation.example.mtom.ImageFileType;

public class MtomRequestProcessor implements MessageProcessor {
	@Override
	public MuleEvent process(final MuleEvent event) {
		try {
			MuleMessage msg = event.getMessage();
			final InputStream inStream = msg.getPayload(InputStream.class);
			final String fileName = msg.getInboundProperty("originalFilename");
			ImageFileType imageFile = new ImageFileType();
			imageFile.setFileName(fileName);
			imageFile.setImageData(new DataHandler(new DataSource() {
				@Override
				public String getContentType() {
					return new MimetypesFileTypeMap().getContentType(fileName);
				}
				@Override
				public InputStream getInputStream() throws IOException {
					return inStream;
				}
				@Override
				public String getName() {
					return fileName;
				}
				@Override
				public OutputStream getOutputStream() throws IOException {
					return null;
				}
			}));
			msg.setPayload(imageFile);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return event;
	}
}

And that's about it. Running both web service consumer and provider above will stream files read from /tmp/mtom/in folder into /tmp/mtom/out folder over the http channel as SOAP message MTOM attachments without comsuming the Mule server memory and CPU resources.