ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: Java and Soap

Working with Complex Data Types, Part 4

Related Reading

Java and SOAP
By Robert Englander

by Robert Englander

This is the last in a series of book excerpts from Java and SOAP on working with complex data types. In this excerpt, learn about returning custom types, using a stock market example.

Returning Custom Types

It's equally useful (and equally common) to return custom types from service method calls. We can enhance our trading service by offering a method that takes a single stock symbol as a parameter and returns its high and low trading prices for the day. The classes HighLow_ServerSide and HighLow_ClientSide represent the high/low prices on the server and client, respectively.

package javasoap.book.ch5;
public class HighLow_ServerSide {
   public float _high;
   public float _low;
   public HighLow_ServerSide(  ) {
   }
   public HighLow_ServerSide (float high, float low) {
      setHigh(high);
      setLow(low);
   }
   public float getHigh(  ) {
      return _high;
   }
   public void setHigh(float high) {
      _high = high;
   }
   public float getLow(  ) {
      return _low;
   }
   public void setLow(float low) {
      _low = low;
   }
}
  
package javasoap.book.ch5;
public class HighLow_ClientSide {
   public float _high;
   public float _low;
   public String toString(  ) {
      return "High: " + _high +
        " Low: " + _low;
   }
   public HighLow_ClientSide(  ) {
   }
   public float getHigh(  ) {
      return _high;
   }
   public void setHigh(float high) {
      _high = high;
   }
   public float getLow(  ) {
      return _low;
   }
   public void setLow(float low) {
      _low = low;
   }
}

In This Series

Working with Complex Data Types, Part 3
The third in a series of excerpts from Java and SOAP, this article excerpt covers passing custom types as parameters.

Working with Complex Data Types, Part 2
In part two in this series of book excerpts from Java and SOAP, learn about returning arrays.

Working with Complex Data Types, Part 1
In this excerpt on complex data types from Java and SOAP, the authors discuss passing arrays as parameters.

The server-side class includes a parameterized constructor as a convenience for creating the return value; the client-side class includes a toString( ) method to make it easy for our client application to display the contents of the object after it's returned from the server. Let's add a new method to the BasicTradingService class called getHighLow( ). That method takes a single string parameter for the stock symbol and returns an instance of HighLow_ServerSide. Here's the class with its new method, with the unchanged code omitted:

package javasoap.book.ch5;
public class BasicTradingService {
   public BasicTradingService(  ) {
   }
   . . .
   . . .
   public HighLow_ServerSide getHighLow(String stock) {
      
      // retrieve the high and low for the specified stock
      return new HighLow_ServerSide((float)110.375, 
                      (float)109.5);
   }
}

In order to make this new method available in Apache SOAP, we'll need to redeploy the service using a modified deployment descriptor. We need to add getHighLow to the list of methods, and add an entry in the mappings section for the high/low object. The second mapping entry defines the HighLowa custom type, namespace-qualified using the service name urn:BasicTradingService. Here is the modified deployment descriptor:

<isd:service 
    xmlns:isd="http://xml.apache.org/xml-soap/deployment"
    id="urn:BasicTradingService">
  <isd:provider 
     type="java"
     scope="Application"
     methods="getTotalVolume getMostActive executeTrade executeStockTrade
              getHighLow">
    <isd:java 
       class="javasoap.book.ch5.BasicTradingService" 
       static="false"/>
  </isd:provider>
  
  <isd:faultListener>org.apache.soap.server.DOMFaultListener
  </isd:faultListener>
  <isd:mappings>
    <isd:map  
       encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
       xmlns:x="urn:BasicTradingService" qname="x:StockTrade"
       javaType="javasoap.book.ch5.StockTradeServer"
       java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
       xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
    <isd:map  
       encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
       xmlns:x="urn:BasicTradingService" qname="x:HighLow"
       javaType="javasoap.book.ch5.HighLow_ServerSide"
       java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
       xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
  </isd:mappings>    
</isd:service>

Now we can create a client application that invokes the getHighLow service method and receives a high/low object in return. For the Apache SOAP client, we'll use the HighLow_ClientSide class to represent the return value. Here's the new application:

package javasoap.book.ch5;
import java.net.*;
import java.util.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.*;
import org.apache.soap.util.xml.*;
public class HighLowClient
{
  public static void main(String[] args) throws Exception 
  {
    URL url = new  
       URL("http://georgetown:8080/soap/servlet/rpcrouter");
    Call call = new Call(  );
    SOAPMappingRegistry smr = new SOAPMappingRegistry(  );
    call.setTargetObjectURI("urn:BasicTradingService");
    call.setMethodName("getHighLow");
    call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
    call.setSOAPMappingRegistry(smr);
    BeanSerializer beanSer = new BeanSerializer(  );
    // Map the High/Low type
    smr.mapTypes(Constants.NS_URI_SOAP_ENC,
             new QName("urn:BasicTradingService", "HighLow"),
             HighLow_ClientSide.class, beanSer, beanSer);
    String stock = "XYZ";
    Vector params = new Vector(  );
    params.addElement(new Parameter("stock", 
                           String.class, stock, null));
    call.setParams(params);
    Response resp;
    try {
      resp = call.invoke(url, "");
      Parameter ret = resp.getReturnValue(  );
      HighLow_ClientSide hilo = 
             (HighLow_ClientSide)ret.getValue(  );
      System.out.println(hilo);
    }
    catch (SOAPException e) {
      System.err.println("Caught SOAPException (" +
                         e.getFaultCode(  ) + "): " +
                         e.getMessage(  ));
    }
  }
}

