Consuming MTOM/XOP web services from Axis 1

Axis 1 does not support MTOM specification for the client side (and it only supports this specification partially for the server side [1]). However, sometimes this combination is required due to technologic restrictions.

In this tutorial is described a way to access MTOM/XOP web services for file transfers using Axis 1 as client by implementing two Java projects:

  1. Creating a MTOM/XOP web service with Apache CXF 2
  2. Consuming the web service with Apache Axis 1

1. Creating a MTOM/XOP web service with Apache CXF

Let’s create a simple web service exposing a XOP node to download a file. Several samples to build a CXF web services client are available [2], so only main resources are detailed.

1.a. Web service interface

package cxf.mtom.server.service;

import javax.jws.WebService;

import cxf.mtom.server.bean.ResponseDownloadFile;

@WebService
public interface DownloadFile {

	ResponseDownloadFile getFile() throws Exception;

}

1.b. Web service implementation

package cxf.mtom.server.service;

import java.io.File;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.jws.WebService;

import cxf.mtom.server.bean.ResponseDownloadFile;

@WebService(endpointInterface = "cxf.mtom.server.service.DownloadFile", serviceName = "DownloadFileWS")
public class DownloadFileImpl implements DownloadFile {

	public ResponseDownloadFile getFile() throws Exception {
		ResponseDownloadFile rdf = new ResponseDownloadFile();
		rdf.setFileName("c:/tmp/readme.txt");
		rdf.setFileType("plain/text");
		rdf.setFile(new DataHandler(new FileDataSource(new File("C:/tmp/readme.txt"))));
		return rdf;
	}

}

1.c. Web service response bean

package cxf.mtom.server.bean;

import javax.activation.DataHandler;

/**
 * Web service response bean.
 */
public class ResponseDownloadFile {

	private String fileName;
	private String fileType;
	private DataHandler file;

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getFileType() {
		return fileType;
	}

	public void setFileType(String fileType) {
		this.fileType = fileType;
	}

	public DataHandler getFile() {
		return file;
	}

	public void setFile(DataHandler file) {
		this.file = file;
	}
}
 

1.d. Spring configuration for CXF

<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.xsd
 					http://cxf.apache.org/jaxws
 					http://cxf.apache.org/schemas/jaxws.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
  <jaxws:endpoint id="downloadfile"
                  implementor="cxf.mtom.server.service.DownloadFileImpl"
                  address="/DownloadFileWS">
       <jaxws:properties>
           <entry key="mtom-enabled" value="true"/>
       </jaxws:properties>	
  </jaxws:endpoint>
</beans>

1.e. Web descriptor

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <display-name>DownloadFile</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:cxf.xml</param-value>
  </context-param>
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  <servlet>
    <servlet-name>CXFServlet</servlet-name>
    <servlet-class>
        org.apache.cxf.transport.servlet.CXFServlet
    </servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>CXFServlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
</web-app>

