Part 2 - A Detailed Look at SOAP

In Part One of the hands-on Web services tutorial we introduced the basic concepts behind web services, including SOAP and WSDL. We showed how to develop a simple Web Service in under 30 minutes, and in the process explained SOAP messaging, how to implement a Java Web service client, and how a WSDL is constructed. In this article we'll check out some more advanced topics including SOAP complex type handling, error processing and remote references.

In Part One of the hands-on web services tutorial we introduced the basic concepts behind web services, including SOAP and WSDL. We showed how to develop a simple Web Service in under 30 minutes, and in the process explained SOAP messaging, how to implement a Java web service client, and how a WSDL is constructed. In this article we'll check out some more advanced topics including SOAP complex type handling, error processing and remote references.

NOTE: If you haven't already downloaded the software used to create the tutorial examples, then please refer to the installation chapter in Part One. You'll also need to download the demo sources. We'll assume that you unpacked this archive into the c:wasp_demo directory. All Java sources mentioned in the tutorial examples can be found in the src subdirectory of the unpacked demo sources archive. They all reside in the com.systinet.demos package. You don't need to download and use the software to understand these articles, but we strongly recommend it. The concepts we introduce and the code we create are generally applicable and relatively independent of the tools used. We assume some knowledge of XML but none of web services. Unless you have some experience with SOAP and WSDL, we recommend you first read Part One of this series.

Modern web service development in Java

Looking for a more modern approach to web service development? Here are some of the latest REST tutorials and SOAP web services examples that we’ve published on TheServerSide. 

I highly recommend these articles as a compliment to this slightly older tutorial. In fact, reading through these articles on modern SOAP and RESTful web service development, it's amazing to see just how far software development in Java has come.

SOAP and complex types

So far, our web services exchanged only primitive data types like strings, int and doubles. Now we will see how complex types are translated to the SOAP message.

The SOAP protocol recommends usage of so-called SOAP encoding for translation of complex programming language types to XML. Usually, the following translations occur automatically:

  • Java 2 primitive types.
  • Custom classes, which are mapped using the well-known JavaBeans pattern. All public fields and fields with public getters/setters are translated to XML by Java reflection serializer.
  • Java 2 collections (HashMap, Hashtable, Vector, List etc.).

The following simple demonstration shows the default JavaBean pattern serialization and Java 2 collection serialization.

We'll pass a simple OrderRequest structure to the web service. The OrderRequest structure is implemented as the simplest possible JavaBean, with getters and setters for symbol,limitPrice and volume. The service processOrder method accepts the OrderRequest Java class as the only parameter. We'll show how the OrderRequest structure is represented in the SOAP message. We'll also code the getOrders method that returns the collection of all accepted orders from the web service to the client. We'll use java.util.Hashtable container as a return value from the getOrders method and look at its XML representation.

In this example, we continue our quest to make serious money on the stock market with a simple ordering service:

package com.systinet.demos.mapping;

public class OrderService {

    private java.util.HashMap orders = new java.util.HashMap();

    public String processOrder(OrderRequest order) {
        String result = "PROCESSING ORDER";

        Long id = new Long(System.currentTimeMillis());

        result       += "n----------------------------";
        result       += "nID:             "+id;
        result       += "nTYPE:           "+
((order.getType()==order.ORDER_TYPE_SELL)?("SELL"):("BUY"));
        result       += "nSYMBOL:         "+order.getSymbol();
        result       += "nLIMIT PRICE:    "+order.getLimitPrice();
        result       += "nVOLUME:         "+order.getVolume();

        this.orders.put(id,order);

        return result;
    }

    public java.util.HashMap getOrders() {
     return this.orders;
    }

}

Figure 1: Complex types handling example (OrderService.java)

NOTE: You'll find all scripts in the bin subdirectory of unpacked demo sources archive.

We'll compile and deploy the ordering web service by running the deployMapping.bat script. The client-side application simply creates two order requests and sends them to the web service. It then retrieves the Hashtable populated with these two requests and displays them on the console. Lets look at the client application code, where once again we are speculating in technology stocks:

package com.systinet.demos.mapping;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;

public final class TradingClient {

