SOAPBuilders Interoperability

If you are depressed and skeptical by now, read on. A lot of great work is being developed that is producing positive results.

The SOAPBuilders group was created to promote interoperability as the SOAP specification matures. The SOAPBuilders discussion group provides a forum for SOAP implementers to discuss issues related to interoperability and the various specifications. It has existed since January 2001. The group has a community of over 750 members and is highly active.

Through SOAPBuilders, two labs for interoperability testing against SOAP 1.1 have been developed. The test labs provide test suites that can be used by SOAP implementations to test interoperability. The SOAPBuilders test suite was initially based on tests created to improve interoperability between Apache SOAP and SOAP::Lite. The tests are a collection of SOAP-RPC echo invocations. WSDL documents and browser-based clients for using the service are provided for all test suites. A directory of endpoints for each implementation is maintained on the lab site.

XMethods (http://www.xmethods.net/ilab/) maintained Round 1, conducted in June 2001. The SOAPBuilders interoperability activity is now in Round 2, maintained by Bob Cunnings of White Mesa (http://www.whitemesa.com/interop.htm). For each round, XMethods and White Mesa provide lists of implementations and WSDL documents that have been tested and document the test results. Based on this information, you can determine the state of interoperability for a specific implementation.

Most implementations listed for Round 1 and many listed for initial testing during Round 2 have released updates to correct identified interoperability issues. Many listed Round 1 services are no longer available for testing. The Round 2 tests are better maintained, but don’t support Apache SOAP 2.2 clients because that tool generates an older schema reference in the SOAP envelope. We will show how to fix this problem later in this chapter; Axis (the next generation of Apache SOAP) does not have this problem.

The Round 1 Interoperability Lab included specifications for a Service test suite and a Service via SOAP Intermediary test suite.

Round 2

The Standard Round 2 Interoperability Lab has been expanded to include specifications for a Base test suite, a Group B test suite, an echo Header or Group C test suite, a Service via SOAP Intermediary test suite (which duplicates the methods from the Base suite), Digest Authentication Implementation, Web Services Routing Protocol (for intermediary nodes as well), and Document/Literal SOAP Operations.

The Base test suite includes methods to test types (string, string array, integer, integer array, float, float array, struct, and struct array). Each method accepts the specific type and echoes it back to the client. Explicit typing information can be carried on the wire, but should not be required on incoming messages. For example, here is the echoString method client request (with sample envelopes). It accepts a string as an argument:

POST /interop HTTP/1.0
Host: www.whitemesa.net
User-Agent: White Mesa SOAP Interop Client/1.0
Content-Type: text/xml; charset="utf-8"
Content-Length: 502
SOAPAction: "http://soapinterop.org/"
  
<?xml version="1.0" encoding="UTF-8"?>
  
<SOAP-ENV:Envelope 
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"  
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <SOAP-ENV:Body>
    <m:echoString xmlns:m="http://soapinterop.org/">
      <inputString>hello world</inputString>
    </m:echoString>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Here is a successful server response; as you’d expect, it returns the same string:

HTTP/1.0 200 OK
Date: Wed, 20 Jun 2001 02:44:16 GMT
Server: WhiteMesa SOAP Server/2.3
Content-Type: text/xml; charset="utf-8"
Content-Length: 508
  
<?xml version="1.0" encoding="UTF-8"?>
  
<SOAP-ENV:Envelope 
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <SOAP-ENV:Body>
    <m:echoStringResponse xmlns:m="http://soapinterop.org/">
      <return>hello world</return>
    </m:echoStringResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Similar echoing methods are provided for:

echoVoid

Accepts no arguments and returns no arguments

echoBase64

Accepts a binary object and echoes it back

echoHexBinary

Accepts a hex-encoded object and echoes it back

echoDate

Accepts a Date/Time and echoes it back

echoDecimal

Accepts a decimal and echoes it back

echoBoolean

Accepts a Boolean and echoes it back

The Group B test suite includes methods with more complex serialization requirements. It includes these echo methods:

echoStructAsSimpleTypes

Accepts a single struct and echoes it (decomposed into three output parameters)

echoSimpleTypesAsStruct

Accepts three input parameters and echoes them integrated into a single struct

echo2DStringArray

Accepts a single two-dimensional array of type xsd:string and echoes it

echoNestedStruct

Accepts a single struct with a nested struct type member and echoes it

echoNestedArray

Accepts a single struct with a nested array type member and echoes it back

The echoHeader or Group C test suite offers standard headers used to test header processing. The SOAP body contains an echoVoid method call. The message recipient is the default actor, and the mustUnderstand element value can be either 0 or 1 to test conformance to the SOAP 1.1 specification. The following header entries are provided:

echoMeStringRequest

Contains a string that is echoed in a corresponding response header entry (echoMeStringResponse)

echoMeStructRequest

Contains a single, echoed struct (echoMeStructResponse)

Unknown

Any header that is not understood by the server. If a server cannot process the header and mustUnderstand is set to true (1), the server must issue a SOAP Fault if it is the target of the header entry, based on the value of the actor attribute. If the target is another server, the server can ignore the header entry. If a server cannot process the header, and mustUnderstand is set to false (0), the server can ignore the header entry and is not required to fault (regardless of whether it is the targeted server).

Polymorphic type methods are also available. They include: echoPolyMorph, echoPolyMorphArray, and echoPolyMorphStruct.

Round 3

Round 3 of interoperability testing, scheduled for the end of February 2002, is co-hosted by IONA and Microsoft and will focus on WSDL interoperability. A formal plan is not yet finalized, but proposed testing includes the following success criteria:

  • Tools can generate WSDL documents correctly for designated scenarios and consume WSDLs generated by other tools.

  • Tools can consume and reuse WSDL documents.

Discussion topics that have been most active on the SOAPBuilders list will be included in the tests:

  • Import (schema import)

  • Document/literal services with multiple schema

  • SOAP binding interoperability issues

Understanding the Echo Test

Let’s examine the EchoTestClient that comes with Apache SOAP 2.2. We picked Apache SOAP because we already explained its underpinnings in earlier chapters and wanted to focus on the test itself. The workings of the test, the interoperability issues, and the concepts of encoding and serialization are generic enough to apply to any SOAP infrastructure you may use. The EchoTestClient represents a Java implementation of the SOAPBuilders Interoperability Labs “Round 1” test and can also be used for the “Round 2: Base” interoperability tests. It can be found in the soap-2.2samplesinterop directory, or as part of the examples available on this book’s web site (http://www.oreilly.com/catalog/javawebserv).

We also verified some of the test results using the Apache Axis version of the same test and used other third-party visual interfaces. These interfaces will be shown later.

Running the EchoTestClient

To see how these tests work, we need an Internet connection. Let’s run the Apache SOAP 2.2 EchoTestClient against the Iona XMLBus echo test service, which Iona has hosted as part of their participation in the SB Round 2 effort:

java samples.interop.EchoTestClient http://interop.xmlbus.com:7002/xmlbus/container/
InteropTest/BaseService/BasePort

The command must be typed on a single line. If you use the EchoTestClient straight from the Apache SOAP distribution, you should see the following errors as output:

echoInteger generated fault:
  Fault Code   = SOAP-ENV:Server
  Fault String = xsi:type doesn't match.  Expected http://www.w3.org/2001/
XMLSchema:int but found http://www.w3.org/1999/XMLSchema:int
echoFloat generated fault:
  Fault Code   = SOAP-ENV:Server
  Fault String = xsi:type doesn't match.  Expected http://www.w3.org/2001/
XMLSchema:float but found http://www.w3.org/1999/XMLSchema:float
soapAction: http://soapinterop.org/
echoString generated fault:
  Fault Code   = SOAP-ENV:Server
  Fault String = xsi:type doesn't match.  Expected http://www.w3.org/2001/
XMLSchema:string but found http://www.w3.org/1999/XMLSchema:string
soapAction: http://soapinterop.org/
...

This result illustrates one of the most basic interoperability problems. The receiving service expected the schema definition http://www.w3.org/2001/XMLSchema, but the sending client generated a SOAP envelope based on http://www.w3.org/1999/XMLSchema. We will show how simple it is to fix this problem in a moment, when we get to the code. In the meantime, when the EchoTestClient works correctly, its output looks like this:

echoInteger      OK
echoFloat        OK
echoString       OK
echoStruct       OK
echoIntegerArray OK
echoFloatArray   OK
echoStringArray  OK
echoStructArray  OK

This test may look simple, but quite a bit is going on internally. At the highest level, this test makes eight RPC calls,[15] each with its own unique datatype as a parameter. The receiving method simply takes that parameter and returns it as the method’s return value. The sending client then compares the returned data with the sent data to verify that it is the same. If the data are equal, we know that:

  • The sending client successfully packaged the method invocation and its parameters and correctly marshalled it onto the wire as a valid SOAP request.

  • The receiving SOAP processor received the request, identified that there was enough correct information in the SOAP request, and dispatched it to the appropriate service method.

  • The receiving SOAP processor also (optionally) validated the request against the WSDL that described the service.

  • The service method was invoked with the correct data and returned the data intended for the caller as the return value.

  • The server implementation correctly marshaled the return values onto the wire as a response envelope.

  • The sending client’s runtime infrastructure successfully unmarshaled the SOAP response envelope into the appropriate Java object.

  • The sent and returned data are equivalent.

The next exercise directs the EchoTestClient at our simple servlet from Chapter 3:

java samples.interop.EchoTestClient http://localhost:8080/examples/servlet/
SimpleHTTPReceive

You will see several errors in the sending client’s console because the simple servlet is not set up to accommodate RPC calls—but that’s not important for this discussion. What’s important is that you should see the following output in the Tomcat console window. The raw SOAP envelopes that were generated by the EchoTestClient are shown here:

____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 469
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoInteger xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputInteger xsi:type="xsd:int">5</inputInteger>
</ns1:echoInteger>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 466
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoFloat xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputFloat xsi:type="xsd:float">55.5</inputFloat>
</ns1:echoFloat>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 476
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoString xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputString xsi:type="xsd:string">Hi there!</inputString>
</ns1:echoString>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 656
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoStruct xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputStruct xmlns:ns2="http://soapinterop.org/xsd" xsi:type="ns2:SOAPStruct">
<varInt xsi:type="xsd:int">5</varInt>
<varFloat xsi:type="xsd:float">10.0</varFloat>
<varString xsi:type="xsd:string">Hola, baby</varString>
</inputStruct>
</ns1:echoStruct>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 748
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoIntegerArray xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputIntegerArray xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" 
xsi:type="ns2:Array" ns2:arrayType="xsd:int[5]">
<item xsi:type="xsd:int">5</item>
<item xsi:type="xsd:int">4</item>
<item xsi:type="xsd:int">3</item>
<item xsi:type="xsd:int">2</item>
<item xsi:type="xsd:int">1</item>
</inputIntegerArray>
</ns1:echoIntegerArray>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 762
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoFloatArray xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputFloatArray xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" 
xsi:type="ns2:Array" ns2:arrayType="xsd:float[5]">
<item xsi:type="xsd:float">5.5</item>
<item xsi:type="xsd:float">4.4</item>
<item xsi:type="xsd:float">3.3</item>
<item xsi:type="xsd:float">2.2</item>
<item xsi:type="xsd:float">1.1</item>
</inputFloatArray>
</ns1:echoFloatArray>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 801
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoStringArray xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputStringArray xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" 
xsi:type="ns2:Array" ns2:arrayType="xsd:string[5]">
<item xsi:type="xsd:string">First</item>
<item xsi:type="xsd:string">Second</item>
<item xsi:type="xsd:string">Fifth (just kidding :))</item>
<item xsi:type="xsd:string">Fourth</item>
<item xsi:type="xsd:string">Last</item>
</inputStringArray>
</ns1:echoStringArray>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________
____________________________
Received request.
-----------------------
  SOAPAction = "http://soapinterop.org/"
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 1527
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns1:echoStructArray xmlns:ns1="http://soapinterop.org/" 
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<inputStructArray xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" 
xsi:type="ns2:Array" xmlns:ns3="http://soapinterop.org/xsd" 
ns2:arrayType="ns3:SOAPStruct[5]"><item xsi:type="ns3:SOAPStruct">
<varInt xsi:type="xsd:int">5</varInt>
<varFloat xsi:type="xsd:float">5.55555</varFloat>
<varString xsi:type="xsd:string">cinqo</varString>
</item>
<item xsi:type="ns3:SOAPStruct">
<varInt xsi:type="xsd:int">4</varInt>
<varFloat xsi:type="xsd:float">4.4444</varFloat>
<varString xsi:type="xsd:string">quattro</varString>
</item>
<item xsi:type="ns3:SOAPStruct">
<varInt xsi:type="xsd:int">3</varInt>
<varFloat xsi:type="xsd:float">3.333</varFloat>
<varString xsi:type="xsd:string">tres</varString>
</item>
<item xsi:type="ns3:SOAPStruct">
<varInt xsi:type="xsd:int">2</varInt>
<varFloat xsi:type="xsd:float">2.22</varFloat>
<varString xsi:type="xsd:string">duet</varString>
</item>
<item xsi:type="ns3:SOAPStruct">
<varInt xsi:type="xsd:int">1</varInt>
<varFloat xsi:type="xsd:float">1.1</varFloat>
<varString xsi:type="xsd:string">un</varString>
</item>
</inputStructArray>
</ns1:echoStructArray>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________

We will examine some of this output in more detail as we walk through the example. But first, here is the Apache SOAP 2.2 EchoTestClient in its entirety:

package samples.interop;
  
import java.util.Vector;
import org.apache.soap.*;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.*;
import org.apache.soap.rpc.*;
import org.apache.soap.messaging.*;
import java.net.URL;
import org.apache.soap.util.xml.*;
import java.io.*;
import org.w3c.dom.*;
import org.apache.soap.util.*;
import java.lang.reflect.*;
  
/** A quick-and-dirty client for the Interop echo test services as defined
 * at http://www.xmethods.net/ilab.
 * 
 * Defaults to the Apache endpoint, but you can point it somewhere else via
 * the command line:
 * 
 *    EchoTestClient http://some.other.place/
 * 
 * DOES NOT SUPPORT DIFFERENT SOAPACTION URIS YET.
 * 
 * @author Glen Daniels ([email protected])
 */
public class EchoTestClient
{
  SOAPMappingRegistry smr = 
    new SOAPMappingRegistry(Constants.NS_URI_CURRENT_SCHEMA_XSD);
  
//  public static final String DEFAULT_URL = 
//    "http://nagoya.apache.org:5089/soap/servlet/rpcrouter";
  public static final String DEFAULT_URL = 
    "http://localhost:8080/soap/servlet/rpcrouter";
// pick one!  First line works for round 1; second for round 2: base
public static final String ACTION_URI = "urn:soapinterop";
// public static final String ACTION_URI = "http://soapinterop.org/";
  
  public static final String OBJECT_URI = "http://soapinterop.org/xsd";
  public Header header = null;
  
  public static void main(String args[])
  {
    URL url = null;
  
    try {
      if (args.length > 0) {
        url = new URL(args[0]);
      } else {
        url = new URL(DEFAULT_URL);
      }
    } catch (Exception e) {
      e.printStackTrace(  );
    }
    
    EchoTestClient eTest = new EchoTestClient(  );
    eTest.doWork(url);
  }
  
  private static boolean equals(Object obj1, Object obj2) {
    if (obj1 == null) return (obj2 == null);
    if (obj1.equals(obj2)) return true;
    if (!obj2.getClass().isArray(  )) return false;
    if (!obj1.getClass().isArray(  )) return false;
    if (Array.getLength(obj1) != Array.getLength(obj2)) return false;
    for (int i=0; i<Array.getLength(obj1); i++)
      if (!equals(Array.get(obj1,i),Array.get(obj2,i))) return false;
    return true;
  }
  
  public void doWork(URL url)
  {
    IntDeserializer intDser = new IntDeserializer(  );
    FloatDeserializer floatDser = new FloatDeserializer(  );
    StringDeserializer stringDser = new StringDeserializer(  );
    ArraySerializer arraySer = new ArraySerializer(  );
    DataSerializer dataSer = new DataSerializer(  );
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName(OBJECT_URI, "SOAPStruct"), Data.class, dataSer, dataSer);
  
    Integer i = new Integer(5);
    Parameter p = new Parameter("inputInteger", Integer.class, i, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, intDser);
    doCall(url, "echoInteger", p);
    
    p = new Parameter("inputFloat", Float.class, new Float(55.5), null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, floatDser);
    doCall(url, "echoFloat", p);
    
    p = new Parameter("inputString", String.class, "Hi there!", null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, stringDser);
    doCall(url, "echoString", p);
    
    p = new Parameter("inputStruct", Data.class, 
        new Data(5, "Hola, baby", (float)10.0), null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, dataSer);
    doCall(url, "echoStruct", p);
    
    p = new Parameter("inputIntegerArray", Integer[].class, new Integer[]{
                      new Integer(5),
                      new Integer(4),
                      new Integer(3),
                      new Integer(2),
                      new Integer(1)}, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, arraySer);
    doCall(url, "echoIntegerArray", p);        
  
    p = new Parameter("inputFloatArray", Float[].class, new Float[]{
                      new Float(5.5),
                      new Float(4.4),
                      new Float(3.3),
                      new Float(2.2),
                      new Float(1.1)}, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, arraySer);
    doCall(url, "echoFloatArray", p);        
  
    p = new Parameter("inputStringArray", String[].class, new String[]{
                      "First",
                      "Second",
                      "Fifth (just kidding :))",
                      "Fourth",
                      "Last"}, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, arraySer);
    doCall(url, "echoStringArray", p);        
  
    p = new Parameter("inputStructArray", Data[].class, new Data[]{
                      new Data(5, "cinqo", new Float("5.55555").floatValue(  )),
                      new Data(4, "quattro", (float)4.4444),
                      new Data(3, "tres", (float)3.333),
                      new Data(2, "duet", (float)2.22),
                      new Data(1, "un", (float)1.1)}, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, arraySer);
    doCall(url, "echoStructArray", p);        
  }
  
  public void doCall(URL url, String methodName, Parameter param)
  {
    try {
      Call call = new Call(  );
      Vector params = new Vector(  );
      params.addElement(param);
      call.setSOAPMappingRegistry(smr);
      call.setTargetObjectURI(ACTION_URI);
      call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
      call.setMethodName(methodName);
      call.setParams(params);
      if (header != null)
        call.setHeader(header);
      
      String soapAction = ACTION_URI;
//      System.out.println("soapAction: " + soapAction);
/*      if (true) {
        soapAction = soapAction + methodName;
      }
*/      
      Response resp = call.invoke(url, soapAction);
      
      // check response 
      if (!resp.generatedFault(  )) {
        Parameter ret = resp.getReturnValue(  );
        Object output = ret.getValue(  );
        Object input = param.getValue(  );
  
        if (equals(input,output)) {
          System.out.println(methodName + "	 OK");
        } else {
          System.out.println(methodName + "	 FAIL: " + output);
        }
      }
      else {
        Fault fault = resp.getFault (  );
        System.err.println (methodName + " generated fault: ");
        System.out.println ("  Fault Code   = " + fault.getFaultCode(  ));  
        System.out.println ("  Fault String = " + fault.getFaultString(  ));
      }
      
    } catch (Exception e) {
      e.printStackTrace(  );
    }
  }
}

