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 3

Related Reading

Java and SOAP
By Robert Englander

by Robert Englander

Editor's Note: This is the third in a series of excerpts from "Chapter 5: Working with Complex Data Types" of Java and SOAP. This excerpt covers passing custom types as parameters.


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:

public void set<PropertyName>(<PropertyType> value);
public <PropertyType> get<PropertyName>(  );
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 boolean is<PropertyName>(  );

In This Series

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

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.

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 boolean indicator 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 class StockTrade, 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_ClientSide has three read/write properties named Symbol, Buy, and NumShares that 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 a StockTrade_ServerSide class 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 the urn:BasicTradingService service called executeStockTrade( ), which takes a stock trade as a parameter. The return value from this method is a string that describes the order. Here's the modified BasicTradingService class. We can take advantage of the executeTrade( ) method that already exists in this class. In the new method, executeStockTrade( ), we build an Object array from the three properties of the trade parameter, and we pass that array to the executeTrade( ) 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 stocks
      String[] 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 total
      
      int 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 the isd:mappings section. Here's the deployment descriptor we'll use to deploy the service in Apache SOAP:


<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">
    <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.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:mappings section. There is one entry, isd:map, that describes the mapping of the stock trade type to the StockTrade_ServerSide class. The isd:map element 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 value urn: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 is x:StockTrade, which is the same StockTrade namespace qualified using the service name. The javaType attribute specifies the server-local Java class used to implement the type; on the server side we're using javasoap.book.ch5.StockTrade_ServerSide. The last two attributes, java2XMLClassName and xml2JavaClassName, 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 accept executeStockTrade method 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 = new  
       URL("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 type
    smr.mapTypes(Constants.NS_URI_SOAP_ENC,
       new QName("urn:BasicTradingService", "StockTrade"),
       StockTrade_ClientSide.class, beanSer, beanSer);
    // create an instance of the stock trade
    StockTrade_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 the Call object is created, we create an instance of org.apache.soap.encoding.SOAPMappingRegistry called smr. This class holds the mappings of custom types to Java classes, and gets passed to the Call object using the setSOAPMappingRegistry( ) method of our Call object. We haven't had to do this in previous examples because the Call object creates a mapping registry for itself if one isn't passed to it. The SOAPMappingRegistry( ) 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 the call.setEncodingStyleURI( ) method. This method specifies the overall encoding style to be used for the custom type parameters. The constant NS_URI_SOAP_ENC, from the org.apache.soap.Constants class, represents the http://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 the StockTrade type. The setTargetObjectURI( ) and setMethodName( ) methods are used in the same way as in previous examples. The next step is to create an instance of org.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 calling smr.mapTypes( ). The first parameter is Constants.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 use null as the parameter value, you'll end up with a mapping that attempts to use the null namespace 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, the encodingStyle attribute will not be repeated for this serialized parameter. If the encoding style were not the same as that of the Call, it would appear as an attribute of the parameter.

The next parameter of smr.mapTypes( ) is an instance of org.apache.soap.util.xml.Qname, which represents a fully qualified name (one that includes a namespace qualifier followed by a name). We use urn:BasicTradingService as the namespace and StockTrade as the name. The third parameter is StockTrade_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 the beanSer object that we created earlier for both, as the org.apache.soap.encoding.soapenc.BeanSerializer class 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 a Vector of 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 XYZ

Let's take a look at the SOAP envelope that was transmitted. The relevant part of the envelope begins with the trade element, representing the custom type parameter passed to the executeStockTrade service method. The value assigned to xsi:type is ns1:StockTrade. The ns1 namespace identifier is declared to be urn:BasicTradingService in the parent executeStockTrade element. And StockTrade is the name we specified for our custom type. There are three child elements of the trade element, each one corresponding to the properties of the StockTrade custom 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: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: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 BasicTradingService class without modification. We need to add the executeStockTrade( ) method to the IBasicTradingService interface. We can deploy the service as we did before. Here's the new version of IBasicTradingService:


package javasoap.book.ch5;
public interface IBasicTradingService {
  int getTotalVolume(String[] symbols);
  String executeTrade(Object[] params);
  String[] getMostActive(  );
  String executeStockTrade(StockTrade_ClientSide trade);
}

Related Reading

Java and SOAP
By Robert Englander

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. The executeStockTrade( ) method of the IBasicTradingInterface interface says that the parameter is an instance of StockTrade_ClientSide. But the executeStockTrade method of the BasicTradingService class that implements the service uses a parameter of StockTrade_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 the StockTrade_ServerSide class in our client application instead of the StockTrade_ClientSide class, 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 wsdl2java utility, 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. wsdl2java generates 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.client package:


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

The parameter to the wsdl2java utility is the full URL of the service WSDL, just like we've been using in the bind( ) methods in previous examples. The -p option tells the utility the name of the local package for which code should be generated: javasoap.book.ch5.client. The output from wsdl2java consists 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 by wsdl2java:


// generated by GLUE
package 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 executeStockTrade taking a parameter of StockTrade_ServerSide instead of StockTrade_ClientSide. Remember that the earlier version of IBasicTradingService won't work for us because GLUE defaults to using the same class on both client and server. At first glance, the use of the StockTrade_ServerSide class here seems to violate our desire to completely decouple the server code from the client code. But remember that the package declaration is javasoap.book.ch5.client, so this Java interface is not referencing the same StockTrade_ServerSide class being used by our server, which belongs to a different package. Let's take a look at the StockTrade_ServerSide class generated by wsdl2java and placed in the file named StockTrade_ServerSide.java:

// generated by GLUE
package 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.ch5 package 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 the executeStockTrade( ) method of the IBasicTradingService interface that was generated.

The third file generated by wsdl2java is 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 the bind( ) 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);
    }
  }
}

In This Series

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

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 critical step is to read the mappings before you perform the bind operation. This is done by calling the readMappings( ) method of the electric.xml.io.Mappings class provided as part of GLUE. After the bind( ) call, we simply create an instance of StockTrade_ServerSide, set its field values, and call the executeStockTrade method. The output from running this application should be:

Trade Description is: Buy 500 of MINDSTRM

Now let's look at the SOAP envelope generated by this application. As we've come to expect, the executeStockTrade element is namespace-qualified using the name of the service. The child element arg0 is a reference to a separately serialized element named id0, just as GLUE generated for an array parameter. This example shows us why GLUE has been generating the ns2 namespace identifier that seems to reference the package where the implementing server-side class resides. Here that namespace is used to qualify the value of the xsi:type attribute, with a value corresponding to the name of the implementing class StockTrade_ServerSide. The child elements of the id0 element contain the data fields for the custom type.

<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: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>

In the next and final installment, learn about returning custom types.

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.

Java and SOAP

Related Reading

Java and SOAP
By Robert Englander

Copyright © 2009 O'Reilly Media, Inc.