smr.MapTypes( ) maps the HighLow custom type to the HighLow_ClientSide Java class. Just as before, we can use Apache's BeanSerializer to convert between XML and Java, since our class conforms to the JavaBeans property accessor pattern. We set up a single String parameter called stock to pass to the getHighLow method (although we don't actually make use of it in the server code). After the method is invoked, we cast the return value of resp.getReturnValue( ) to an instance of HighLow_ClientSide. Then we pass the return parameter variable, ret, to the System.out.println( ) method for display. This is all we need, since we implemented the toString( ) method in our HighLow_ClientSide class. When you run this example, you'll get the following output:

High: 110.375 Low: 109.5

Here's the SOAP envelope returned from this method invocation. The return element is typed as a HighLow that is namespace-qualified by the urnBasicTradingService namespace. The properties, which are child elements of the return element, are typed as floats and appear along with their corresponding values.

<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:getHighLowResponse 
      xmlns:ns1="urn:BasicTradingService" 
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <return xsi:type="ns1:HighLow">
         <low xsi:type="xsd:float">109.5</low>
         <high xsi:type="xsd:float">110.375</high>
      </return>
   </ns1:getHighLowResponse>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

To return a HighLow using GLUE, we'll take the same steps we did for passing custom types. Again we'll separate our client-side work into another package, javasoap.book.ch5.client. Just restart the application of BasicTradingApp to get the service deployed. Now run the wsdl2java utility from the directory corresponding to the javasoap.book.ch5.client package:

wsdl2java http://georgetown:8004/glue/urn:BasicTradingService.wsdl 
    -p javasoap.book.ch5.client

The following code is the new IBasicTradingService interface generated by wsdl2java. The getHighLow( ) method returns an instance of HighLow_ServerSide; remember that this class is the new one generated by GLUE as part of the javasoap.book.ch5.client package, not the one being used by our server class, BasicTradingApp.

// generated by GLUE
package javasoap.book.ch5.client;
public interface IBasicTradingService
  {
  HighLow_ServerSide getHighLow( String arg0 );
  String executeStockTrade( StockTradeServer arg0 );
  String[] getMostActive(  );
  int getTotalVolume( String[] arg0 );
  String executeTrade( Object[] arg0 );
  }

The next class, HighLow_ServerSide, is simply a Java data structure reflecting the data fields that will be mapped to the HighLow custom type. I added the toString( ) method by hand to make it simpler to display the results.

package javasoap.book.ch5.client;
public class HighLow_ServerSide {
  public float _high;
  public float _low;
  public String toString(  ) {
     return "High: " + _high +
        " Low: " + _low;
  }  
}

Now let's create a client application using GLUE that invokes the getHighLow service method and displays the contents of the resulting return value. There's not much to this, really; we simply read the map file, perform the bind, and then call the getHighLow method on the bound interface. The resulting instance of javasoap.book.ch5.client.HighLow_ServerSide is passed to System.out.println( ) for display. Just as in the Apache SOAP example, the toString( ) method of the class handles the creation of the display string.

package javasoap.book.ch5.client;
import electric.registry.RegistryException;
import electric.registry.Registry;
import electric.xml.io.Mappings;
public class HighLowClient2 {
   public static void main(String[] args) throws Exception 
   {
      try {
        Mappings.readMappings("BasicTradingService.map");
        IBasicTradingService srv = (IBasicTradingService)Registry.bind(
          "http://georgetown:8004/glue/urn:BasicTradingService.wsdl",
          IBasicTradingService.class);
        HighLow_ServerSide hilo = srv.getHighLow("ANY");
        System.out.println(hilo);
    }
    catch (RegistryException e)
    {
       System.out.println(e);
    }
  }
}

Here's the SOAP envelope returned from the server. You should be able to follow this by now. GLUE uses a reference to a separately serialized instance of the custom type, just as it did when returning an array.

<soap:Envelope 
 xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' 
 xmlns:xsd='http://www.w3.org/2001/XMLSchema'   
 xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' 
 xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' 
 soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
    <soap:Body>
      <n:getHighLowResponse xmlns:n='urn:BasicTradingService'>
        <Result href='#id0'/>
      </n:getHighLowResponse>
      <id0 id='id0' soapenc:root='0' 
        xmlns:ns2='http://www.themindelectric.com/package/javasoap.book.ch5/'
        xsi:type='ns2:HighLow_ServerSide'>
        <_high xsi:type='xsd:float'>110.375</_high>
        <_low xsi:type='xsd:float'>109.5</_low>
      </id0>
    </soap:Body>
</soap:Envelope>

In this chapter, we've taken an in-depth look at the use of arrays and custom data types, and discussed how these structures are supported by Apache SOAP and GLUE. There are other useful types that you may find support for in these, or other, SOAP implementations. These types might include Java Vector and Hashtable classes, Java collections, and a variety of other commonly used Java classes.

We've seen that Apache SOAP and GLUE approach complex types in different ways, and both do a good job of supporting them. However, there may be times when you need to work with a custom type that either can't, or shouldn't, be serialized in the way provided by your SOAP implementation. This situation may arise because there simply is no support for a particular type of data, as is the case with multidimensional or sparse arrays, or perhaps for some other reason related to your application. We'll tackle this issue in the next chapter.

Robert Englander is Principal Engineer and President of MindStream Software, Inc. (www.mindstrm.com). He provides consulting services in software architecture, design, and development, as well as developing frameworks for use on client projects.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.