Interoperability between Microsoft WSE 2.0 and Apache Axis 1.2 RC2 using a SOAP TCP Channel

Summary:

This article shows how to extend the usage of the SOAP TCP channel, a sample custom transport supplied with Microsoft Web Services Enhancements (WSE) 2.0, to provide interoperability with Apache Axis 1.2.  The walkthrough will show how Microsoft WSE 2.0 and Apache Axis 1.2 can send SOAP requests via TCP.

Download:

Click here to download the source code for this article.

Required Software to Run the Code Samples:

·         Microsoft .NET Framework 1.1

(Go to http://www.microsoft.com/net and refer to the downloads link)

·         Microsoft Visual Studio .NET 2003

(http://www.microsoft.com/visualstudio)

·         Microsoft Web Services Enhancements (WSE) 2.0 SP2

(http://msdn.microsoft.com/webservices)

·         Apache Axis 1.2 RC2

          (http://www.apache.org/dist/ws/axis/1_2RC2/)

·         Compliant Java 2 Standard Edition (J2SE) SDK (Version 1.4.2 Recommended)

(http://www.java.com)

Installing and Configuring the Sample Code:

This article shows how to extend the usage of the SOAP TCP channel, a sample custom transport supplied with Microsoft WSE 2.0, to provide interoperability with Apache Axis 1.2.  The walkthrough will show how Microsoft WSE 2.0 and Apache Axis 1.2 can send SOAP requests via TCP. 

The sample code for this article can be downloaded here.  The default directory is c:\axis.tcp.  Although you are free to specify your own directory, the walkthroughs in this article assume you are using this default one.

Installing and Configuring WSE 2.0:

If you have not already, you should install Microsoft WSE 2.0, the download for which can be found here.

The installation is straightforward; however it is recommended that you choose Visual Studio Developer as the installation type.  This will ensure that the correct options are installed for integration with Visual Studio .NET 2003.

Installing Apache Axis 1.2:

You can install Apache Axis 1.2 RC2 from the www.apache.org website.  This can be found here.

After download, unzip the contents of the zip file to the root of your hard drive.  This will create a directory called c:\axis-1_2RC2.  You are free to change this location, but doing so will require updating some of the build scripts supplied with this article.

The following JAR files will be downloaded with this install, and placed in the c:\axis-1_2RC2\lib directory:

·         axis.jar

·         commons-discovery.jar

·         commons-logging.jar

·         jaxrpc.jar

·         saaj.jar

·         log4j-1.2.8.jar

·         wsdl4j.jar

To run this sample, you will require three additional JAR files:

·         xml-apis.jar and xercesImpl.jar

    These JAR files contain the xml parser classes complaint with JAXP 1.1 for XML parsing functionality. They can be extracted from xerces-J bin.2.5.0.zip, downloadable from http://xml.apache.org/dist/xerces-j

·         activation.jar

This JAR file contains the classes that make up JavaBeans Activation Framework, which is used for sending and receiving SOAP messages with attachments.  This JAR file can be extracted from the zip file downloadable from http://java.sun.com/products/javabeans/glasgow/jaf.html

Please all of these JAR files in the c:\axis-1_2RC2\lib directory.

Installing WS-Addressing Support for Apache Axis 1.2:

In addition to the standard libraries listed previously, you will need to download WS-Addressing support for Apache Axis.  This is currently contained within the Apache WS-FX project (http://ws.apache.org/ws-fx/addressing/).

Download the source for the latest RC2 compatible build from the CVS drop:  http://cvs.apache.org/viewcvs.cgi/ws-fx/addressing/.  This will reside in an /org subdirectory.  Create a new JAR called addressing.jar using the following command:

jar –cvf addressing.jar org/

Once complete, copy the addressing.jar file to the c:\axis-1_2RC2\lib directory.

Running the Sample Web Service:

The SOAP TCP sample that ships with the WSE 2.0 distribution shows a simple operation to perform stock quote lookups.  In the sample, the client issues a ticker symbol or stock code and the service returns the required details.  The operation is fairly simple; the client sends the stock code information to the running Web service.  On receipt of the information, the service returns the stock quote details (corresponding to the code) back to the client. 

This very same example will be used in this article.

Using Microsoft WSE to call an Apache Axis Web Service:

This sample shows bi-directional interoperability between Microsoft WSE and Apache Axis:

·         Microsoft WSE Client calls the Apache Axis Web Service

·         Apache Axis Client calls the Microsoft WSE Web Service

The following sections cover these two scenarios.

Building the Apache Axis Sample Web Service:

To build and run the Apache Axis Web Service, first navigate to the c:\axis.tcp\axis\service folder within a command prompt.  Run the build.bat file to compile the Java code under this directory.

After the sample successfully compiles, run the run.bat file.  This will run the service, which will start listening for incoming requests.

Listening for messages at soap.tcp://localhost:1234/axisport

With the service running, you can now build and run the .NET client.

Building the Microsoft WSE Client:

To build the client, run the build.bat file in the c:\axis.tcp\dotnet\client directory.  Run the StockClient.exe file.

The client calls the Apache Axis Web Service, and the following should be displayed:

Calling soap.tcp://localhost:1234/axisport

Symbol: FABRIKAM

Name: Fabrikam, Inc.

Last: 120

Change: 5.5%

Press [Enter] to continue…

By running this, the .NET client has invoked the Apache Axis Web Service using the SOAP TCP channel.  The client passes a stock code (FABRIKAM) and is returned the details for the stock. 

If you switch to the running Apache Axis console, you should see the following:

Request Received:

Symbol: FABRIKAM

Let’s now run the sample with Apache Axis acting as a client, calling the Microsoft WSE Web Service.

Building the Microsoft WSE Sample Web Service:

To build and run the Microsoft WSE sample Web Service, run the build.bat file from the c:\axis.tcp\dotnet\service directory.  When the service is waiting for incoming requests, the following will be displayed:

Press any key to exit when done…

Building the Apache Axis Client:

Now that the service is running, switch to the c:\axis.tcp\axis\client directory.  From here, run the build.bat batch file to compile the Java source.  Once complete, run the run.bat file to run.

The Java client will now call the Microsoft WSE Web Service and the following will be displayed:

Symbol: FABRIKAM

Name: Fabrikam, Inc.

Last: 120

PreviousChange: 5.5

Just like the previous example, the Apache Axis client has called the Microsoft WSE Web Service using a SOAP TCP channel.

How the Sample Works:

Running this sample demonstrates how SOAP messages can be exchanged over a TCP channel between a client and service, both in Apache Axis and using Microsoft WSE 2.0. You may have noticed that in this sample it was not required to setup or use a HTTP server, such as Microsoft IIS or Apache Tomcat.

To discover how this is possible, let’s first take a look at the Apache Axis service:

The Apache Axis Web Service:

The Apache Axis service (found in AxisService.java) works by first starting an instance of the ServerSocket to listen a connection to be made to this socket and accepts it.

srvSocket = new ServerSocket(port);

sock = srvSocket.accept();

The WSE 2.0 client sends the request as a SOAP message, framed with DIME, over TCP.  After receiving the stream of bytes using socket object, the Apache Axis service converts it to its original form using the MultiPartDimeInputStream class.  This makes the request SOAP message ready to be processed by the service.

BufferedInputStream str = new BufferedInputStream(s.getInputStream());

MultiPartDimeInputStream dime = new MultiPartDimeInputStream(str);

Message mes = new Message(dime);

The end-point reference of the client (to which the response message has to be sent) is then obtained from the SOAPEnvelope.

AddressingHeaders headers = new AddressingHeaders(mes.getSOAPEnvelope());

EndpointReference replyto = headers.getReplyTo();

String path = replyto.getAddress().getPath();

int port = replyto.getAddress().getPort();

With this done, the SOAP body has to be extracted from the message.  With this, the stock symbol will be extracted and sent to the service to be processed. The following shows how this is done.

// Extract the Soap Message Body.

SOAPElement msgbody = (SOAPElement)(mes.getSOAPBody().getChildElements().next());

// Iterate Soap Message Body to extract the Symbol sent by client.

while(iterator.hasNext()) {

SOAPElement soapelement = (SOAPElement) iterator.next();

SOAPElement soapelement1 = (SOAPElement) soapelement.getChildElements().next();

valueSent = soapelement1.getValue();

After receiving the Symbol sent by client, the Apache Axis Service is invoked (with the symbol as a parameter).  This obtains the details of the stock.

StockQuoteService service = new StockQuoteService();

StockQuote quote = service.getQuote(valueSent);

Once the information is obtained from the service, its time to create the Response message to be sent back to the client. Using the Apache Axis API, the following code is used:

SOAPEnvelope res = new SOAPEnvelope();

SOAPBody body = (SOAPBody)res.getBody();

SOAPFactory soapFactory = SOAPFactory.newInstance();                         

Name bodyName = soapFactory.createName("StockQuote","","http://stockservice.contoso.com/wse/samples/2003/06");

SOAPBodyElement bodyElement = body.addBodyElement(bodyName);

Name namesymbol = soapFactory.createName("Symbol");

SOAPElement symbol = bodyElement.addChildElement(namesymbol);

symbol.addTextNode(quote.getSymbol());

As the WSE 2.0 client requires messages to be framed with DIME, the above SOAP response has to be converted to the required format. The response is converted to the DIME Encapsulated format and returned to the client via a new socket.

// Set the addressing headers in Soap Response.

AddressingHeaders resHeader = new AddressingHeaders();

resHeader.setAction("urn:soap.tcp.test");

resHeader.setTo(new To("soap.tcp://" + host + ":" + String.valueOf(port) + path));

resHeader.toEnvelope(res);

// Convert the Soap response in DIME Encapsulated format.              

byte[] encodedEnvelope = res.toString().getBytes("UTF8");

DimeBodyPart resBody = new DimeBodyPart(encodedEnvelope, DimeTypeNameFormat.URI, "http://schemas.xmlsoap.org/soap/envelope/", UUIDGenFactory.getUUIDGen().nextUUID());

DimeMultiPart message = new DimeMultiPart();

message.addBodyPart(resBody);

// Send the response to .Net client using TCP Protocol.

Socket cli = new Socket(host, port);

OutputStream netout = cli.getOutputStream();                     

message.write(netout);

The Microsoft WSE Client:

To send messages to the Apache Axis Web Service, the WSE 2.0 client (StockClient.cs) sends and receives SOAP messages using WSE messaging classes – namely SoapSender and SoapReceiver.

An instance of SoapSender is used to send the SOAP request to the Axis Service.  This is done by creating an EndpointReference that specifies the host name and path of the message destination, passed as a parameter to the constructor for the SoapSender class. 

SoapSender s = new SoapSender(new Uri("soap.tcp://localhost:1234/axisport"));

A SoapEnvelope variable is then created and populated with the request message. As shown, the request message also includes the client’s end-point reference to which the response must be sent.  

EndpointReference responsePoint = new EndpointReference(new Uri("soap.tcp://localhost:5678/wse2port")) ;

SoapEnvelope req = new SoapEvnelope() ;

req.SetBodyObject( request );

req.Context.Addressing.Action = "urn:soap.tcp.test";

req.Context.Addressing.ReplyTo = new ReplyTo(responsePoint);

The request for the stock symbol is created:

String[] symbols = { "FABRIKAM" };

StockQuoteRequest request = new StockQuoteRequest();

request.Symbols = symbols;

Once the message is created and the SoapEnvelope populated, this is sent across to the service by calling the send method of the SoapSender class and passing this SoapEnvelope as a parameter.

s.Send(req);

With the request sent, the client now waits for a response from the service. 

SOAP messages are received using the SoapReceiver class. A SoapReceiver class needs to be registered in order to receive a message. To do this, a class inheriting from the SoapReceiver class is created. Any class that inherits from the SoapReceiver class has to override the Receive method. This method provides a notification when an incoming SOAP message is received. 

public class  ResponseReceiver : SoapReceiver

{

protected override void Receive( SoapEnvelope message )

{

     //process the incoming response message

}

}

In our sample, the receiver simply receives the message, extracts the contents from the message body and displays its contents. This is done in the Receive method:

XmlElement xmlElement = message.Body;

XmlNodeList xmlNodeList = xmlElement.GetElementsByTagName("StockQuote","http://stockservice.contoso.com/wse/samples/2003/06");                      

IEnumerator enumerator = xmlNodeList.GetEnumerator();

int index = 1;

while(enumerator.MoveNext())

{                            

   XmlNode xmlNode = (XmlNode) enumerator.Current;                           

   XmlNodeList xmlInnerNodeList = xmlNode.ChildNodes;

   IEnumerator enumerator1 = xmlInnerNodeList.GetEnumerator();

   while( enumerator1.MoveNext() && index < xmlInnerNodeList.Count)

   {                                                       

     XmlNode xmlNode1 = (XmlNode) xmlInnerNodeList.Item(index);

     Console.WriteLine(xmlNode1.Name+": "+xmlNode1.InnerText);                                 

     index+=2;                                       

   }                                     

}                            

Once the Receive method is defined, in the code for the application that is receiving the request, create an instance of the Receiver class created above and add it to the SoapReceivers collection. This registers the Receiver class for the TCP Protocol.

EndpointReference responsePoint = new EndpointReference(new Uri("soap.tcp://localhost:5678/wse2port")) ;

ResponseReceiver response = new ResponseReceiver() ;

SoapReceivers.Add(responsePoint,response);

The Microsoft WSE Web Service:

Previously we have shown how a Microsoft WSE client can call a Web Service on Apache Axis.  Let’s now see how the opposite can be achieved.

SOAP messages are received from Axis Client using the SoapReceiver class. A SoapReceiver class needs to be registered in order to receive a message.

In the service (Service.cs) a class inheriting from the SoapReceiver class is created. Any class that inherits from the SoapReceiver class has to override the Receive method. This method represents a Web service operation for receiving a SOAP message that includes a stock symbol.

public class  RequestReceiver: SoapReceiver

{

protected override void Receive( SoapEnvelope message )

{

     //process the incoming request message and send the response.

}

}

In our sample, the Receiver simply receives the message, extracts the symbol from the message body, performs operation on its contents and sends the soap response to the axis client. This is done in the Receive method and the code snippet which does this is as follows:

XmlElement xmlElement = message.Body;

XmlNodeList xmlNodeList = xmlElement.GetElementsByTagName("StockQuote","http://stockservice.contoso.com/wse/samples/2003/06");                      

IEnumerator enumerator = xmlNodeList.GetEnumerator();

int index = 1;

while(enumerator.MoveNext())

{                            

      XmlNode xmlNode = (XmlNode) enumerator.Current;            

      XmlNodeList xmlInnerNodeList = xmlNode.ChildNodes;

      IEnumerator enumerator1 = xmlInnerNodeList.GetEnumerator();

      while( enumerator1.MoveNext() && index < xmlInnerNodeList.Count)

      {                                                          

            XmlNode xmlNode1 = (XmlNode) xmlInnerNodeList.Item(index);

            symbol = (string) xmlNode1.InnerText;                                                      

            index+=2;                                      

      }                                        

}                      

The following code is used to simulate the lookup for the stock symbol.

ArrayList list = new ArrayList();

StockQuote quote = new StockQuote();

quote.Symbol = symbol;                         

if( symbol == "FABRIKAM" )

{

      quote.Name = "Fabrikam, Inc.";

      quote.Last = 120.00;

      quote.PreviousChange = 5.5;

} else {

      quote.Name = "Contoso Corp.";

      quote.Last = 50.07;

      quote.PreviousChange = 1.15;

}

list.Add( quote );           

After this is complete, a SOAP response is created and returned to the sender.

ArrayList list = new ArrayList();

// The end-point reference of the client (to which the response message // has to be sent) is obtained from the SOAPEnvelope.    

Uri replyTo = (Uri) message.Context.Addressing.From.Address.Value;                       

StockQuotes response = new StockQuotes();

response.Quotes = (StockQuote[])list.ToArray(typeof(StockQuote));      

SoapEnvelope responseMessage = new SoapEnvelope();               

responseMessage.SetBodyObject( response );     

responseMessage.Context.Addressing.Action = "urn:soap.tcp.test";                                                       

SoapSender s = new SoapSender(replyTo);

s.Send(responseMessage);

An instance of the Receiver class is created and added to the SoapReceivers collection. This registers the Receiver Class for the TCP Protocol.

EndpointReference responsePoint = new EndpointReference(new Uri("soap.tcp://localhost:5678/wse2port")) ;

ResponseReceiver response = new ResponseReceiver() ;

SoapReceivers.Add(responsePoint,response);

The Apache Axis Client:

To complete this sample, let’s finally take a look at the Apache Axis client (AxisClient.java) – and how it works to call the .NET Web Service.  The first task for the Apache Axis client is to create a Socket class to establish the connection with the WSE 2.0 Service using TCP.  Two arguments are passed in the constructor of Socket class, the host name and port number of the WSE 2.0 Service.

Socket cli = new Socket(host, port);

OutputStream netout = cli.getOutputStream();

The Apache Axis client then creates the SOAP Request and sends it to the WSE 2.0 Service. The following code snippet shows how this is done using the Apache Axis API:

SOAPEnvelope res = new SOAPEnvelope();

SOAPBody body = (SOAPBody)res.getBody();

SOAPFactory soapFactory = SOAPFactory.newInstance();             

Name bodyName = soapFactory.createName("StockQuote","", "http://stockservice.contoso.com/wse/samples/2003/06");

SOAPBodyElement bodyElement = body.addBodyElement(bodyName);

Name name1 = soapFactory.createName("Symbol");

SOAPElement symbol = bodyElement.addChildElement(name1);

symbol.addTextNode("FABRIKAM");

The WS-Addressing headers are set correctly (including the reply address for the client).  Since the WSE 2.0 Service requires messages to be framed using DIME, the above SOAP request is converted to the required format.

// Set the addressing headers in Soap Request.

AddressingHeaders resHeader = new AddressingHeaders();

resHeader.setAction("urn:soap.tcp.test");

resHeader.setTo(new To("soap.tcp://" + host + ":" + String.valueOf(port) + path));                               

resHeader.setFrom(new EndpointReference(new Address("soap.tcp://localhost:1234/")));     

resHeader.toEnvelope(res);

// Convert the Soap Request in DIME Encapsulated format.         

byte[] encodedEnvelope = res.toString().getBytes("UTF8");

DimeBodyPart resBody = new DimeBodyPart(encodedEnvelope, DimeTypeNameFormat.URI, "http://schemas.xmlsoap.org/soap/envelope/", UUIDGenFactory.getUUIDGen().nextUUID());

DimeMultiPart message = new DimeMultiPart();

message.addBodyPart(resBody);

The message is then sent to the service.

// Send the request to WSE 2.0 Service using socket object and TCP Protocol. 

message.write(netout);

OutputStream netout = cli.getOutputStream();

In order to receive the response sent by the Service, the Apache Axis client creates an instance of ServerSocket to listen a connection to be made.

srvSocket = new ServerSocket(port);

sock = srvSocket.accept();

The WSE 2.0 service sends the response as a DIME Encapsulated SOAP message over TCP. After receiving the stream of bytes using socket object, it is converted to its original form using the MultiPartDimeInputStream class and is set to an instance of the Message class.

BufferedInputStream str = new BufferedInputStream(s.getInputStream());

MultiPartDimeInputStream dime = new MultiPartDimeInputStream(str);

Message mes = new Message(dime);

With this done, stock quote details are extracted from the SOAP Response. And displayed in the console.

SOAPElement msgbody = (SOAPElement)(mes.getSOAPBody().getChildElements().next());

Iterator iterator = msgbody.getChildElements();

while(iterator.hasNext())

{

      SOAPElement sub_ito = (SOAPElement) iterator.next();

      Iterator iterator1 = sub_ito.getChildElements();

      while(iterator1.hasNext())

      {

            SOAPElement sub_ito1 = (SOAPElement) iterator1.next();

       System.out.println((sub_ito1.getNodeName()).replace(':','\b')+" : "+sub_ito1.getValue());

      }

}    

Conclusion

This sample has shown how Microsoft WSE 2.0 and Apache Axis 1.2 can interoperate using the sample SOAP TCP channel.  Although unsupported, this shows an interesting way of achieving low level interoperability between the two platforms.  This article would not have been possible without the help of Sarvashrestha Paliwal, Hrudaya Sruti, Parmod Pawar, Anurag Katre of Tata Consulting Services and Fumiaki Yoshimatsu (a Microsoft MVP, based in Japan).