SUMMARY
This step-by-step article describes how to integrate an Apache SOAP 2.2 client with an XML Web Service that is based on Active Server Pages (ASP) .NET. This article assumes that the ASP .NET server-side component exposes the Web method with the
WebMethod attribute and does nothing more than provide a namespace for the Web Service itself.
back to the top
Requirements
The following items describe the recommended hardware, software, network infrastructure, skills and knowledge, and service packs that you need:
- Java JDK version 1.3
- Apache SOAP version 2.2
This article assumes that you are familiar with the following topics:
- Apache SOAP 2.2 libraries setup
- Java language and JavaBean technology
- Web Service Description Language (WSDL) files
- Simple API for XML (SAX) XML parsing model
For more information about how to set up the Apache SOAP 2.2 libraries, refer to the downloads for these products, as well as the
REFERENCES at the end of this article.
When you use complex types in the SOAP interfaces, it is helpful to understand JavaBean technology as well. In addition, you need some familiarity with Web Service Description Language (WSDL) files when you create the various Java classes.
back to the top
The .NET XML Web Service
This article assumes that your .NET Web Service resembles the following:
Microsoft Visual C# .NET:
public class SimpleService : System.Web.Services.WebService
{
public SimpleService() {
}
[WebMethod]
public string echoString(string inputString) {
if ( inputString == null ) {
return "Input string is null";
}
return inputString;
}
}
Microsoft Visual Basic .NET:
Public Class SimpleService
Inherits System.Web.Services.WebService
Public Sub New()
MyBase.New()
End Sub
<WebMethod()> Public Function echoString( _
ByVal inputString As String) As String
echoString = inputString
End Function
End Class
These declarations tell the .NET runtime to not expect any encoding information and to interpret the messages as document or literal types. That is, the element names mean something and the server is responsible for knowing what data types are passed in.
back to the top
The Apache SOAP Toolkit
The Apache SOAP Toolkit is designed as a remote procedure call (RPC) mechanism, not a document exchange mechanism. It expects the message exchange mechanism to be RPC encoded. As a result, you must follow these steps so that everything works correctly:
- Write a Java proxy for the .NET endpoint.
- Create a class that generates the SOAP message Body element.
- Create a class that parses the SOAP response.
back to the top
The Java Proxy
The IBM Web Services Toolkit can produce proxies; however, due to the amount of work you need to do after the proxy is generated, it is usually easier to write the proxy by hand. Beyond that, you need to write functions that mimic the signatures in the WSDL of the operations that are associated with the
portType to which you are connecting. For example, the
echoString function signature resembles the following:
public synchronized String echoString( String inputString )
throws SOAPException
This allows users of the proxy to instantiate the proxy and call functions, while only worrying about handling SOAP faults. The proxy performs the following steps:
- Verifies that the URL has been set.
- Prepares the message.
- Sends the message.
- Parses the response.
Because of the way in which the Apache SOAP 2.2 classes encode messages, you must override the piece that builds the body as well as the pieces that interpret the response. You must change the way in which the response is handled because the changes to Apache go outside its original RPC design. The full code to call
echoString resembles the following:
public synchronized String echoString( String inputString )
throws SOAPException {
String retval = "";
if (url == null) {
throw new SOAPException(Constants.FAULT_CODE_CLIENT,
"A URL must be specified via " +
"SoapBuildersExSoapProxy.setEndPoint(URL).");
}
// Instantiate the message and the envelope.
// The message sends the envelope and gets
// the response.
Message message = new Message();
Envelope env = new Envelope();
DataHandler soapMsg = null;
// Get this from the soapAction attribute on the
// soap:operation element that is found within the SOAP
// binding information in the WSDL.
String SOAPActionURI = "http://tempuri.org/echoString";
MessageBody theBody = new MessageBody();
// Set the argument.
theBody.echoString = inputString;
// Replace the default body with our own.
env.setBody( theBody );
message.send( getEndPoint(), SOAPActionURI, env );
try{
// Because the Body.unmarshall handler is static,
// you cannot replace the basic machinery easily.
// Instead, you must obtain and parse the
// message on your own.
soapMsg = message.receive();
XMLReader xr = XMLReaderFactory.createXMLReader(
"org.apache.xerces.parsers.SAXParser");
ClientHandler ch = new ClientHandler();
ch.setElementToSearchFor("echoStringResult");
// Set the ContentHandler.
xr.setContentHandler( ch );
// Parse the file.
xr.parse( new InputSource(
new StringReader( soapMsg.getContent().toString() ) ) );
// At this point, the result has been parsed and stored
// within the ClientHandler instance.
retval = ch.getResult();
} catch ( Exception e ) {
// You need to do something with the exception.
// Here, we print out the exception to the console.
System.out.println( "***Exception***: " + e.toString() );
}
return retval;
}
This basic form works regardless of whether the actual argument is a simple type such as a string or a complex type such as an array. Creating the
Body class is more difficult. You must do the serialization by hand.
back to the top
Override the Body Serialization
Apache SOAP uses a class called
Body to serialize and deserialize SOAP messages. It does so with the
marshall and
unmarshall methods. The
marshall method is an instance method and the
unmarshall method is a static class method. Because of the structure of the Apache SOAP library and the way in which you are using it, you cannot inherit from
Body and expect your own version of
unmarshall to be called. Fortunately, you can replace
marshall and change how you serialize the SOAP
Body element.
To correctly serialize the
echoString method, provide your own extended version of
Body. Depending on how many methods you have, you can either create one version of a class that extends
Body per method, or you can have the
marshall method choose the correct code based on other information.
Assuming that you only serialize one method call in the derived class, the class must include the following:
- A way to set the data that is being serialized.
- Knowledge of how to write the XML so that it is formatted correctly for the ASP .NET endpoint.
Because the ASP .NET endpoint uses document/literal encoding, you only need to write out the following information:
- Body element.
- Method name and corresponding namespace.
- Arguments that are passed into the method.
For the
echoString example, the class resembles the following:
import java.io.*;
import org.apache.soap.util.*;
import org.apache.soap.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.rpc.SOAPContext;
public class MessageBody extends Body
{
public String echoString;
public void marshall(String inScopeEncStyle,
Writer sink,
NSStack nsStack,
XMLJavaMappingRegistry xjmr,
SOAPContext ctx)
throws IllegalArgumentException, IOException
{
// Set the Body element
String soapEnvNSPrefix = "SOAP-ENV";
sink.write('<' + soapEnvNSPrefix + ':'
+ Constants.ELEM_BODY + '>' + StringUtils.lineSeparator);
// Write out the method name and related argument(s)
sink.write("<echoString xmlns=\"http://tempuri.org/\">" +
"<inputString>" + echoString +
"</inputString></echoString>" );
// Close the SOAP Body
sink.write("</" + soapEnvNSPrefix + ':' +
Constants.ELEM_BODY + '>' + StringUtils.lineSeparator);
nsStack.popScope();
}
}
Now that you can send the message, you need to be able to read the response. To do this, you need a class that is called by the SAX parser.
back to the top
Parse the Response
When the message goes out and comes back, you can determine the entire SOAP response. This part is used within the code in the
echoStringtry/catch block for the example proxy. This example
ClientHandler class attempts to be a general purpose class that can obtain any single element response. Users of the class should be able to use the class as-is. If the value is a Boolean, date, or numeric type, you can do the conversion after you retrieve the result. More complex types need more complex implementations.
To retrieve simple values, you must do the following:
import org.xml.sax.helpers.*;
import org.xml.sax.*;
public class ClientHandler extends DefaultHandler {
private String result = "";
private String elementToSearchFor = "";
private boolean foundResult = false;
public ClientHandler() {
}
public String getResult(){
return result;
}
public void setElementToSearchFor( String elemName ) {
elementToSearchFor = elemName;
}
public String getElementToSearchFor() {
return elementToSearchFor;
}
// Override methods of the DefaultHandler class
// to gain notification of SAX events.
//
// See org.xml.sax.ContentHandler for all available events.
//
public void startElement( String namespaceURI,
String localName,
String qName,
Attributes attr ) throws SAXException {
if ( foundResult == false ) {
foundResult = (localName.compareTo(
elementToSearchFor ) == 0);
}
}
public void characters( char[] ch, int start, int length )
throws SAXException {
if ( foundResult ) {
// Read all the data in
result = String.valueOf( ch, start, length );
foundResult = false;
}
}
}
In the above class, you call
getResult to obtain the single element result. Modify the code as necessary for complex types and arrays.
back to the top
Troubleshooting
Overriding the body serialization can be time-consuming and error-prone. Test your proxy to ensure that it can handle whatever the ASP .NET XML Web Service returns to you.
back to the top