    public static void main( String[] args ) throws Exception {

      WebServiceLookup lookup = (WebServiceLookup)Context.getInstance(Context.WEBSERVICE_LOOKUP);
      OrderServiceProxy service =
(OrderServiceProxy)lookup.lookup("http://localhost:6060/MappingService/",OrderServiceProxy.class);

      com.systinet.demos.mapping.struct.OrderRequest order = new com.systinet.demos.mapping.struct.OrderRequest();
      order.symbol = "SUNW";
      order.type = com.systinet.demos.mapping.OrderRequest.ORDER_TYPE_BUY;
      order.limitPrice = 10;
      order.volume = 100000;
      String result = service.processOrder(order);

      System.out.println(result);

      order = new com.systinet.demos.mapping.struct.OrderRequest();
      order.symbol = "BEAS";
      order.type = com.systinet.demos.mapping.OrderRequest.ORDER_TYPE_BUY;
      order.limitPrice = 13;
      order.volume = 213000;
      result = service.processOrder(order);

      System.out.println(result);

      java.util.HashMap orders = service.getOrders();

      java.util.Iterator iter = orders.keySet().iterator();

      while(iter.hasNext()) {
       Long id = (Long)iter.next();
       OrderRequest req = (OrderRequest)orders.get(id);
       System.out.println("n----------------------------");
        System.out.println("nID:             "+id);
        System.out.println("nTYPE:           "+
((req.getType()==com.systinet.demos.mapping.OrderRequest.ORDER_TYPE_SELL)?("SELL"):("BUY")));
        System.out.println("nSYMBOL:         "+req.getSymbol());
        System.out.println("nLIMIT PRICE:    "+req.getLimitPrice());
        System.out.println("nVOLUME:         "+req.getVolume());
      }

    }

}



Figure 2: Ordering client source code (TradingClient.java)

Complex type mapping - deeper insight

The first thing to uncover is the Web Services Description Language (WSDL) file that was generated at the time we deployed. If you deployed the mapping service, you can view the complete WSDL file at http://localhost:6060/MappingService/.

You'll remember from Part One of our tutorial that the WSDL describes what functionality a web service offers, how it communicates, and where it is accessible. WSDL provides a structured mechanism to describe the operations a web service can perform, the formats of the messages that it can process, the protocols that it supports, and the access point of an instance of the web service. In our example, the most important part to note is the definition of the OrderRequest Java type mapping:

<xsd:complexType name="OrderRequest">
  <xsd:sequence>
    <xsd:element name="limitPrice" type="xsd:double"/>
    <xsd:element name="symbol" type="xsd:string"/>
    <xsd:element name="type" type="xsd:short"/>
    <xsd:element name="volume" type="xsd:long"/>
  </xsd:sequence>
</xsd:complexType>

The mapping of the OrderRequest to the XML is defined as a set of primitive type fields. The HashMap returned from the getOrders mapping is imported as http://idoox.com/containers:HashMap datatype. Our WSDL file imports the following definition:

<complexType name="HashMap">
  <sequence>
    <element name="item" minOccurs="0" maxOccurs="unbounded">
      <complexType>
        <sequence>
          <element name="key" type="anyType" />
          <element name="value" type="anyType" />
        </sequence>
      </complexType>
    </element>
  </sequence>
</complexType>

Lets now look at the SOAP messages exchanged between the SOAP client and the web service. We'll first start the SOAP message tracing on the SOAP server by opening the Administration console in the HTTP browser, clicking the Refresh button and clicking on the enable link in the MappingService section of the console. Next, we'll run the client application by invoking the runMappingClient.bat script and look at the SOAP messages. The message below is an invocation of the processOrder method with an instance of OrderRequest as a parameter:

<?xml version="1.0" encoding="UTF-8"?>
  <ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
    <ns0:Body
      ns0:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
      xmlns:xsd="https://www.w3.org/2001/XMLSchema"
      xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
      xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
      <ns0:processOrder xmlns:ns0=
"http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/mapping/OrderService">
        <p0 xsi:type=
"ns1:OrderRequest" xmlns:ns1="http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/mapping/">
          <limitPrice xsi:type="xsd:double">10.0</limitPrice>
          <symbol xsi:type="xsd:string">SUNW</symbol>
          <type xsi:type="xsd:short">1</type>
          <volume xsi:type="xsd:long">100000</volume>
        </p0>
      </ns0:processOrder>
    </ns0:Body>
  </ns0:Envelope>