Getting it to work

Let’s examine some of this code in detail. First, here is what we did to get around the mismatch of the schema namespace URIs. The value passed into the mapping registry is based on a default, which was initialized to use the older value:

public class EchoTestClient
{
  SOAPMappingRegistry smr = 
    new SOAPMappingRegistry(Constants.NS_URI_CURRENT_SCHEMA_XSD);

Apache SOAP 2.2 supports the 2001 schema—it just does not default to it. We could have simply changed this line of code, but changing it doesn’t fix the problem everywhere. Instead, we got the Constants.java file from the Apache SOAP distribution; updated Constants.NS_URI_CURRENT_SCHEMA_XSD to default to the desired value; and recompiled the Constants, SoapEncUtils, and ArraySerializer classes:

/*  Changed this:
public static final String NS_URI_CURRENT_SCHEMA_XSI =
    NS_URI_1999_SCHEMA_XSI;
  public static final String NS_URI_CURRENT_SCHEMA_XSD =
    NS_URI_1999_SCHEMA_XSD;
*/
// To this:
  public static final String NS_URI_CURRENT_SCHEMA_XSI =
    NS_URI_2001_SCHEMA_XSI;
  public static final String NS_URI_CURRENT_SCHEMA_XSD =
    NS_URI_2001_SCHEMA_XSD;

We also commented out the following lines in the doWork( ) method of the test:

      String soapAction = ACTION_URI;
/*      if (true) {
        soapAction = soapAction + methodName;
      }
*/      
      Response resp = call.invoke(url, soapAction);

Almost every test we tried did not like having the method name appended to the soapAction header. It’s good that the author of the code was aware of this problem and isolated it so it could be turned on and off easily. Also, depending on what round of tests we wanted to run, we had to change the action URI; the Round 1 tests expected the URI urn:soapinterop, while the current Round 2: base tests expect http://soapinterop.org/:

// pick one!  First line works for round 1; second for round 2: base
public static final String ACTION_URI = "urn:soapinterop";
// public static final String ACTION_URI = "http://soapinterop.org/";

The Apache Axis version of the same test didn’t need any modification because it is a new test that presumably works only against the Round 2 tests.

Default serialization of data

The mapping registry maintains the relationship between the XML datatypes and their corresponding Java types. It is also a utility that marshals and unmarshals the data between the Java representation and the SOAP envelope representation. It accomplishes this task by maintaining a list of serialization and deserialization classes for each datatype it knows about.

By default, the SOAPMappingRegistry is instantiated with the appropriate mappings and serializers/deserializers for each type that it supports.

The default serialization mapping for the String class creates the XML rendition. The SOAP envelope resulting from this call looks like:

<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SOAP-ENV:Body>
    <ns1:echoString 
        xmlns:ns1="urn:soapinterop" 
        SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <inputString 
          xmlns:ns2="http://www.w3.org/2001/XMLSchema"
           xsi:type="ns2:string">
                          Hi there!
      </inputString>
    </ns1:echoString>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Note the inclusion of the xsi:type attribute. Not all SOAP toolkits that participated in the Round 1 testing supported this attribute.

These default mappings may be overridden by calling the mapTypes( ) method. The mapTypes( ) method takes the following parameters:

