|
|
|
|
Java and SOAPBy Robert EnglanderMay 2002 0-596-00175-4, Order Number: 1754 276 pages, $39.95 US $61.95 CA |
Chapter 5
Working with Complex Data TypesIn the previous chapter, we created RPC-style services in Java. Those services dealt only with simple data types. Simple data types may suffice in many situations, but you'll eventually need to use more complex data types, like those you're familiar with in your day-to-day Java programming. In this chapter we'll look at creating services with method parameters and return values that are arrays and Java beans. We'll hold off on custom data types until the next chapter, since it's possible that any custom types you create would use the complex data types we'll be discussing here.
Passing Arrays as Parameters
Let's face it--arrays are probably the most common complex data type in programming. They're everywhere, so their use in SOAP is critical. We covered the details of SOAP arrays back in Chapter 3, so you should be aware of how arrays are encoded. So let's get right into writing some Java code for services that use arrays.
We've been working with stock market examples, so let's stick with that theme. It might be useful to have a service that returns information about a collection of stock symbols. It might provide the total volume of shares traded for the day, the average trading price of those stocks, the number of stocks trading higher for the day, etc. There are lots of possibilities. Let's start out with a service that returns the total number of shares traded for the day. The service is calledurn:BasicTradingService, and it has a method calledgetTotalVolume. Here is the Java class that implements the service:package javasoap.book.ch5;public class BasicTradingService {public BasicTradingService( ) {}public int getTotalVolume(String[] stocks) {// get the volumes for each stock from some// data feed and return the totalint total = 345000;return total;}}
TheBasicTradingServiceclass contains the methodgetTotalVolume( ), which returns the total number of shares traded. Since we're not going to access a data feed, we'll just return a made-up value. The method returns an integer and takes a single parameter calledstocksthat is an array ofStringvalues. The strings in the array are the stock symbols; in a real application, we'd retrieve the volume for each stock from our data feed and return the total for all the stocks in the array.Apache SOAP has built-in support for arrays, so you don't need to do anything special on the service side. This also means that there's nothing new in the deployment descriptor; it's built just like it was in the previous chapter. Here is the deployment descriptor for the
urn:BasicTradingServiceservice:<isd:servicexmlns:isd="http://xml.apache.org/xml-soap/deployment"id="urn:BasicTradingService"><isd:provider type="java"scope="Application"methods="getTotalVolume"><isd:java class="javasoap.book.ch5.BasicTradingService"static="false"/></isd:provider><isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener><isd:mappings></isd:mappings></isd:service>You can deploy the service using this deployment descriptor, or you can use the Apache SOAP Admin tool from your browser.
Now let's write a client application that accesses the service. This application is similar to some of the examples from the previous chapter; it differs only in that the parameter we're passing is an array of
Stringinstances, rather than a single object. Here is the code:package javasoap.book.ch5;import java.net.*;import java.util.*;import org.apache.soap.*;import org.apache.soap.rpc.*;public class VolumeClient {public static void main(String[] args) throws Exception {URL url = new URL("http://georgetown:8080/soap/servlet/rpcrouter");Call call = new Call( );call.setTargetObjectURI("urn:BasicTradingService");call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);String[] stocks = { "MINDSTRM", "MSFT", "SUN" };Vector params = new Vector( );params.addElement(new Parameter("stocks",String[].class, stocks, null));call.setParams(params);try {call.setMethodName("getTotalVolume");Response resp = call.invoke(url, "");Parameter ret = resp.getReturnValue( );Object value = ret.getValue( );System.out.println("Total Volume is " + value);}catch (SOAPException e) {System.err.println("Caught SOAPException (" +e.getFaultCode( ) + "): " +e.getMessage( ));}}}We passed
String[].classas the second parameter of theParameterconstructor. That identifies thestocksvariable as an array of strings. That's it. Nothing else is required. If you run this example, the output should be:Total Volume is 345000Let's
take a look at the SOAP envelope that was passed to the server for the invocation of thegetTotalVolumeservice method:<SOAP-ENV:Envelopexmlns: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:getTotalVolume xmlns:ns1="urn:BasicTradingService"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><stocksxmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/"xsi:type="ns2:Array"ns2:arrayType="xsd:string[3]"><item xsi:type="xsd:string">MINDSTRM</item><item xsi:type="xsd:string">MSFT</item><item xsi:type="xsd:string">SUN</item></stocks></ns1:getTotalVolume></SOAP-ENV:Body></SOAP-ENV:Envelope>The
stockselement represents the string array parameter that we passed to the service method. It is typed as an array by setting thexsi:typeattribute tons2:Array.Thens2namespace identifier was defined on the previous line. Next, thens2:arrayTypeattribute is assigned a value ofxsd:string[3]. This means that this is an array of size 3, and that each element of the array is anxsd:string. There are three child elements ofstocks, each one nameditem. Remember that the name used for the array elements doesn't matter, and that different SOAP implementations use different schemes for naming these elements. Each element is explicitly typed by setting thexsi:typeattribute to the valuexsd:string.This example uses a homogeneous array, i.e., all of the elements of the array are instances of the same type. You may have occasion to use heterogeneous arrays as well, so let's look at that possibility. In Java, arrays are often used as parameters to methods that, in other languages, would have a variable-length parameter list. For instance, the
printf( )function in the C language doesn't have a fixed number of parameters. Even though Java doesn't support this capability, you can simulate it by passing your parameter values in an array. An array can be of any size, and the array elements aren't required to have the same type.
Let's add a method to theurn:BasicTradingServicethat takes a single heterogeneous array as a parameter. The method is calledexecuteTrade. Its parameter is an array containing the stock symbol, the number of shares to trade, and a flag indicating whether it's a buy or sell order (truemeans buy). The return value is a string that describes the trade.[1] Here is the modifiedBasicTradingServiceclass:package javasoap.book.ch5;public class BasicTradingService {public BasicTradingService( ) {}public int getTotalVolume(String[] stocks) {// get the volumes for each stock from some// data feed and return the totalint total = 345000;return total;}public String executeTrade(Object[] params) {String result;try {String stock = (String)params[0];Integer numShares = (Integer)params[1];Boolean buy = (Boolean)params[2];String orderType = "Buy";if (false == buy.booleanValue( )) {orderType = "Sell";}result = (orderType + " " + numShares + " of " + stock);}catch (ClassCastException e) {result = "Bad Parameter Type Encountered";}return result;}}There is only one parameter for the
executeTrade( )method, anObject[]calledparams. The objects in this array must be cast to their corresponding types: aString, anInteger, and aBoolean. I like to put class casts inside atry/catchblock in case the caller makes a mistake. That way I can do something useful if the method is called incorrectly, even if that means simply returning a description of the error. In Chapter 7, we'll look at generating SOAP faults for situations like this. The information passed in the array is used to generate a string that describes the parameters, and that string is stored in theresultvariable that is returned to the caller.Now we can modify the client application so that it passes an appropriate
Object[]as the parameter to theexecuteTradeservice method. ThemultiParamsvariable is declared as anObject[], and is populated with theStringMINDSTRM, anIntegerwith the value of 100, and aBooleanwith the value oftrue. Since we're using an array of JavaObjectinstances, we don't use Java primitives as elements of the array. Instead we wrap those primitive values in their Java object equivalents. The second parameter of theParameterconstructor isObject[].class, which is the class for an array of object instances.package javasoap.book.ch5;import java.net.*;import java.util.*;import org.apache.soap.*;import org.apache.soap.rpc.*;public class TradingClient {public static void main(String[] args)throws Exception {URL url =new URL("http://georgetown:8080/soap/servlet/rpcrouter");Call call = new Call( );call.setTargetObjectURI("urn:BasicTradingService");call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);Object[] multiParams = { "MINDSTRM", new Integer(100),new Boolean(true) };Vector params = new Vector( );params.addElement(new Parameter("params",Object[].class, multiParams, null));call.setParams(params);try {call.setMethodName("executeTrade");Response resp = call.invoke(url, "");Parameter ret = resp.getReturnValue( );Object value = ret.getValue( );System.out.println("Trade Description: " + value);}catch (SOAPException e) {System.err.println("Caught SOAPException (" +e.getFaultCode( ) + "): " +e.getMessage( ));}}}If all goes well, the result of executing the
executeTrade( )service method is:Trade Description: Buy 100 of MINDSTRMWe could force the service object down another path by changing the order of the parameters in the
multiParamsarray. In this case, we would encounter a class cast exception, and the method would return an error string.Here is the SOAP envelope for the proper invocation of the
executeTrade( )service method:<SOAP-ENV:Envelopexmlns: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:executeTrade xmlns:ns1="urn:BasicTradingService"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><paramsxmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/"xsi:type="ns2:Array" ns2:arrayType="xsd:anyType[3]"><item xsi:type="xsd:string">MINDSTRM</item><item xsi:type="xsd:int">100</item><item xsi:type="xsd:boolean">true</item></params></ns1:executeTrade></SOAP-ENV:Body></SOAP-ENV:Envelope>
Theparamselement is typed by assigning thexsi:typeattribute the value ofns2:Array. The only difference from the homogeneous case is that every array element has a different value assigned to thens2:arrayTypeattribute. The valuexsd:anyType[3]indicates that the array contains 3 elements, each of which can be of any valid data type.[2]Now let's take a look at passing arrays as parameters using GLUE. The
BasicTradingServiceclass can be deployed in GLUE without modification. We'll use a simple Java application to get this service started:package javasoap.book.ch5;import electric.util.Context;import electric.registry.Registry;import electric.server.http.HTTP;public class BasicTradingApp {public static void main( String[] args )throws Exception {HTTP.startup("http://georgetown:8004/glue");Context context = new Context( );context.addProperty("activation", "application");context.addProperty("namespace","urn:BasicTradingService");Registry.publish("urn:BasicTradingService",javasoap.book.ch5.BasicTradingService.class, context );}}Compile and execute the application, and the service is deployed. Now let's write a simple example to access the service using the GLUE API. First let's look at the interface to the service,
IBasicTradingService:package javasoap.book.ch5;public interface IBasicTradingService {int getTotalVolume(String[] symbols);String executeTrade(Object[] params);}Now we can write an application that binds to the service and calls both its methods:
package javasoap.book.ch5;import electric.registry.RegistryException;import electric.registry.Registry;public class BasicTradingClient {public static void main(String[] args) throws Exception{try {IBasicTradingService srv =(IBasicTradingService)Registry.bind("http://georgetown:8004/glue/urn:BasicTradingService.wsdl",IBasicTradingService.class);String[] stocks = { "MINDSTRM", "MSFT", "SUN" };int total = srv.getTotalVolume(stocks);System.out.println("Total Volume is " + total);Object[] multiParams = { "MINDSTRM", new Integer(100),new Boolean(true) };String desc = srv.executeTrade(multiParams);System.out.println("Trade Description: " + desc);}catch (RegistryException e){System.out.println(e);}}}As we've seen before, GLUE allows us to use familiar Java programming syntax without having to think about the underlying SOAP constructs. This holds true for the passing of array parameters as well. Everything is pretty much handled for us after the interface is bound to the service.
There are some interesting things to see in the SOAP request envelopes generated by this example. Here is the SOAP envelope for the
getTotalVolume( )method invocation:<soap:Envelopexmlns: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:getTotalVolume xmlns:n='urn:BasicTradingService'><arg0 href='#id0'/></n:getTotalVolume><id0 id='id0' soapenc:root='0'xmlns:ns2='http://www.themindelectric.com/package/java.lang/'xsi:type='soapenc:Array' soapenc:arrayType='xsd:string[3]'><i xsi:type='xsd:string'>MINDSTRM</i><i xsi:type='xsd:string'>MSFT</i><i xsi:type='xsd:string'>SUN</i></id0></soap:Body></soap:Envelope>The
Bodyelement starts off like we've seen before; the envelope is qualified by a namespace identifier,soap, which represents the namespace of the SOAP envelope. The first child element of the SOAP body isgetTotalVolume, which of course is the name of the service method being invoked.getTotalVolumeis namespace-qualified using the name of the service. The only child element ofgetTotalVolumeisarg0, which represents the parameter passed to the method. But this isn't the array we passed; it's a reference to the array. This is a significant difference between the ways that GLUE and the Apache SOAP API generate this call. Apache SOAP puts the array in the envelope as a child element ofgetTotalVolume, and GLUE uses a reference and serializes the array after thegetTotalVolumeelement terminates. So the parameter is serialized asarg0, and includes anhrefattribute with the value#id0. No data is included, as the array resides elsewhere.The array that we passed as a parameter follows the
getTotalVolumeelement. It's namedid0, although the element name itself, which is generated by GLUE, is not important. Theidattribute is assigned a value ofid0, which coincides with thehrefvalue used in thegetTotalVolumeelement. GLUE generates thesoapenc:rootattribute with a value of 0, meaning that this element is not considered the root of an object graph. (GLUE seems to include that attribute automatically.)Next we see a declaration of a namespace identifier calledns2that seems to identify the internal GLUE package forjava.lang; however, thens2namespace identifier is never used. Thexsi:typeandsoapenc:arrayTypeattributes are set up in the same way as in the Apache SOAP examples. Finally, the elements of the array are serialized. The only difference between this example and the one generated by Apache SOAP is in the name of the array elements themselves. Apache SOAP named these elementsitem, and GLUE named themi. The names don't matter; the result is the same.This example gives us a good opportunity to see two equally valid ways to serialize arrays. It's important that SOAP implementations understand these different styles if they are to interoperate. This is one of the reasons we keep showing the SOAP envelopes generated by the examples. Becoming familiar with the various styles of serialization will help you down the road if you run into problems getting applications based on different implementations to communicate correctly. (Did I say if ?)
Let's take a look at the SOAP envelope for the call to the
executeTradeservice method. This method takes a heterogeneous array as a parameter. It too uses a reference to a separately serialized array, this time encoded asxsd:anyType:<soap:Envelopexmlns: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:executeTrade xmlns:n='urn:BasicTradingService'><arg0 href='#id0'/></n:executeTrade><id0 id='id0' soapenc:root='0'xmlns:ns2='http://www.themindelectric.com/package/java.lang/'xsi:type='soapenc:Array' soapenc:arrayType='xsd:anyType[3]'><i xsi:type='xsd:string'>MINDSTRM</i><i xsi:type='xsd:int'>100</i><i xsi:type='xsd:boolean'>true</i></id0></soap:Body></soap:Envelope>Returning Arrays
So far we've been passing arrays as parameters. Now let's use an array as the return value of a service method.
We'll add a method to our service calledgetMostActive( ), which returns aString[]that contains the symbols for the most actively traded stocks of the day. Here's the new version of theBasicTradingServiceclass:package javasoap.book.ch5;public class BasicTradingService {public BasicTradingService( ) {}public String[] getMostActive( ) {// get the most actively traded stocksString[] actives = { "ABC", "DEF", "GHI", "JKL" };return actives;}public int getTotalVolume(String[] stocks) {// get the volumes for each stock from some// data feed and return the totalint total = 345000;return total;}public String executeTrade(Object[] params) {String result;try {String stock = (String)params[0];Integer numShares = (Integer)params[1];Boolean buy = (Boolean)params[2];String orderType = "Buy";if (false == buy.booleanValue( )) {orderType = "Sell";}result = (orderType + " " + numShares + " of " + stock);}catch (ClassCastException e) {result = "Bad Parameter Type Encountered";}return result;}}Since we're not really calling a data feed, we just stuff a few phony stock symbols into an array and return it. Go ahead and redeploy the service now. Calling this service method from an Apache SOAP client is simple. There are no parameters to the service method, so we just have to set up the call and invoke it:
package javasoap.book.ch5;import java.net.*;import org.apache.soap.*;import org.apache.soap.rpc.*;public class MostActiveClient{public static void main(String[] args) throws Exception{URL url = newURL("http://georgetown:8080/soap/servlet/rpcrouter");Call call = new Call( );call.setTargetObjectURI("urn:BasicTradingService");call.setMethodName("getMostActive");Response resp;try {resp = call.invoke(url, "");Parameter ret = resp.getReturnValue( );String[] value = (String[])ret.getValue( );int cnt = value.length;for (int i = 0; i < cnt; i++) {System.out.println(value[i]);}}catch (SOAPException e) {System.err.println("Caught SOAPException (" +e.getFaultCode( ) + "): " +e.getMessage( ));}}}We cast the return value of
ret.getValueto aString[], since that's the return type we're expecting. In past examples we were able to leave the value as anObjectinstance because we relied on the object'stoString( )method to display the value. In this case we need to iterate over the array, so it's necessary to cast the value to the appropriate array type. After that, we just find the array length and then loop over the array values, printing each one as we get to it. If you run this example you should see the following output:ABCDEFGHIJKLThe SOAP envelope returned by this method invocation is pretty straightforward:
<SOAP-ENV:Envelopexmlns: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:getMostActiveResponsexmlns:ns1="urn:BasicTradingService"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><returnxmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/"xsi:type="ns2:Array" ns2:arrayType="xsd:string[4]"><item xsi:type="xsd:string">ABC</item><item xsi:type="xsd:string">DEF</item><item xsi:type="xsd:string">GHI</item><item xsi:type="xsd:string">JKL</item></return></ns1:getMostActiveResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>To deploy this version of the
BasicTradingServiceclass in GLUE, we can use our oldBasicTradingAppclass. We can modify the Java interface,IBasicTradingService, to include the new method:package javasoap.book.ch5;public interface IBasicTradingService {int getTotalVolume(String[] symbols);String executeTrade(Object[] params);String[] getMostActive( );}Now we modify the application
BasicTradingClientto include a call to thegetMostActive( )method, and then iterate over the values in the array and print them out. When using GLUE we don't have to cast the return value to aString[]because, unlike the Apache SOAP example, thegetMostActive( )method of the interface is defined to return the proper type. Here's the modified code:package javasoap.book.ch5;import electric.registry.RegistryException;import electric.registry.Registry;public class BasicTradingClient {public static void main(String[] args) throws Exception{try {IBasicTradingService srv = (IBasicTradingService)Registry.bind("http://georgetown:8004/glue/urn:BasicTradingService.wsdl",IBasicTradingService.class);String[] stocks = { "MINDSTRM", "MSFT", "SUN" };int total = srv.getTotalVolume(stocks);System.out.println("Total Volume is " + total);Object[] multiParams = { "MINDSTRM", new Integer(100),new Boolean(true) };String desc = srv.executeTrade(multiParams);System.out.println("Trade Description: " + desc);String[] actives = srv.getMostActive( );int cnt = actives.length;for (int i = 0; i < cnt; i++) {System.out.println(actives[i]);}}catch (RegistryException e){System.out.println(e);}}}If you run this example, you'll get the following output:
Total Volume is 345000Trade Description: Buy 100 of MINDSTRMABCDEFGHIJKLGLUE uses the same serialization technique for arrays as return values that we saw earlier for array parameters; it uses a reference to a separately serialized array as the actual return value, and it references the actual array data. The SOAP envelope returned when invoking the
getMostActive( )method is:<soap:Envelopexmlns: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:getMostActiveResponsexmlns:n='urn:BasicTradingService'><Result href='#id0'/></n:getMostActiveResponse><id0 id='id0' soapenc:root='0'xmlns:ns2='http://www.themindelectric.com/package/java.lang/'xsi:type='soapenc:Array' soapenc:arrayType='xsd:string[4]'><i xsi:type='xsd:string'>ABC</i><i xsi:type='xsd:string'>DEF</i><i xsi:type='xsd:string'>GHI</i><i xsi:type='xsd:string'>JKL</i></id0></soap:Body></soap:Envelope>Passing Custom Types as Parameters
As Java programmers, we certainly don't restrict ourselves to the classes found in the standard Java packages. A substantial part of our code exists as custom types, Java classes of our own creation that embody the functionality and characteristics of the systems we're building.
Consider the design of a Java class that contains all of the data necessary to specify a stock trade. This new class might contain the symbol for the stock being traded, the number of shares to trade, and an indication of the order type (buy or sell). When designing such a class, it's important to view it in the context of the larger system. That kind of analysis yields clues that can lead to decisions regarding the properties and behaviors to be given to the class. We all do this kind of work all the time; it's called software design. The result is a Java class that contains methods for accessing properties and behavior. Since SOAP is a data transport, we're interested in the properties of the class. That's what we want to transmit over the wire.
One common way to express the properties of a Java class is to use the JavaBeans design patterns. These patterns specify a naming convention to be used for the class's access methods. You may not be familiar with JavaBeans, but I bet you've seen this pattern many times. Here's how the property accessor pattern is described in the O'Reilly book Developing Java Beans:
The methods used for getting and setting property values should conform to the standard design pattern for properties. These methods are allowed to throw checked exceptions if desired, but this is optional. The method signatures are as follows:The existence of a matching pair of methods that conform to this pattern represents a read/write property with the name <PropertyName> of the type <PropertyType>. If only the get method exists, the property is considered to be read only, and if only the set method exists the property is considered to be write only. In the case where the <PropertyType> is boolean, the get method can be replaced or augmented with a method that uses the following signature:public void set<PropertyName>(<PropertyType> value);public <PropertyType> get<PropertyName>( );public boolean is<PropertyName>( ); If you follow this pattern for naming property accessors, the accessor methods can be determined at runtime by using the Java reflection mechanism.[3] This is a convenient way for SOAP implementations to access the data values of a Java class instance in order to serialize the data in a SOAP message. It turns out that both Apache SOAP and GLUE take advantage of reflection. This means that all you need to do is follow a well-established naming convention, and you'll be well on your way to using custom classes in SOAP. Of course, there's a little more to it than that, so let's dig in.
First let's define a stock trade in terms of data that we want it to contain. It needs to have a stock symbol to represent the stock being traded; the symbol should be a string type element. It needs a
booleanindicator that specifies whether the order is buy or sell. Lastly, it needs an integer that contains the number of shares to be traded. (In a real-world application, it might also contain various security credentials, the names of the purchaser and the broker, and many other things. Alternatively, this ancillary data could be represented in other objects.) Here's what an XML schema snippet for the stock trade might look like:<element name="StockTrade" type="StockTrade"/><complexType name="StockTrade"><element name="symbol" type="xsd:string"/><element name="buy" type="xsd:boolean"/><element name="numshares" type="xsd:int"/></complexType>Let's create a custom Java class for specifying a stock trade called
StockTrade_ClientSide. Normally I'd name this classStockTrade, but I want to make it clear that I'll be using this class on the client side of the example. We'll be writing a similar class to be used on the server side that will have a corresponding name. I'm doing this to point out that you are not required to use the same Java class on both sides of the message transaction. In fact, it probably won't make sense to use the same class, and it often won't even be possible.
StockTrade_ClientSidehas three read/write properties namedSymbol,Buy, andNumSharesthat represent the stock symbol, the buy/sell indicator, and the number of shares to trade, respectively.package javasoap.book.ch5;public class StockTrade_ClientSide {String _symbol;boolean _buy;int _numShares;public StockTrade_ClientSide( ) {}public StockTrade_ClientSide(String symbol,boolean buy, int numShares) {_symbol = symbol;_buy = buy;_numShares = numShares;}public String getSymbol( ) {return _symbol;}public void setSymbol(String symbol) {_symbol = symbol;}public boolean isBuy( ) {return _buy;}public void setBuy(boolean buy) {_buy = buy;}public int getNumShares( ) {return _numShares;}public void setNumShares(int numShares) {_numShares = numShares;}}
Now let's create aStockTrade_ServerSideclass to represent the stock trade on the server side. Just to be sure that this class is different from its client-side counterpart, let's eliminate the constructor that takes parameters. And for good measure, let's also change the names of the class variables and the order in which they appear.package javasoap.book.ch5;public class StockTrade_ServerSide {int _shares;boolean _buyOrder;String _stock;public StockTrade_ServerSide( ) {}public String getSymbol( ) {return _stock;}public void setSymbol(String stock) {_stock = stock;}public boolean isBuy( ) {return _buyOrder;}public void setBuy(boolean buyOrder) {_buyOrder = buyOrder;}public int getNumShares( ) {return _shares;}public void setNumShares(int shares) {_shares = shares;}}
We can add a new method to theurn:BasicTradingServiceservice calledexecuteStockTrade( ), which takes a stock trade as a parameter. The return value from this method is a string that describes the order. Here's the modifiedBasicTradingServiceclass. We can take advantage of theexecuteTrade( )method that already exists in this class. In the new method,executeStockTrade( ), we build anObjectarray from the three properties of thetradeparameter, and we pass that array to theexecuteTrade( )method.package javasoap.book.ch5;public class BasicTradingService {public BasicTradingService( ) {}public String executeStockTrade(StockTrade_ServerSide trade) {Object[] params = new Object[3];params[0] = trade.getSymbol( );params[1] = new Integer(trade.getNumShares( ));params[2] = new Boolean(trade.isBuy( ));return executeTrade(params);}public String[] getMostActive( ) {// get the most actively traded stocksString[] actives = { "ABC", "DEF", "GHI", "JKL" };return actives;}public int getTotalVolume(String[] stocks) {// get the volumes for each stock from some// data feed and return the totalint total = 345000;return total;}public String executeTrade(Object[] params) {String result;try {String stock = (String)params[0];Integer numShares = (Integer)params[1];Boolean buy = (Boolean)params[2];String orderType = "Buy";if (false == buy.booleanValue( )) {orderType = "Sell";}result = (orderType + " " + numShares + " of " + stock);}catch (ClassCastException e) {result = "Bad Parameter Type Encountered";}return result;}}To deploy the service, we need to map the custom type to the Java class that implements that type. We need to give the custom type a name and qualify it with an appropriate namespace. This is not unlike the process we'd use to declare a service.
This mapping takes place in the deployment descriptor, within theisd:mappingssection. Here's the deployment descriptor we'll use to deploy the service in Apache SOAP:<isd:servicexmlns:isd="http://xml.apache.org/xml-soap/deployment"id="urn:BasicTradingService"><isd:providertype="java"scope="Application"methods="getTotalVolume getMostActive executeTrade executeStockTrade"><isd:javaclass="javasoap.book.ch5.BasicTradingService"static="false"/></isd:provider><isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener><isd:mappings><isd:mapencodingStyle="http://schemas.xmlsoap.org/soap/encoding/"xmlns:x="urn:BasicTradingService" qname="x:StockTrade"javaType="javasoap.book.ch5.StockTrade_ServerSide"java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/></isd:mappings></isd:service>This is important, so let's summarize the mapping before we go any further. The mapping of a custom type to a Java class is associated with an encoding style as well as a fully qualified type name. A Java class that implements the custom type is specified, as well as the utility classes used to perform serialization and deserialization. Now let's look at the details of the example.
Most of the deployment descriptor should look familiar; it's similar to the ones shown in Chapter 4. The difference is that we now have an entry in the
isd:mappingssection. There is one entry,isd:map, that describes the mapping of the stock trade type to theStockTrade_ServerSideclass.Theisd:mapelement has no data; all the information is supplied as attributes. The first attribute,encodingStyle, specifies the encoding style associated with the serialization of the type. Next, a namespace identifier,x, is assigned the valueurn:BasicTradingService. I'm using the name of the service as the namespace qualifier for the custom type; however, you don't have to do this, and in fact may not want to in many instances. For example, if you have custom types that are used by multiple services, or if you're using custom types whose definitions are specified by a third party, then you'd certainly want to qualify the custom type using the appropriate namespace. The next attribute,qname, specifies the fully qualified name of the type being mapped. The value assigned isx:StockTrade, which is the sameStockTradenamespace qualified using the service name. ThejavaTypeattribute specifies the server-local Java class used to implement the type; on the server side we're usingjavasoap.book.ch5.StockTrade_ServerSide. The last two attributes,java2XMLClassNameandxml2JavaClassName, tell Apache SOAP which local Java classes to use to perform the serialization and deserialization, respectively. Apache SOAP comes with a custom serializer/deserializer class that can convert between custom XML types and Java classes that conform to the JavaBeans property accessor pattern. It can handle both serialization and deserialization, which is why we use it for both attributes. In the next chapter we'll look at creating custom type serializers.Now that we have a deployment descriptor, we can go ahead and redeploy the
urn:BasicTradingService. Once we do that, our service is ready to acceptexecuteStockTrademethod invocations. However, we need to do some setup work in the client application as well. Let's take a look at the client 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 StockTradeClient{public static void main(String[] args) throws Exception{URL url = newURL("http://georgetown:8080/soap/servlet/rpcrouter");Call call = new Call( );SOAPMappingRegistry smr = new SOAPMappingRegistry( );call.setSOAPMappingRegistry(smr);call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);call.setTargetObjectURI("urn:BasicTradingService");call.setMethodName("executeStockTrade");BeanSerializer beanSer = new BeanSerializer( );// Map the Stock Trade typesmr.mapTypes(Constants.NS_URI_SOAP_ENC,new QName("urn:BasicTradingService", "StockTrade"),StockTrade_ClientSide.class, beanSer, beanSer);// create an instance of the stock tradeStockTrade_ClientSide trade =new StockTrade_ClientSide("XYZ", false, 350);Vector params = new Vector( );params.addElement(new Parameter("trade",StockTrade_ClientSide.class, trade, null));call.setParams(params);Response resp;try {resp = call.invoke(url, "");Parameter ret = resp.getReturnValue( );Object desc = ret.getValue( );System.out.println("Trade Description: " + desc);}catch (SOAPException e) {System.err.println("Caught SOAPException (" +e.getFaultCode( ) + "): " +e.getMessage( ));}}}
After theCallobject is created, we create an instance oforg.apache.soap.encoding.SOAPMappingRegistrycalledsmr.This class holds the mappings of custom types to Java classes, and gets passed to theCallobject using thesetSOAPMappingRegistry( )method of ourCallobject. We haven't had to do this in previous examples because theCallobject creates a mapping registry for itself if one isn't passed to it. TheSOAPMappingRegistry( )contains all of the predefined mappings, such as those we took advantage of for arrays. We'll add our mapping to it shortly.
Next we call thecall.setEncodingStyleURI( )method. This method specifies the overall encoding style to be used for the custom type parameters.The constantNS_URI_SOAP_ENC, from theorg.apache.soap.Constantsclass, represents thehttp://schemas.xmlsoap.org/soap/encoding/namespace that we've been using thus far. This should be the same encoding style namespace that we specified in the deployment descriptor for theStockTradetype.ThesetTargetObjectURI( )andsetMethodName( )methods are used in the same way as in previous examples.The next step is to create an instance oforg.apache.soap.encoding.soapenc.BeanSerializer; we can use this standard serializer because our custom type conforms to the JavaBeans property accessor pattern.Now we can establish the mapping by callingsmr.mapTypes( ). The first parameter isConstants.NS_URI_SOAP_ENC, which specifies that the encoding style used for this mapping is the standard SOAP encoding. You may be wondering why we need to do this if we've already specified the encoding style for the entire call earlier. The reason is simple: this parameter gives you the opportunity to override the encoding style used for this particular mapping. However, if you usenullas the parameter value, you'll end up with a mapping that attempts to use thenullnamespace for the encoding style, which is not correct. When we look at the SOAP envelope for this message, you'll see that since the encoding style is the same as that of the overall call, theencodingStyleattribute will not be repeated for this serialized parameter. If the encoding style were not the same as that of theCall, it would appear as an attribute of the parameter.
The next parameter ofsmr.mapTypes( )is an instance oforg.apache.soap.util.xml.Qname, which represents a fully qualified name (one that includes a namespace qualifier followed by a name). We useurn:BasicTradingServiceas the namespace andStockTradeas the name. The third parameter isStockTrade_ClientSide.class, the Java class that implements the custom type. The next two parameters are instances of the serializer and deserializer that will be used for this type. We use thebeanSerobject that we created earlier for both, as theorg.apache.soap.encoding.soapenc.BeanSerializerclass implements both serialization and deserialization.The rest is pretty simple. We create an instance of
StockTrade_ClientSide, taking advantage of the parameterized constructor to set its property values. Then we set up aVectorof parameters, just as we've done in earlier examples. If you run this example, you should see the following output:Trade Description: Sell 350 of XYZLet's take a look at the SOAP envelope that was transmitted. The relevant part of the envelope begins with the
tradeelement, representing the custom type parameter passed to theexecuteStockTradeservice method. The value assigned toxsi:typeisns1:StockTrade. Thens1namespace identifier is declared to beurn:BasicTradingServicein the parentexecuteStockTradeelement. AndStockTradeis the name we specified for our custom type. There are three child elements of thetradeelement, each one corresponding to the properties of theStockTradecustom type. The name of each element corresponds exactly to the property name. This is crucial, as Apache SOAP is going to use Java reflection to find the set methods of the associated Java class. Therefore, the names in the envelope must match the names used by the class. Each one of these property elements is explicitly typed, and those types have to coincide with the types of the corresponding properties as well.<SOAP-ENV:Envelopexmlns: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:executeStockTrade xmlns:ns1="urn:BasicTradingService"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><trade xsi:type="ns1:StockTrade"><numShares xsi:type="xsd:int">350</numShares><buy xsi:type="xsd:boolean">false</buy><symbol xsi:type="xsd:string">XYZ</symbol></trade></ns1:executeStockTrade></SOAP-ENV:Body></SOAP-ENV:Envelope>Now let's see how custom types are handled in GLUE, using the
BasicTradingServiceclass without modification.We need to add theexecuteStockTrade( )method to theIBasicTradingServiceinterface. We can deploy the service as we did before. Here's the new version ofIBasicTradingService:package javasoap.book.ch5;public interface IBasicTradingService {int getTotalVolume(String[] symbols);String executeTrade(Object[] params);String[] getMostActive( );String executeStockTrade(StockTrade_ClientSide trade);}Writing applications that access services has been easy using the GLUE API because we haven't dealt directly with the SOAP constructs. We should be able to follow that same pattern here, but there's a problem.
TheexecuteStockTrade( )method of theIBasicTradingInterfaceinterface says that the parameter is an instance ofStockTrade_ClientSide. But theexecuteStockTrademethod of theBasicTradingServiceclass that implements the service uses a parameter ofStockTrade_ServerSide. So there's a mismatch, albeit by design. By default, GLUE looks for the same class on the client and server sides. If we had used theStockTrade_ServerSideclass in our client application instead of theStockTrade_ClientSideclass, all would work perfectly. We're not going to do that, so just take my word for it that it works (or better yet, try it for yourself). We'll have to take another approach.Whenever I develop distributed systems, I'm happy to share Java interfaces between both client and server code bases. However, I don't like to share Java classes, especially if those classes contain code that is specific to either the client or the server. In this example, we could rework the design of our stock trade classes to come up with something that contains the relevant data without any of the functionality of either the client or the server. We would want both the client and the server to use the class from the same package, so as the implementer of the basic trading web service, we'd have to distribute the package containing such a class to the developers' client applications. That would work, and it's not uncommon in practice. Getting back to the notion of sharing Java interfaces instead of classes, we could create an interface for the trade data, and have both the server and client classes implement that interface. That's a nice clean way to share between server and client code bases without sharing any actual executable code. Again, this interface would reside in a package available to both server and client systems. This mechanism is expected to be supported in a future version of GLUE (which might already be available by the time you read this).
GLUE does support another mechanism for making this stuff work. This mechanism doesn't require you to modify the server side, and the work involved on the client side is trivial. We're going to create a new package for this client example, because our work will create some files that have the same names as those we created earlier. The new package prevents us from overwriting files when running both client and server on the same machine. So be aware that this example is part of a new package called
javasoap.book.ch5.client, and the files we'll be developing need to be in the corresponding directory for that package.I've purposely avoided discussion of WSDL so far, even though GLUE is based entirely on WSDL. However, the first step in this process makes use of GLUE's
wsdl2javautility, which generates Java code based on a WSDL file. So where does the WSDL come from? The GLUE server generates it automatically. The client-side applications based on the GLUE API have been taking advantage of this all along.wsdl2javagenerates the Java interface for binding to the service, a helper class, a Java data structure class that represents the data fields of the custom type we're working with, and a map file. The map file is essentially a schema definition for the custom type; it tells GLUE how to map the fields of the Java data structure class to the fields of the custom type. GLUE uses a number of mechanisms for handling custom type mapping; in this case, the map file defines the mapping explicitly, rather than basing the mapping on Java reflection.So let's go ahead and generate the files for the client application. Enter the following command, making sure you are in the directory for the
javasoap.book.ch5.clientpackage:wsdl2java http://georgetown:8004/glue/urn:BasicTradingService.wsdl-p javasoap.book.ch5.clientThe parameter to the
wsdl2javautility is the full URL of the service WSDL, just like we've been using in thebind( )methods in previous examples. The-poption tells the utility the name of the local package for which code should be generated:javasoap.book.ch5.client. The output fromwsdl2javaconsists of four files. The first one is IBasicTradingService.java, which contains the Java interface for binding to the service. We've been writing this one by hand up until now; let's take a look at the one generated bywsdl2java:// generated by GLUEpackage javasoap.book.ch5.client;public interface IBasicTradingService{String executeStockTrade( StockTrade_ServerSide arg0 );String[] getMostActive( );int getTotalVolume( String[] arg0 );String executeTrade( Object[] arg0 );}The only differences between this interface definition and the one we wrote ourselves are a different package declaration, the naming of the method parameters, and the
executeStockTradetaking a parameter ofStockTrade_ServerSideinstead ofStockTrade_ClientSide. Remember that the earlier version ofIBasicTradingServicewon't work for us because GLUE defaults to using the same class on both client and server. At first glance, the use of theStockTrade_ServerSideclass here seems to violate our desire to completely decouple the server code from the client code. But remember that the package declaration isjavasoap.book.ch5.client, so this Java interface is not referencing the sameStockTrade_ServerSideclass being used by our server, which belongs to a different package. Let's take a look at theStockTrade_ServerSideclass generated bywsdl2javaand placed in the file named StockTrade_ServerSide.java:// generated by GLUEpackage javasoap.book.ch5.client;public class StockTrade_ServerSide{public int _shares;public boolean _buyOrder;public String _stock;}This is only a shadow of the corresponding class in the
javasoap.book.ch5package that the server side uses. It does, however, properly reflect the data values. We'll be creating an instance of this class in our client application to pass to the service, as this is the type expected by theexecuteStockTrade( )method of theIBasicTradingServiceinterface that was generated.The third file generated by
wsdl2javais BasicTradingService.map, which contains the mapping schema mentioned earlier. You can take a look at the contents of the mapping file on your own; suffice it to say that it contains XML entries that define field mappings between the client-side and server-side Java. The last generated file is BasicTradingServiceHelper.java, which contains a helper class for performing thebind( )operation. I don't use that helper class, so we'll ignore it here.It's taken far longer to describe the process than it takes to perform it. Now let's move on to writing the client application:
package javasoap.book.ch5.client;import electric.registry.RegistryException;import electric.registry.Registry;import electric.xml.io.Mappings;public class StockTradeClient2 {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);StockTrade_ServerSide trade =new StockTrade_ServerSide( );trade._stock = "MINDSTRM";trade._buyOrder = true;trade._shares = 500;String desc = srv.executeStockTrade(trade);System.out.println("Trade Description is: " + desc);}catch (RegistryException e){System.out.println(e);}}}The critical step is to read the mappings before you perform the bind operation. This is done by calling the
readMappings( )method of theelectric.xml.io.Mappingsclass provided as part of GLUE. After thebind( )call, we simply create an instance ofStockTrade_ServerSide, set its field values, and call theexecuteStockTrademethod. The output from running this application should be:Trade Description is: Buy 500 of MINDSTRMNow let's look at the SOAP envelope generated by this application. As we've come to expect, the
executeStockTradeelement is namespace-qualified using the name of the service. The child elementarg0is a reference to a separately serialized element namedid0, just as GLUE generated for an array parameter. This example shows us why GLUE has been generating thens2namespace identifier that seems to reference the package where the implementing server-side class resides. Here that namespace is used to qualify the value of thexsi:typeattribute, with a value corresponding to the name of the implementing classStockTrade_ServerSide. The child elements of theid0element contain the data fields for the custom type.<soap:Envelopexmlns: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:executeStockTrade xmlns:n='urn:BasicTradingService'><arg0 href='#id0'/></n:executeStockTrade><id0 id='id0' soapenc:root='0'xmlns:ns2='http://www.themindelectric.com/package/javasoap.book.ch5/'xsi:type='ns2:StockTrade_ServerSide'><_shares xsi:type='xsd:int'>500</_shares><_buyOrder xsi:type='xsd:boolean'>true</_buyOrder><_stock xsi:type='xsd:string'>MINDSTRM</_stock></id0></soap:Body></soap:Envelope>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_ServerSideandHighLow_ClientSiderepresent 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;}}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 theBasicTradingServiceclass calledgetHighLow( ). That method takes a single string parameter for the stock symbol and returns an instance ofHighLow_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 stockreturn 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
getHighLowto the list of methods, and add an entry in the mappings section for the high/low object. The second mapping entry defines theHighLowcustom type, namespace-qualified using the service nameurn:BasicTradingService. Here is the modified deployment descriptor:<isd:servicexmlns:isd="http://xml.apache.org/xml-soap/deployment"id="urn:BasicTradingService"><isd:providertype="java"scope="Application"methods="getTotalVolume getMostActive executeTrade executeStockTradegetHighLow"><isd:javaclass="javasoap.book.ch5.BasicTradingService"static="false"/></isd:provider><isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener><isd:mappings><isd:mapencodingStyle="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:mapencodingStyle="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
getHighLowservice method and receives a high/low object in return. For the Apache SOAP client, we'll use theHighLow_ClientSideclass 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 = newURL("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 typesmr.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 theHighLowcustom type to theHighLow_ClientSideJava class. Just as before, we can use Apache'sBeanSerializerto convert between XML and Java, since our class conforms to the JavaBeans property accessor pattern. We set up a singleStringparameter calledstockto pass to thegetHighLowmethod (although we don't actually make use of it in the server code). After the method is invoked, we cast the return value ofresp.getReturnValue( )to an instance ofHighLow_ClientSide. Then we pass the return parameter variable,ret, to theSystem.out.println( )method for display. This is all we need, since we implemented thetoString( )method in ourHighLow_ClientSideclass. When you run this example, you'll get the following output:High: 110.375 Low: 109.5Here's the SOAP envelope returned from this method invocation. The
returnelement is typed as aHighLowthat is namespace-qualified by theurnBasicTradingServicenamespace. The properties, which are child elements of thereturnelement, are typed asfloats and appear along with their corresponding values.<SOAP-ENV:Envelopexmlns: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:getHighLowResponsexmlns: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
HighLowusing 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 ofBasicTradingAppto get the service deployed. Now run thewsdl2javautility from the directory corresponding to thejavasoap.book.ch5.clientpackage:wsdl2java http://georgetown:8004/glue/urn:BasicTradingService.wsdl-p javasoap.book.ch5.client
The following code is the newIBasicTradingServiceinterface generated bywsdl2java. ThegetHighLow( )method returns an instance ofHighLow_ServerSide; remember that this class is the new one generated by GLUE as part of thejavasoap.book.ch5.clientpackage, not the one being used by our server class,BasicTradingApp.// generated by GLUEpackage 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 theHighLowcustom type. I added thetoString( )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
getHighLowservice 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 thegetHighLowmethod on the bound interface. The resulting instance ofjavasoap.book.ch5.client.HighLow_ServerSideis passed toSystem.out.println( )for display. Just as in the Apache SOAP example, thetoString( )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:Envelopexmlns: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
VectorandHashtableclasses, 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.
1. There are certainly other ways to design the interface to a method like this, and this is not the design I'd choose. This situation probably calls for using a custom class or Java bean. However, the approach I've used in this example demonstrates the use of heterogeneous arrays.
2. In Chapter 3, we talked about using
ur-typeto represent any possible data type. Here we see the use ofanyTypefor that purpose. The XML Schema Part 0 recommendation, dated May 2, 2001, explains this in section 2.5.4 as follows: "The anyType represents an abstraction called theur-typewhich is the base type from which all simple and complex types are derived. AnanyTypetype does not constrain its content in any way."3. JavaBeans provides for other ways to accomplish this, but those are not within the scope of this book.
Back to: Java and SOAP
© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com