The following message represents the return value (HashMap populated with processed order requests) from the getOrders method:

<?xml version="1.0" encoding="UTF-8"?>
  <ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
    <ns0:Body
      ns0:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
      xmlns:xsd="https://www.w3.org/2001/XMLSchema"
      xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
      xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
        <ns0:getOrdersResponse xmlns:ns0=
"http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/mapping/OrderService">
          <response xsi:type="ns1:HashMap" xmlns:ns1="http://idoox.com/containers">
            <item>
              <key xsi:type="xsd:long">1006209071080</key>
              <value xsi:type=
"ns2:com.systinet.demos.mapping.OrderRequest" xmlns:ns2="http://idoox.com/package/">
                <volume xsi:type="xsd:long">100000</volume>
                <symbol xsi:type="xsd:string">SUNW</symbol>
                <limitPrice xsi:type="xsd:double">10.0</limitPrice>
                <type xsi:type="xsd:short">1</type>
              </value>
            </item>
          <item>
        <key xsi:type="xsd:long">1006209071130</key>
        <value xsi:type="ns3:com.systinet.demos.mapping.OrderRequest" xmlns:ns3="http://idoox.com/package/">
          <volume xsi:type="xsd:long">213000</volume>
          <symbol xsi:type="xsd:string">BEAS</symbol>
          <limitPrice xsi:type="xsd:double">13.0</limitPrice>
          <type xsi:type="xsd:short">1</type>
          </value>
      </item></response>
    </ns0:getOrdersResponse></ns0:Body>
  </ns0:Envelope>

The mapping from Java to XML is straightforward. We can see the outer HashMap definition with key amd value elements. Notice that there is an inner XML definition of the OrderRequest data type.

In the final step we will undeploy the web service from the server by running the undeployMapping.bat script.

SOAP Error Processing

When things go wrong SOAP defines a so-called SOAP Fault XML construct that represents an error that occured on the server-side. We briefly introduced these error messages in Part One of our tutorial. Here we'll examine them in more detail. The SOAP Fault contains three basic elements:

  • FAULTCODE that contains an error code or ID.
  • FAULTSTRING that carries the short description of the error.
  • DETAIL that describes the error in some detail.

To illustrate error processing we'll add some exceptions to our stock quote example. First, we'll set the getQuote method to throw StockNotFoundException if it can't find a specified one of our three stock picks:

package com.systinet.demos.fault;

public class StockQuoteService {


    public double getQuote(String symbol) throws StockNotFoundException {
        if(symbol!=null && symbol.equalsIgnoreCase("SUNW"))
            return 10;
        if(symbol!=null && symbol.equalsIgnoreCase("MSFT"))
            return 50;
        if(symbol!=null && symbol.equalsIgnoreCase("BEAS"))
            return 11;
        throw new StockNotFoundException("Stock symbol "+symbol+" not found.");
    }

    public java.util.LinkedList getAvailableStocks() {
        java.util.LinkedList list = new java.util.LinkedList();
        list.add("SUNW");
        list.add("MSFT");
        list.add("BEAS");
        return list;
    }

}


Figure 3: SOAP web service Java source (StockQuoteService.java)

Next, we will deploy the web service using the deployFault.bat commandline script.

We will use the SOAP message tracing on the SOAP server by opening the Administration console in the HTTP browser, clicking the Refresh button and clicking on the enable link in the StockQuoteService section of the console.

Now we will check out the WSDL file generated by the SOAP server at deployment. Open the http://localhost:6060/StockQuoteService/ URL in a browser.

Notice the SOAP fault message definition in the WSDL:

<wsdl:
message name='StockQuoteService_getQuote_com.systinet.demos.fault.StockNotFoundException_Fault'>
  <wsdl:part name='idoox-java-mapping.com.systinet.demos.fault.StockNotFoundException' type='xsd:string'/>
</wsdl:message>

The fault message is referenced in the getQuote operation in the WSDL port type element:

<wsdl:operation name='getQuote' parameterOrder='p0'>
  <wsdl:input name='getQuote' message='tns:StockQuoteService_getQuote_Request'/>
  <wsdl:output name='getQuote' message='tns:StockQuoteService_getQuote_Response'/>
  <wsdl:fault name='getQuote_fault1'
message='tns:StockQuoteService_getQuote_com.systinet.demos.fault.StockNotFoundException_Fault'/>
</wsdl:operation>

and in the binding element:

<wsdl:operation name='getQuote'>
  <soap:operation soapAction='' style='rpc'/>
  <wsdl:input name='getQuote'>
    <soap:body use='encoded' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'
namespace='http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/fault/'/>
  </wsdl:input>
  <wsdl:output name='getQuote'>
    <soap:body use='encoded' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'
namespace='http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/fault/'/>
  </wsdl:output>
  <wsdl:fault name='getQuote_fault1'>
    <soap:fault name='getQuote_fault1' use='encoded' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'
namespace='http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/fault/'/>
  </wsdl:fault>
</wsdl:operation>

Summarized, the server-side Java exception is represented with a simple SOAP message that is returned to the client side if the exception occurs.

The next step is to create a simple web service client application:

package com.systinet.demos.fault;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;


public final class StockClient {

    public static void main( String[] args ) throws Exception {

      // lookup service
      WebServiceLookup lookup = (WebServiceLookup)Context.getInstance(Context.WEBSERVICE_LOOKUP);
      // bind to StockQuoteService
      StockQuoteServiceProxy quoteService = (StockQuoteServiceProxy)lookup.lookup(
        "http://localhost:6060/StockQuoteService/",
        StockQuoteServiceProxy.class
      );


      // use StockQuoteService
      System.out.println("Getting available stocks");
      System.out.println("------------------------");
      java.util.LinkedList list = quoteService.getAvailableStocks();
      java.util.Iterator iter = list.iterator();
      while(iter.hasNext()) {
         System.out.println(iter.next());
      }
      System.out.println("");

      System.out.println("Getting SUNW quote");
      System.out.println("------------------------");
      System.out.println("SUNW "+quoteService.getQuote("SUNW"));
      System.out.println("");

      System.out.println("Getting IBM quote (warning, this one doesn't exist, so we will get an exception)");
      System.out.println("------------------------");
      System.out.println("SUNW "+quoteService.getQuote("IBM"));
      System.out.println("");


    }

}

Figure 4: SOAP client Java source (StockClient.java)

We'll need to generate the client Java interface, compile all Java classes and run the client application. All these tasks are carried out in the runFaultClient.bat

Our existing stock portfolio is rather sparse, and doesn't include IBM. The client should first show all available symbols, retrieve the stock quote of the SUNW symbol and then finally throw the StockNotFound exception complaining that 'Stock symbol IBM not found'. Now lets look at the Administration console and click on the show SOAP conversation link. In a new window the following message is displayed (important message parts are highlighted):

==== INPUT ==== http://localhost:6060/StockQuoteService/ ==== 11/14/01 4:44 PM =
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
    <ns0:Body 
        ns0:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
        xmlns:xsd="https://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" 
        xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
        <ns0:getQuote xmlns:ns0="http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/fault/">
            <p0 xsi:type="xsd:string">IBM</p0>
        </ns0:getQuote>

      </ns0:Body>
</ns0:Envelope>
==== CLOSE ===================================================================== 

==== OUTPUT ==== http://localhost:6060/StockQuoteService/ ======================
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
    <ns0:Body 
        ns0:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
        xmlns:xsd="https://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" 
        xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
        <ns0:Fault xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">
            <faultcode>ns0:Server</faultcode>
            <faultstring>Stock symbol IBM not found.</faultstring>
            <detail xmlns:ijm="urn:idoox-java-mapping">
                <ijm:idoox-java-mapping.com.systinet.demos.fault.StockNotFoundException>
                    <ijm:stack-trace>
                        com.systinet.demos.fault.StockNotFoundException: Stock symbol IBM not found.
                        at com.systinet.demos.fault.StockQuoteService.getQuote(StockQuoteService.java:24)
                        at java.lang.reflect.Method.invoke(Native Method)
                        at com.idoox.wasp.server.adaptor.JavaAdaptorInvoker.invokeService(JavaAdaptorInvoker.java:387)
                        at com.idoox.wasp.server.adaptor.JavaAdaptorInvoker.invoke(JavaAdaptorInvoker.java:239)
                        at com.idoox.wasp.server.adaptor.JavaAdaptorImpl.dispatch(JavaAdaptorImpl.java:164)
                        at com.idoox.wasp.server.AdaptorTemplate.dispatch(AdaptorTemplate.java:178)
                        at com.idoox.wasp.server.ServiceConnector.dispatch(ServiceConnector.java:217)
                        at com.idoox.wasp.server.ServiceManager.dispatch(ServiceManager.java:231)
                        at com.idoox.wasp.server.ServiceManager$DispatcherConnHandler.handlePost(ServiceManager.java:1359)
                        at com.idoox.transport.http.server.Jetty$WaspHttpHandler.handle(Jetty.java:94)
                        at com.mortbay.HTTP.HandlerContext.handle(HandlerContext.java:1087)
                        at com.mortbay.HTTP.HttpServer.service(HttpServer.java:675)
                        at com.mortbay.HTTP.HttpConnection.service(HttpConnection.java:457)
                        at com.mortbay.HTTP.HttpConnection.handle(HttpConnection.java:317)
                        at com.mortbay.HTTP.SocketListener.handleConnection(SocketListener.java:99)
                        at com.mortbay.Util.ThreadedServer.handle(ThreadedServer.java:254)
                        at com.mortbay.Util.ThreadPool$PoolThreadRunnable.run(ThreadPool.java:601)
                        at java.lang.Thread.run(Thread.java:484)
                    </ijm:stack-trace>
                </ijm:idoox-java-mapping.com.systinet.demos.fault.StockNotFoundException>
            </detail>
        </ns0:Fault>

    </ns0:Body>
</ns0:Envelope>
==== CLOSE =====================================================================

Notice the output SOAP message and particularly the FAULT construct. The FAULTCODE contains the generic error code, the FAULTSTRING element holds the exception message and DETAIL element carries the full stack trace of the exception. All SOAP fault messages follow this basic format. Hopefully, you won't encounter too many future examples.

Finally, we can undeploy the service using undeployFault.bat script.

Remote references

Remote references are a construct used in most distributed object systems, such as RMI, CORBA, and DCOM. Assume that you have a client that is invoking methods on a server object. Here's how they work. Let's say that the server object creates another object, and it has to somehow pass the new object back to the client (see Figure 8 below). It can pass the new object either by "value" or by "reference". If you pass the object by value, you are passing the entire object. If you pass by reference, you are just passing a pointer to the object. A remote reference is a reference that works across a network. Remote references are critical to many distributed computing design patterns, particularly the Factory pattern. Even though this feature is critical for many distributed computing applications, not all SOAP implementations support it.

For example, we may define a createLineItem method on an Order Web Service. This method will create a new LineItem object that holds the catalog number and price of the ordered product and ordered quantity. The Order potentially references many of the LineItem objects. The LineItem object should be returned to the client application as the remote reference in order to further specify all necessary data.

Figure 5: Remote references



Implementing simple remote references

To illustrate the remote references feature we're going to create a new example, and spend our gains from the stock market by ordering some products. We'll start with definitions for two interfaces: Both Order and LineItem Java interfaces will be used for referencing the web service on the client-side:

package com.systinet.demos.interref;

public interface LineItem extends java.rmi.Remote {

   public String getID();

   public long getCount();

   public String getProductID();

   public void close();

}

Figure 6: LineItem interface



package com.systinet.demos.interref;

public interface Order {

   public LineItem addItem(String productID, long count);

   public LineItem getItem(String id);

   public void removeItem(String id);

}


Figure 7: Order interface

Notice that the LineItem interface extends the java.rmi.Remote interface. This is the simplest method for handling remote references using WASP. Otherwise, the LineItem interface is fairly obvious. The addItem method in the Order interface creates and returns a new order item. The getItem returns an existing item and removeItem removes the specified item from the order.

Now we will implement both LineItem and Order interfaces:

package com.systinet.demos.interref;

import org.idoox.webservice.server.WebServiceContext;
import org.idoox.webservice.server.LifeCycleService;

public class LineItemImpl implements LineItem {

    private String pid;
    private String id;
    private long count;


    public LineItemImpl(String pid, long count) {
        System.err.println("Creating new LineItem.");
        this.id = pid+System.currentTimeMillis();
        this.pid = pid;
        this.count = count;
    }

    public void close() {
        System.err.println("close()");
        WebServiceContext context = WebServiceContext.getInstance();
        LifeCycleService lc = context.getLifeCycleService();
        lc.disposeServiceInstance(this);
    }

    public long getCount() {
        System.err.println("getCount()");
        return this.count;
    }

    public String getProductID() {
        System.err.println("getProductID()");
        return this.pid;
    }

    public String getID() {
        System.err.println("getID()");
        return this.id;
    }

}



Figure 8: LineItem implementation

package com.systinet.demos.interref;

public class OrderImpl implements Order {

    private java.util.HashMap items = new java.util.HashMap();

    public LineItem getItem(String id) {
        return (LineItem)this.items.get(id);
    }

    public LineItem addItem(java.lang.String pid, long count) {
        LineItem item = new LineItemImpl(pid, count);
        this.items.put(item.getID(), item);
        return item;
    }

    public void removeItem(java.lang.String id) {
        LineItem item = (LineItem)this.items.remove(id);
        item.close();
    }


}


Figure 9: Order implementation

We will use deployInterref.bat script for deploying the web service.

Standard implementation, no surprises. Notice that the LineItem prints method call tracing messages. The client code is also pretty standard:

package com.systinet.demos.interref;

import javax.wsdl.QName;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;

public final class OrderClient {

    public static void main( String[] args ) throws Exception {

      // lookup service
      WebServiceLookup lookup = (WebServiceLookup)Context.getInstance(Context.WEBSERVICE_LOOKUP);

      Order order = (Order)lookup.lookup("http://localhost:6060/OrderService/",
                new QName("http://idoox.com/wasp/tools/java2wsdl/output/com/systinet/demos/interref/", "OrderService"),
                "OrderImpl", Order.class);

      String id1 = order.addItem("THNKPDT23", 2).getID();
      String id2 = order.addItem("THNKPDT22", 2).getID();

      System.out.println("ID1 "+id1);
      System.out.println("ID2 "+id2);

      LineItem item = order.getItem(id1);

      System.out.println("Line ITEM");
      System.out.println("---------");
      System.out.println("ID:         "+item.getID());
      System.out.println("Product ID: "+item.getProductID());
      System.out.println("Count:      "+item.getCount());

      item = order.getItem(id2);

      System.out.println("Line ITEM");
      System.out.println("---------");
      System.out.println("ID:         "+item.getID());
      System.out.println("Product ID: "+item.getProductID());
      System.out.println("Count:      "+item.getCount());


    }

}



Figure 10: Order client

This simple client application creates the dynamic proxy for the ordering web service and then creates two order items, THNKPDT23 and THNKPDT22. You should see on the server console that all method invocations on these order items take place at the server-side. So both order items are dynamically created on the server, while the client gets their references. This is a good example of the Factory pattern we mentioned earlier. In our case, the ordering service acts as the factory for both order items.

Notice that the line items are stateful, as they keep particular order item data.

Deleting remote references

Unlike stateless web services, stateful web services require special handling codes for deinitialization. We use the explicit instance removal in our example. This is done by calling the disposeServiceInstance method on the LifeCycle system web service. Check out the LineItemImpl's close method to see how to delete a web service explicitly:

public void close() {
  System.err.println("close()");
  WebServiceContext context = WebServiceContext.getInstance();
  LifeCycleService lc = context.getLifeCycleService();
  lc.disposeServiceInstance(this);
}


The last step is to undeploy the service using undeployInterref.bat script.

Learn Git - Master distributed source code management

New to Git and distributed version control? Here are some Git examples and Jenkins-Git integration tutorials designed to help you master the popular source code versioning tool.

What is next?

In this installment we've mastered SOAP complex type handling, and looked in detail at how SOAP error messages are created. We've also explored how to use remote references with web services. We now share a good understanding of SOAP, WSDL and the process of creating and working with web services. Hopefully, we've made it appear straightforward. In Part Three we'll address the important issue of web services security.

In the meantime we'd welcome feedback, comments and ideas. Please get in touch at [email protected]

Next Steps

Here are some other helpful tips, tricks, tutorials and examples by Cameron McKenzie (@cameronmcnz):

Dig Deeper on DevOps-driven, cloud-native app development