  • A namespace for the schema

  • A QName for the element that represents the parameter/return value[16]

  • An instance of a class that describes the data

  • An instance of an object that performs the serialization of the Java object into the XML representation

  • An instance of an object that deserializes the XML data back into the Java object

For example, the following objects are part of Apache SOAP’s serialization framework and are included in the org.apache.soap.encoding.soapenc package:

    IntDeserializer intDser = new IntDeserializer(  );
    FloatDeserializer floatDser = new FloatDeserializer(  );
    StringDeserializer stringDser = new StringDeserializer(  );
    ArraySerializer arraySer = new ArraySerializer(  );

In the EchoTestClient, the call to mapTypes( ) is necessary for the “Return” because the SOAP Toolkit that services the request may not support the placement of the xsi:type attribute in the response envelope. The type information for the parameter is specified along with its name and value when the parameter object is constructed:

    p = new Parameter("inputString", String.class, "Hi there!", null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, stringDser);
    doCall(url, "echoString", p);

Default mappings are available for more complicated objects, such as arrays. The following code constructs a method invocation with a parameter that is an array of 5 integers, with the values of 1 through 5:

    p = new Parameter("inputIntegerArray", Integer[].class, new Integer[]{
                      new Integer(5),
                      new Integer(4),
                      new Integer(3),
                      new Integer(2),
                      new Integer(1)}, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName("", "Return"), null, null, arraySer);
    doCall(url, "echoIntegerArray", p);

The serialization framework knows how to traverse the array, using the appropriate serialization classes for each member element (in this case, all of type Integer). The serialized output looks like:

<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope 
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SOAP-ENV:Body>
    <ns1:echoIntegerArray 
        xmlns:ns1="urn:soapinterop" 
        SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <inputIntegerArray 
          xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" 
          xsi:type="ns2:Array" 
          xmlns:ns3="http://www.w3.org/2001/XMLSchema" 
          ns2:arrayType="ns3:int[5]">
        <item xsi:type="ns3:int">5</item>
        <item xsi:type="ns3:int">4</item>
        <item xsi:type="ns3:int">3</item>
        <item xsi:type="ns3:int">2</item>
        <item xsi:type="ns3:int">1</item>
      </inputIntegerArray>
    </ns1:echoIntegerArray>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Custom serialization

Part of the test involves sending a complex data structure represented as a SOAP struct.[17] The structure contains three data items: an integer, a string, and a float. Creating a custom data serialization requires the following steps:

  1. Create a Java object to describe the data.

  2. Create a custom class to perform the serialization/deserialization.

  3. Plug them into the mapping registry.

In the following code, DataSerializer is a custom class that we will explain shortly. The custom object, the serializer, and the deserializer are plugged into the mapping registry with the call to mapTypes( ):

    DataSerializer dataSer = new DataSerializer(  );
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
        new QName(OBJECT_URI, "SOAPStruct"), Data.class, dataSer, dataSer);

As shown in the following listing, the setup for call( ) is similar to the setup that was used for the “built-in” types:

    p = new Parameter("inputStruct", Data.class, 
       new Data(5, "Hola, baby", (float)10.0), null);
    doCall(url, "echoStruct", p);

Here’s the SOAP envelope generated by the call:

<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope 
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SOAP-ENV:Body>
    <ns1:echoStruct xmlns:ns1="urn:soapinterop" 
        SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <inputStruct xmlns:ns2="http://soapinterop.org/xsd" 
        xsi:type="ns2:SOAPStruct">
        <varInt xmlns:ns3="http://www.w3.org/2001/XMLSchema" 
            xsi:type="ns3:int">
          5
        </varInt>
        <varFloat xmlns:ns4="http://www.w3.org/2001/XMLSchema" 
            xsi:type="ns4:float">
          10.0
        </varFloat>
        <varString xmlns:ns5="http://www.w3.org/2001/XMLSchema" 
            xsi:type="ns5:string">
          Hola, baby
        </varString>
      </inputStruct>
    </ns1:echoStruct>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The next listing shows the Java class that represents the data structure. It holds the data and implements an equals( ) method that the EchoTestClient uses to compare results:

class Data
{
  Integer myInt;
  String myString;
  Float myFloat;
  
  public Data(  )
  {
  }
  
  public Data(int i, String s, float f)
  {
    myInt = new Integer(i);
    myString = s;
    myFloat = new Float(f);
  }
  
  public String toString(  )
  {
    return "Data[MyInt=" + myInt + ", MyString='" + 
      myString + "', myFloat=" + myFloat + "]";
  }
  
  /**
   * Equality comparison.  
   */
  public boolean equals(Object object) {
    if (!(object instanceof Data)) return false;
  
    Data that= (Data) object;
  
    if (!this.myInt.equals(that.myInt)) return false;
    if (!this.myFloat.equals(that.myFloat)) return false;
  
    if (this.myString == null) {
      if (that.myString != null) return false;
    } else {
      if (!this.myString.equals(that.myString)) return false;
    }
  
    return true;
  };
}

The custom DataSerializer class implements both the Serializer and Deserializer interfaces; these interfaces define the marshall( ) and unmarshall( ) methods, respectively. Putting both methods in one class and using that class for both serialization and deserialization is a common design strategy. The marshall( ) method simply calls the individual marshall( ) method for each datatype in the Java class. Likewise, the unmarshall( ) method calls the individual unmarshall( ) method for each type in the SOAP struct and returns a JavaBean instance:

package samples.interop;
  
import java.util.Vector;
import org.apache.soap.*;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.encoding.soapenc.*;
import org.apache.soap.rpc.*;
import org.apache.soap.messaging.*;
import java.net.URL;
import org.apache.soap.util.xml.*;
import java.io.*;
import org.w3c.dom.*;
import org.apache.soap.util.*;
import java.lang.reflect.*;
  