1.f. Generated WSDL (p. e. http://localhost:8080/cxfMTOMServer/services/DownloadFileWS?wsdl)

<?xml version='1.0' encoding='utf-8'?>
<wsdl:definitions name="DownloadFileWS"
targetNamespace="http://service.server.mtom.cxf/"
xmlns:ns1="http://schemas.xmlsoap.org/soap/http"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://service.server.mtom.cxf/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:types>
    <xs:schema elementFormDefault="unqualified"
    targetNamespace="http://service.server.mtom.cxf/" version="1.0"
    xmlns:tns="http://service.server.mtom.cxf/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="getFile" type="tns:getFile" />
      <xs:element name="getFileResponse"
      type="tns:getFileResponse" />
      <xs:complexType name="getFile">
        <xs:sequence />
      </xs:complexType>
      <xs:complexType name="getFileResponse">
        <xs:sequence>
          <xs:element minOccurs="0" name="return"
          type="tns:responseDownloadFile" />
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="responseDownloadFile">
        <xs:sequence>
<!-- XOP Node -->          
          <xs:element minOccurs="0" name="file"
          type="xs:base64Binary" />
          <xs:element minOccurs="0" name="fileName"
          type="xs:string" />
          <xs:element minOccurs="0" name="fileType"
          type="xs:string" />
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
  </wsdl:types>
  <wsdl:message name="getFileResponse">
    <wsdl:part element="tns:getFileResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getFile">
    <wsdl:part element="tns:getFile" name="parameters"></wsdl:part>
  </wsdl:message>
  <wsdl:portType name="DownloadFile">
    <wsdl:operation name="getFile">
      <wsdl:input message="tns:getFile" name="getFile">
      </wsdl:input>
      <wsdl:output message="tns:getFileResponse"
      name="getFileResponse"></wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="DownloadFileWSSoapBinding"
  type="tns:DownloadFile">
    <soap:binding style="document"
    transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="getFile">
      <soap:operation soapAction="" style="document" />
      <wsdl:input name="getFile">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="getFileResponse">
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="DownloadFileWS">
    <wsdl:port binding="tns:DownloadFileWSSoapBinding"
    name="DownloadFileImplPort">
      <soap:address location="http://localhost:8080/cxfMTOMServer/services/DownloadFileWS" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

2. Consuming the web service with Apache Axis 1

And now the Axis 1 client to consume CXF web service.

2.a. Create standard axis client through WSDL file

Use wsdl2java tool [3].

2.b. Axis client configuration (client-config.wsdd)

<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

    <handler name="log" type="java:org.apache.axis.handlers.LogHandler"/>
    
    <!-- MTOM Axis 1 Handler -->
    <handler name="xop" type="java:axis.mtom.client.handler.XOPHandler">
        <parameter name="serviceName" value="{http://service.server.mtom.cxf/}DownloadFileWS"/>
        <parameter name="operationName" value="getFile"/>
        <parameter name="mtomNodePath" value="getFileResponse.return.file"/>
    </handler>
    
    <globalConfiguration>
       <requestFlow>
           <handler type="log"/>
       </requestFlow>
       <responseFlow>
            <handler type="log"/>
            <handler type="xop"/>
       </responseFlow>
    </globalConfiguration>
    
    <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
    
</deployment>

2.c. Axis Response Handler for XOP

package axis.mtom.client.handler;

import java.io.InputStream;
import java.io.StringWriter;
import java.util.Iterator;

import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.apache.axis.AxisFault;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.attachments.AttachmentPart;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.handlers.BasicHandler;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Retrieve MTOM/XOP file referenced by "mtomNodePath" parameter 
 * from "client-config.wsdd" file.
 * Exposes a Stream including file content to clients through 
 * ThreadLocal mechanism. 
 *
 */
public class XOPHandler extends BasicHandler {

	// "client-config.wsdd" parameters
	private static final String HANDLER_OPTION_MTOM_NODE_PATH = "mtomNodePath";
	private static final String HANDLER_OPTION_OPERATION_NAME = "operationName";
	private static final String HANDLER_OPTION_SERVICE_NAME = "serviceName";
	
	// Stream including file content 
	private static ThreadLocal documentStream = new ThreadLocal();
    public static InputStream getDocumentStream() {
        return (InputStream)documentStream.get();
    }
    
	/**
	 * Copy Stream reference to file content by searching specified SOAP node on client-config.wsdd file.
	 */
    public void invoke(MessageContext msgContext) throws AxisFault {
    	
    	// Only process targeted service and operation
    	if (isTargetedServiceAndOperation(msgContext)) {
    	
			// Recover SOAP node from client-config.wsdd parameter
			String mtomNodePath = (String) getOption(HANDLER_OPTION_MTOM_NODE_PATH);
			if (mtomNodePath == null) {
				System.out.println("No mtomNodePath parameter specified on client-config.wsdd file");
			} else {
				
				String[] pathToNode = mtomNodePath.split("\\.");
				int nodesLevel = pathToNode.length;
				
				try {
					
					// Recover SOAP Body
					Message msg = msgContext.getResponseMessage();
					SOAPMessage soapMessage = msgContext.getResponseMessage();
		            SOAPBody soapBody = soapMessage.getSOAPBody();
		            
		        	// Search mtomNodePath in SOAP Body
		            Node currentNode = null;
		            int currentNodeLevel = 0;
		            NodeList nodeList = soapBody.getChildNodes();
		            while (currentNodeLevel < nodesLevel && nodeList != null) {
		            	currentNode = null;
		                for (int i = 0; i < nodeList.getLength(); i++) {
		                	if (nodeList.item(i).getLocalName().equals(pathToNode[currentNodeLevel])) {
		                		currentNode = nodeList.item(i);
		                		break;
		                	}
		                }
		                if (currentNode != null) {
		                    nodeList = currentNode.getChildNodes();
		                } else {
		                	nodeList = null;
		                }
		            	currentNodeLevel++;
		            }
		            
		            // mtomNodePath found
		            if (currentNode != null) {
		            
			            // Recover reference to SOAP Attachment from node attribute "Include" 
		            	String attachmentRef = null;
		            	if (currentNode.getChildNodes() != null && 
						    currentNode.getChildNodes().getLength() > 0) {
				    		NamedNodeMap nnm = currentNode.getChildNodes().item(0).getAttributes();
				    		for (int j = 0; j < nnm.getLength(); j++) {
				    			attachmentRef = nnm.item(j).getNodeValue();
				    		}
				    		// (!) Delete XOP node to prevent Axis service unmarshalling errors
				    		currentNode.getParentNode().removeChild(currentNode);
		            	} else {
		            		System.out.println("No reference founded at node " + mtomNodePath);
		            	}
			    		
						// Recover SOAP Attachment from attachments using the reference
		            	if (attachmentRef != null) {
		            		
				            Iterator attachments = msg.getAttachments();
							while (attachments.hasNext()) {
								AttachmentPart attachmentPart = (AttachmentPart)attachments.next();
								if (attachmentPart.getContentIdRef().equals(attachmentRef)) {
								    documentStream.set(attachmentPart.getDataHandler().getInputStream());
								}
							}
							
				            // Perform SOAP Body operations to persist changes
							TransformerFactory tff = TransformerFactory.newInstance();
				            Transformer tf = tff.newTransformer();
				            Source sc = soapMessage.getSOAPPart().getContent();
				            StringWriter modifiedBody = new StringWriter();
				            StreamResult result = new StreamResult(modifiedBody);
				            tf.transform(sc, result);            
				            Message modifiedMsg = new Message(modifiedBody.toString(), false);
				            msgContext.setResponseMessage(modifiedMsg);
				            
		            	} else {
		            		System.out.println("No reference to XOP file founded at node " + mtomNodePath);
		            	}
			            
		            } else {
		            	
		            	System.out.println("Node " + mtomNodePath + " not found at Response SOAP Body");
		            	
		            }
		            
				} catch (Exception e) {
					throw new AxisFault("Handler exception", e);
				}
			}
        }
		
	}

    /**
     * Check targeted service and operation from "client-config.wsdd" parameters
     * with runtime service and operation from the web service response.
     */
    private boolean isTargetedServiceAndOperation(MessageContext msgContext) {
    	
    	boolean parametersFound = false;
    	
    	String serviceName = (String) getOption(HANDLER_OPTION_SERVICE_NAME);
        if (serviceName == null) {
        	System.out.println("No serviceName property specified on client-config.wsdd file");
        }
        
		Service locator = (Service) msgContext.getProperty(Call.WSDL_SERVICE);
		if (locator != null && locator.getServiceName().toString().equals(serviceName)) {
	    	String operationName = (String) getOption(HANDLER_OPTION_OPERATION_NAME);
	        if (operationName == null) {
	        	System.out.println("No operationName property specified on client-config.wsdd file");
	        } else if (msgContext.getOperation().getName().equals(operationName)) {
			    parametersFound = true;
	        }
		}
		
		return parametersFound;
    }
    
}

2. d. Sample web service client

package axis.mtom.client;

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

import axis.mtom.client.handler.XOPHandler;

import cxf.mtom.server.service.DownloadFileWSLocator;
import cxf.mtom.server.service.DownloadFileWSSoapBindingStub;
import cxf.mtom.server.service.ResponseDownloadFile;

/**
 * Access MTOM / XOP service from Axis 1
 */
public class TestClient {

	public static void main(String[] args) throws Exception {

       	// Axis client invocation
		DownloadFileWSSoapBindingStub binding =
			(DownloadFileWSSoapBindingStub) new DownloadFileWSLocator().getDownloadFileImplPort();
        ResponseDownloadFile rdf = binding.getFile();

        // Recovered file name
        System.out.println(rdf.getFileName());

        // Recovered file type
        System.out.println(rdf.getFileType());

        // UNUSED METHOD -> System.out.println(rdf.getFile());
        // Recover file content
        convertStreamToFile(XOPHandler.getDocumentStream(), rdf.getFileName());

	}

	/**
	 * Sample method: write Stream to File
	 * @param is
	 * @param fileName
	 * @throws IOException
	 */
	public static void convertStreamToFile(InputStream is, String fileName) throws IOException {

		if (is != null) {

			FileOutputStream fos = new FileOutputStream(fileName);

			byte[] buffer = new byte[1024];
			try {
				int n;
				while ((n = is.read(buffer)) != -1) {
					fos.write(buffer, 0, n);
				}
			} finally {
				is.close();
				fos.close();
			}
		}
	}
}

3. Final considerations

3.a. Sample SOAP Request

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <getFile xmlns="http://service.server.mtom.cxf/" />
  </soapenv:Body>
</soapenv:Envelope>

3.b. Sample SOAP Response

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ns2:getFileResponse xmlns:ns2="http://service.server.mtom.cxf/">
      <return>
        <file>
		  <!-- XOP node -->
          <xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include"
          href="cid:89f719ee-ed51-4955-a80d-103f1d851763-4@cxf.apache.org" />
        </file>
        <fileName>c:/tmp/readme.txt</fileName>
        <fileType>plain/text</fileType>
      </return>
    </ns2:getFileResponse>
  </soap:Body>
</soap:Envelope>


--MIMEBoundary4A7AE55984E7438034
content-type: application/octet-stream
content-transfer-encoding: binary
content-id: <89f719ee-ed51-4955-a80d-103f1d851763-4@cxf.apache.org>

Binary Data.....
--MIMEBoundary4A7AE55984E7438034--

3.c. Resources

[1] http://axis.apache.org/axis2/java/core/docs/mtom-guide.html
[2] http://cxf.apache.org/docs/writing-a-service-with-spring.html
[3] http://axis.apache.org/axis/java/user-guide.html#WSDL2JavaBuildingStubsSkeletonsAndDataTypesFromWSDL

Un comentario en “Consuming MTOM/XOP web services from Axis 1

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s