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

Anuncios

AXIS2 + JIBX web service client step by step

Below a low level tutorial on generating web services clients based on AXIS2 and JIBX marshalling. In real world, many of this tasks can be automatized using some tool like ANT or MAVEN.

0. Required tools

1. Initial resources

  • XSD file for JIBX
  • WSDL file for AXIS2

 
service.xsd



  
    
      
        
          
            
              
              
            
          
        
        
          
            
              
            
          
        
      
    
  
  
    
      
        
          
            
              
              
              
              
            
          
        
        
          
            
              
              
            
          
        
      
    
  

service.wsdl



  
    
      
        
          
            
              
                
                  
                  
                
              
            
            
              
                
                  
                
              
            
          
        
      
      
        
          
            
              
                
                  
                  
                  
                  
                
              
            
            
              
                
                  
                  
                
              
            
          
        
      
    
  
  
    
  
  
    
  
  
    
      
      
    
  
  
    
    
      
      
        
      
      
        
      
    
  
  
    
      
    
  

Note. Character “:” changed by “_” for visualization purpouses in “service.wsdl”

2. Generate JIBX binding

[Previous tasks: copy service.xsd file on JIBX home directory]

Command line (JIBX home)

Input: java -jar xsd2jibx.jar service.xsd

Output: com/bar/foo/binding.xml, com/bar/foo/FooRequest.java, com/bar/foo/FooRequestRequestData.java, com/bar/foo/FooRequestRequestHeader.java, com/bar/foo/FooResponse.java, com/bar/foo/FooResponseResponseData.java, com/bar/foo/FooResponseResponseHeader.java

3. Java compiler

Command line (JIBX home)

Input: javac -sourcepath com/bar/foo/*.java

Output: com/bar/foo/FooRequest.class, com/bar/foo/FooRequestRequestData.class, com/bar/foo/FooRequestRequestHeader.class, com/bar/foo/FooResponse.class, com/bar/foo/FooResponseResponseData.class, com/bar/foo/FooResponseResponseHeader.class

4. Binding compiler

Command line (JIBX home)

Input: java -jar jibx-bind.jar com/bar/foo/binding.xml

Output: com/bar/foo/JiBX_com_bar_foo_bindingFactory.class, com/bar/foo/JiBX_com_bar_foo_bindingFooRequest_access.class, com/bar/foo/JiBX_com_bar_foo_bindingFooRequestRequestData_access.class, com/bar/foo/JiBX_com_bar_foo_bindingFooRequestRequestHeader_access.class, com/bar/foo/JiBX_com_bar_foo_bindingFooResponse_access.class, com/bar/foo/JiBX_com_bar_foo_bindingFooResponseResponseData_access.class, com/bar/foo/JiBX_com_bar_foo_bindingFooResponseResponseHeader_access.class

5. Generate AXIS2 client

[Previous tasks: copy service.wsdl file, binding.xml (generated on step 2) and “com” folder (generated on step 4) on AXIS2 home directory]

Command line (AXIS2 home)

Input: wsdl2java -uri service.wsdl -o build -ss -sd -g -d jibx -ssi -Ebindingfile binding.xml

Output: build/src/com/bar/foo/FooService.java, build/src/com/bar/foo/FooServiceCallbackHandler.java, build/src/com/bar/foo/FooServiceMessageReceiverInOut.java, build/src/com/bar/foo/FooServiceSkeleton.java, build/src/com/bar/foo/FooServiceSkeletonInterface.java, build/src/com/bar/foo/FooServiceStub.java

6. AXIS2 Client usage

[Previous tasks: config a Java project including all the resources generated by now, JIBX libraries and AXIS2 libraries.]

import com.bar.foo;

FooRequest fooRequest = new FooRequest();

FooRequestRequestHeader fooRequestHeader = new FooRequestHeader();
fooRequestHeader.setType("1");
fooRequestHeader.setVersion("1");

FooRequestRequestData fooRequestData = new FooRequestData();
fooRequestData.setIdObject(1);

fooRequest.setRequestHeader(fooRequestHeader);
fooRequest.setRequestData(fooRequestData);

FooServiceStub fooStub = new FooServiceStub();
FooResponse fooResponse = fooStub.foo(fooRequest);

System.out.println(fooResponse.getResponseData().getName());
System.out.println(fooResponse.getResponseData().getAttributes());