public class DataSerializer implements Serializer, Deserializer
{
  public void marshall(String inScopeEncStyle, Class javaType, Object src,
                       Object context, Writer sink, NSStack nsStack,
                       XMLJavaMappingRegistry xjmr, SOAPContext ctx)
    throws IllegalArgumentException, IOException
  {
    if(!javaType.equals(Data.class))
    {
      throw new IllegalArgumentException("Can only serialize Data instances");
    }
    
    Data data = (Data)src;
    
    nsStack.pushScope(  );
    if(src!=null)
    {
      SoapEncUtils.generateStructureHeader(inScopeEncStyle,
                                           javaType,
                                           context,
                                           sink,
                                           nsStack,xjmr);
  
      sink.write(StringUtils.lineSeparator);
      
      xjmr.marshall(inScopeEncStyle, Integer.class, data.myInt, "varInt",
                    sink, nsStack, ctx);
      sink.write(StringUtils.lineSeparator);
      xjmr.marshall(inScopeEncStyle, Float.class, data.myFloat, "varFloat",
                    sink, nsStack, ctx);
      sink.write(StringUtils.lineSeparator);
      xjmr.marshall(inScopeEncStyle, String.class, data.myString, "varString",
                    sink, nsStack, ctx);
      sink.write(StringUtils.lineSeparator);
      
      sink.write("</" + context + '>'),
    }
    else
    {
      SoapEncUtils.generateNullStructure(inScopeEncStyle,
                                         javaType,
                                         context,
                                         sink,
                                         nsStack,xjmr);
    }
    nsStack.popScope(  );
  }
  
  public Bean unmarshall(String inScopeEncStyle, QName elementType, Node src,
                         XMLJavaMappingRegistry xjmr, SOAPContext ctx)
    throws IllegalArgumentException
  {
    Element root = (Element)src;
    String name = root.getTagName(  );
  
    if (SoapEncUtils.isNull(root))
    {
      return new Bean(Data.class, null);
    }
    
    Data ret = new Data(  );
    NodeList list = root.getElementsByTagName("varInt");
    if (list == null || list.getLength(  ) == 0) {
        throw new IllegalArgumentException(
        "No 'varInt' Element (deserializing Data struct)");
    }
    Element el = (Element)list.item(0);
    ret.myInt = new Integer(DOMUtils.getChildCharacterData(el));
    
    list = root.getElementsByTagName("varFloat");
    if (list == null || list.getLength(  ) == 0) {
        throw new IllegalArgumentException(
        "No 'varFloat' Element (deserializing Data struct)");
    }
    el = (Element)list.item(0);
    ret.myFloat = new Float(DOMUtils.getChildCharacterData(el));
    
    list = root.getElementsByTagName("varString");
    if (list == null || list.getLength(  ) == 0) {
      throw new IllegalArgumentException(
       "No 'varString' Element (deserializing Data struct)");
    }
    el = (Element)list.item(0);
    ret.myString = ((Text)el.getFirstChild()).getData(  );
    
    return new Bean(Data.class, ret);
  }
}

Next, the custom structs are aggregated into an array:

      p = new Parameter("inputStructArray", Data[].class, new Data[]{
                        new Data(5, "cinqo", new Float("5.55555").floatValue(  )),
                        new Data(4, "quattro", (float)4.4444),
                        new Data(3, "tres", (float)3.333),
                        new Data(2, "duet", (float)2.22),
                        new Data(1, "un", (float)1.1)}, null);
      smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
          new QName("", "Return"), null, null, arraySer);
     doCall(url, "echoStructArray", p);        
    }

The output looks like . . . well, you get the idea. Because the Data type is already plugged into the serialization framework, the default array handling mechanism can just call Dataserializer to do its work.

The server

All this coding, marshalling, and unmarshalling is required because we are using a dynamic call interface. On the service side, we don’t need any of this work; we just make an RPC call and Apache SOAP takes care of the rest. The server runtime needs only the information specified in a deployment descriptor. The EchoTest Java service from Apache SOAP 2.2 is comparatively uninteresting:

package samples.interop;
  
/** An implementation of the interop echo service as defined at
 * http://www.xmethods.net/ilab.
 * 
 * @author Glen Daniels ([email protected])
 */
public class EchoTestService
{
  public void nop(  )
  {
  }
  
  public int echoInteger(int i)
  {
    return i;
  }
  
  public float echoFloat(float f)
  {
    return f;
  }
  
  public String echoString(String str)
  {
    return str;
  }
  
  public Data echoStruct(Data data)
  {
    return data;
  }
  
  public int [] echoIntegerArray(int [] ii)
  {
    return ii;
  }
  
  public float [] echoFloatArray(float [] ff)
  {
    return ff;
  }
  
  public String [] echoStringArray(String [] ss)
  {
    return ss;
  }
  
  public Data [] echoStructArray(Data [] ds)
  {
    return ds;
  }
}

In an environment in which the client and service interfaces are automatically generated by a tool, such as one that implements JAX-RPC, this coding is moot because it is hidden from the developer. When you need to understand interoperability issues between clients and services, however, it is critical to understand the concepts behind them. In the future, we may reach a state in which interoperability “just works” between almost any client and server-side implementations; however, that future is still remote.

Fun with Testing

We ran a couple of other tests as well. We tried pointing the Apache SOAP client at the Round 2: Base tests for MS SOAP Toolkit Version 3.0 and got the following results. The first three invocations failed, and the other five succeeded:

java EchoTestClient http://mssoapinterop.org/stkV3/Interop.wsdl
  
[SOAPException: faultCode=SOAP-ENV:Client; msg=No Deserializer found to 
deserialize a ':Result' using encoding style 'http://schemas.xmlsoap.org/soap/
encoding/'.; targetException=java.lang.IllegalArgumentException: No Deserializer 
found to deserialize a ':Result' using encoding style 'http://schemas.xmlsoap.org/
soap/encoding/'.]
        at org.apache.soap.rpc.Call.invoke(Call.java:246)
        at EchoTestClient.doCall(EchoTestClient.java:222)
        at EchoTestClient.doWork(EchoTestClient.java:142)
        at EchoTestClient.main(EchoTestClient.java:114)
[SOAPException: faultCode=SOAP-ENV:Client; msg=No Deserializer found to 
deserialize a ':Result' using encoding style 'http://schemas.xmlsoap.org/soap/
encoding/'.; targetException=java.lang.IllegalArgumentException: No Deserializer 
found to deserialize a ':Result' using encoding style 'http://schemas.xmlsoap.org/
soap/encoding/'.]
        at org.apache.soap.rpc.Call.invoke(Call.java:246)
        at EchoTestClient.doCall(EchoTestClient.java:222)
        at EchoTestClient.doWork(EchoTestClient.java:147)
        at EchoTestClient.main(EchoTestClient.java:114)
[SOAPException: faultCode=SOAP-ENV:Client; msg=No Deserializer found to 
deserialize a ':Result' using encoding style 'http://schemas.xmlsoap.org/soap/
encoding/'.; targetException=java.lang.IllegalArgumentException: No Deserializer 
found to deserialize a ':Result' using encoding style 'http://schemas.xmlsoap.org/soap/
encoding/'.]
        at org.apache.soap.rpc.Call.invoke(Call.java:246)
        at EchoTestClient.doCall(EchoTestClient.java:222)
        at EchoTestClient.doWork(EchoTestClient.java:152)
        at EchoTestClient.main(EchoTestClient.java:114)
echoStruct       OK
echoIntegerArray OK
echoFloatArray   OK
echoStringArray  OK
echoStructArray  OK

The errors occurred for the following reasons:

  • The generated response did not contain the xsi:type information for the return value.

  • The test client we used looked for an element with the name <Return>. The generated response used the name <Result> instead.

  • To compensate for this problem, we added the following code, which adds another entry in the SOAPMappingRegistry for the "Result" element. This addition is made for each datatype:

        p = new Parameter("inputString", String.class, "Hi there!", null);
         smr.mapTypes(Constants.NS_URI_SOAP_ENC, 
             new QName("", "Return"), null, null, stringDser);
         smr.mapTypes(Constants.NS_URI_SOAP_ENC,
                                   new QName("", "Result"), null, null, stringDser);
         doCall(url, "echoString", p);

In recognition of this issue, the MS SOAP Toolkit Version 3.0 site supports two versions of the test: one that expects and returns xsi:type information and one that does not. The test we picked was the untyped version—hence the errors. The typed version of the test is located at http://mssoapinterop.org/stkV3/InteropTyped.wsdl.

We could have used that version to begin with and declared victory, but we wanted to go through the exercise with you because this issue can arise when communicating between two different kinds of toolkits.

We tried the same test with the Apache Axis version of the Echo Test client and got the following results:

java TestClient
URL: http://mssoapinterop.org/stkV3/Interop.wsdl
echoString               OK
echoStringArray          OK
echoInteger              OK
echoIntegerArray         OK
echoFloat                OK
echoFloatArray           OK
echoStruct               OK
echoStructArray          OK
echoVoid                 OK
echoBase64               OK
echoHexBinary            OK
echoDate                 OK
echoDecimal              OK
echoBoolean              OK
echoMap                  Fail: WSDLReader:None of the matching operations for 
soapAction http://soapinterop.org/ could successfully load the incoming request. 
Potential typemapper problem
echoMapArray             Fail: WSDLReader:None of the matching operations for 
soapAction http://soapinterop.org/ could successfully load the incoming request. 
Potential typemapper problem

Using other test clients through a browser interface

Many SB Interop participants have built a browser client that remotely executes a test client on your behalf. Figure 9-1 shows the interface to one such site for Iona XMLBus (http://interop.xmlbus.com:7002/InteropTest/index.jsp). You can type in any known WSDL endpoint that participates in the testing and learn how an XMLBus client fared against the remote service.

The browser interface used for running SB Interop clients remotely

Figure 9-1. The browser interface used for running SB Interop clients remotely

Figure 9-2 shows an example of the results.

Test results from browser-based remote client testing

Figure 9-2. Test results from browser-based remote client testing



[15] The scope of the test has grown to include other datatypes. This version of the test exercises eight of them.

[16] A QName is a namespace-qualified element name, for which the namespace is optional.

[17] A struct is a complex datatype defined by SOAP encoding.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset