Access Web services from wireless devices

Handle SOAP messages on MIDP devices using kSOAP

As I discussed in the first Wireless Java column, "Java Readies Itself for Wireless Web Services," XML processing capability represents one of the key requirements for wireless Web services applications. However, the standard J2ME/MIDP (Java 2 Platform, Micro Edition/Mobile Information Device Profile) specification lacks standard XML APIs, which aren't included in the upcoming MIDP 2.0 specification either. Thus, we need third party J2ME/CLDC (Connected Limited Device Configuration) libraries that can handle XML, especially those Web services-specific XML protocols.

In this article, I discuss how to process Web services messages using the open source kSOAP package on the J2ME/MIDP platform. As do many other enterprise computing architectures, Web services involve both clients and servers. As do many discussions focus on how to use J2EE (Java 2 Platform, Enterprise Edition) to develop and deploy Web services on the server side, I focus on only the J2ME client side in this article.

The SOAP advantage

An important XML protocol for accessing Web services is SOAP (Simple Object Access Protocol). Compared with competing technologies, SOAP has the following advantages:

  1. SOAP defines more than 40 standard data types through XML Schema and allows users to custom-define complex data types. Such sophisticated data-type support makes SOAP a powerful and rich language for exchanging information among today's widely deployed object-oriented systems.
  2. In addition to strong data-type support, SOAP also supports various messaging schemes. Those schemes include synchronous remote procedure calls (RPC), asynchronous messaging, multicast messaging (subscription), and complex message routes with multiple intermediaries.
  3. Since SOAP has gained mainstream support as a Web services messaging standard, most other Web services protocols must interoperate or bind with SOAP. For example, WSDL (Web Services Description Language), UDDI (Universal Description, Discovery, and Integration), and most XML registries support SOAP; XML Digital Signature, XML Encryption, SAML (Security Assertion Markup Language), and other secure XML protocols all provide standard binding with SOAP. Each binding protocol provides syntax of its own special element inside SOAP messages. SOAP's full support for XML namespaces has made it easy to bind with other protocols.

Because of the above advantages, SOAP is already the most widely used communication protocol for Web services. So, a core requirement for a wireless Web service application is the ability to understand SOAP messages. Now let's look at some simple SOAP examples. Listing 1 illustrates a simple, generic SOAP message:

Listing 1. Hello World SOAP message

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <SOAP-ENV:Body>
       <message xsi:type="xsd:string">Hello World</message>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

SOAP is most widely used in Web services RPCs. A SOAP response message from a Web services RPC usually contains the return values inside a Result element under the SOAP Body element. Listing 2 shows a simple SOAP RPC response message:

Listing 2. Hello World SOAP RPC response message

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <SOAP-ENV:Body>
       <result>
         <message xsi:type="xsd:string">Hello World</message>
       </result>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Since we must access SOAP messages programmatically in our programs, we need a SOAP parser.

What is SOAP parsing?

Every generic XML parser with namespace support understands SOAP messages and can extract information from them. In theory, we can always extract text information from a SOAP message using a generic XML parser and then convert those text strings to Java data objects when we need to use them. For example, int i = Integer.parseInt("123"); converts a text string "123" to an integer value 123. But such manual conversion burdens application programmers. Extracting Java data objects directly from a SOAP message would provide a better approach. Enter the SOAP parser.

A SOAP parser is built on a generic XML parser with special type-mapping and text data-marshaling mechanisms. A SOAP parser understands the data-type information in SOAP messages and automatically converts the SOAP message to Java data objects. The parser's real value is that it provides programming transparency between a Java program and a SOAP message. A programmer just feeds Java objects into a SOAP writer, sends the message, waits for the server response, and then reads Java objects directly from the SOAP parser.

As I discussed, SOAP features a rich set of functionalities. Many consider SOAP parsing support on resource-restricted wireless platforms such as J2ME/CLDC as expensive. Practical difficulties also exist:

  1. The lightweight J2ME/CLDC platform sacrifices many useful standard Java functionalities for size and speed. As a result, the J2ME/CLDC platform has only limited string functionality, a major problem for every wireless Java XML parser.
  2. SOAP parsing requires the parser to read the whole document into memory. But most J2ME/CLDC parsers are memory-efficient linear SAX parsers, which never construct in-memory object models.
  3. The J2ME/CLDC platform lacks support for some basic data types, such as the Float type.

Fortunately, project kSOAP solves these problems and provides a SOAP solution for small devices.

kSOAP to the rescue

Based on its renowned open source generic XML parser kXML, Enhydra.org has started an open source project for SOAP parsing on J2ME/MIDP platforms—the kSOAP project. Part of the EnhydraME project, kSOAP was written by a group of developers lead by Stefan Haustein. Enhydra released kSOAP's first alpha version in May 2001. After a year of development, kSOAP, now at version 1.2, supports a core set of SOAP 1.2 functionalities. This article's examples and architecture discussions apply to kSOAP version 0.95 and beyond (see Resources for the full source code). The code runs in the MIDP environment; if you are new to MIDP development or need to refresh your skills, refer to Michael Cymerman's series "Device Programming with MIDP."

This code segment parses the Hello World examples using kSOAP:

ByteArrayInputStream bis = new ByteArrayInputStream (mesg.getBytes ());
InputStreamReader reader = new InputStreamReader (bis);
XmlParser xp = new XmlParser (reader);
// Use default mapping between Java objects and Soap elements
SoapEnvelope envelope = new SoapEnvelope (new ClassMap (Soap.VER12));
envelope.parse (xp);

The string variable mesg stores the entire SOAP document.

Now, we must retrieve the message from the parsed SOAP envelope. The following code retrieves the first child under the SOAP Body element:

// For Hello World Listing 1
String result = (String) envelope.getBody();

When applied to Hello World Listing 1, result contains string value Hello World.

This next code segment retrieves the first grandchild under the SOAP Body element:

// For Hello World Listing 2
String result = (String) envelope.getResult();

The SoapEnvelope.getResult() method conveniently retrieves values from SOAP RPC response messages like the Hello World in Listing 2. However, as I have said, a SOAP parser's core value lies not in its ability to retrieve text strings from a SOAP document, but in its ability to map SOAP XML elements to Java objects. Let's examine how kSOAP achieves that mapping.

kSOAP objects structure

In a SOAP message, an element's xsi:type attribute specifies the data type of an XML element's content. For example, <myValue xsi:type="xsd:int">123</myValue> specifies an integer value of 123, and <myValue xsi:type="xsd:string">123</myValue> specifies a string value of "123".

kSOAP automatically maps four SOAP types to Java types according to the following list:

Default type mapping
SOAP typeJava type
xsd:int java.lang.Integer
xsd:long java.lang.Long
xsd:string java.lang.String
xsd:booleanjava.lang.Boolean

When a simple kSOAP parser encounters a SOAP element, the parser reads the XML element into an in-memory Java data object according to the following rules:

  1. If the SOAP element is one of the default primitive types in the table above, it converts to a Java object of a matching type.
  2. If the SOAP element has no children (a primitive element) but is a default type, it converts to a SoapPrimitive object. You can retrieve the element's original SOAP type information from the SoapPrimitive.getNamespace() and SoapPrimitive.getName() methods. You can access the element's string value from the SoapPrimitive.toString() method.
  3. If the SOAP element has children (a complex element), it converts to a KvmSerializable object. KvmSerializable is an interface; the kSOAP package provides the interface's convenience implementation: SoapObject. I mainly discuss SoapObject usage in this article. Similar to SoapPrimitive objects, you can retrieve the element's original SOAP type information from the SoapObject.getNamespace() and SoapObject.getName() methods.
  4. A complex element's children convert to properties inside the parent SoapObject according to the above three rules in this list. Each property also has an associated PropertyInfo object containing information such as the SOAP element name and the property's Java object type. PropertyInfo does not yet support element namespace, but support is planned for future kSOAP releases.

Because a SoapObject can take other SoapObjects as properties, we can use SoapObject to present complex hierarchical structures. If you are an XML developer, you are probably familiar with the Document Object Model (DOM); the SoapObject resembles the parent element, and the property and PropertyInfo pairs resemble child elements in a DOM construct.

In trivial cases, like our Hello World examples where the message contains only one primitive SOAP element, the SOAP parser directly gives the mapped Java object or SoapPrimitive object through the SoapEnvelope.getBody() or SoapEnvelope.getResult() methods. If the SOAP message contains more information, the above two get methods return a root SoapObject, which contains the entire hierarchy of Java objects mapped from the SOAP elements according to the above rules.

The figure below shows an example of such a structure. Each box with a dashed-line border represents a SOAP element in the parsed document. Inside each box, the original SOAP element name is stored in the PropertyInfo object and the SOAP type attribute name/namespace is stored in the property object. Arrows represent property relationships. You can find the Java data objects' original SOAP type information by searching the mappings in the ClassMap object, which contains the information in the above table (more on ClassMap later). As we can see, the root SoapObject lacks a pairing PropertyInfo to make a full dashed-line box. Therefore, we lose the root element's name after parsing. However, that is not an issue when we read SOAP documents, since element names normally serve only as indexes for accessing Java data objects, and we do not need an index to access the root element.

Structure of a parsed SOAP document. Click on thumbnail for full-size image.

A note on serialization (SOAP writing)

Above, I presented a simple parsing process. To compose a SOAP message, we reverse the process. We first build the SoapObject hierarchy in memory. All leaf properties must be either SoapPrimitive or one of the four default type Java objects. Then we use a kSOAP writer object to serialize the memory object to an XML stream.

However, as you might already see, the root element presents a serialization problem. When we construct the structure according to the figure, the root SoapObject contains only the SOAP type name and namespace. No XML element name is available for the root element due to the pairing PropertyInfo's absence. We can't write an XML element without an element name. kSOAP 1.2's writer sidesteps this problem by making a notable exception from the parsing rule: when we serialize a root SoapObject, its Name and Namespace are used as element name/namespace rather than the SOAP type name/namespace. Let's see a simple example to understand these points:

A kSOAP parser will parse a SOAP root element <MyRoot xmlns="http://myns" xsi:type="xsd:mytype">...</MyRoot> into a SoapObject equivalent of new SoapObject( Soap.XSD, "mytype");. Since no PropertyInfo is available for the root element, the element name MyRoot (and namespace http://myns) are lost. However, if you serialize the above SoapObject as a root element back to XML, kSOAP writer will not give the original SOAP string. According to the exception rule, the writer will give <xsd:mytype>...</xsd:mytype>, which is clearly not what we wanted. Under the exception rule, the correct root SoapObject for serialization is new SoapObject( "http://myns", "MyRoot");. The kSOAP writer will give a SOAP element:

<MyRoot xmlns="http://myns">...</MyRoot>

As we can see, kSOAP's trade-off is its inability to specify the root element's SOAP type when serializing. But this is normally not a big issue. Because the exception rule for SoapWriter proves somewhat confusing, the project's designers plan to make it more intuitive in future kSOAP releases.

In the following sections, I use examples to illustrate simple SOAP handling, and discuss problems and drawbacks. Afterward, I introduce kSOAP's more sophisticated features.

The stock-trade example

Throughout the rest of this article, I use a fictitious stock-trade wireless Web services application. The wireless client interacts with the Web services server through synchronous SOAP remote procedure calls. The client sends a SOAP request containing the trade order; the server executes the order and returns the status information. In general, a synchronous SOAP RPC session consists of the following steps:

  1. The client composes a SOAP request message with invocation parameters.
  2. The client sends the SOAP request to a Web services server through a standard messaging protocol such as HTTP.
  3. The server invokes the requested service with the parameters extracted from the request SOAP message.
  4. The server composes the return values into a response SOAP message and sends them back. In case of HTTP invocation, the same HTTP connection can be used.
  5. The client receives the response message and then parses the return values into Java data objects for further processing.

Web services components on the server side handle Steps 3 and 4. (Server-side Web services implementation reaches beyond this article's scope. Refer to Resources for more information.) Step 2 (and part of 4) handles the network interaction between the client and the server, which standard MIDP HTTP communication APIs could handle. kSOAP does have a helper class, org.ksoap.transport.HttpTransport, to automate this HTTP-call transport process. Most real-world applications use the HttpTransport class directly. However, you must understand exactly how kSOAP works under the hood to use the HttpTransport class effectively. So, I will demonstrate how kSOAP works on serialized SOAP XML strings first. Then, toward the end of this article, I will give a simple HttpTransport example.

In this article, I focus only on how kSOAP itself works and ignore the server-side and networking issues. In the RPC request example, I demonstrate how to serialize Java objects to a SOAP string. In the RPC response examples, I assume we have already received the response SOAP messages and have stored them in string variables, and demonstrate how to retrieve Java objects from the message strings.

A simple request

We can use the following Java code to construct a memory model for the SOAP stock-trade order-request and serialize it to an XML string:

// SoapObject "method" is the calling construct
//
// As we discussed before,
// the "" and "StockOrderParameters" here are
// element name/namespace rather than SOAP type name/namespace
SoapObject method = new SoapObject("",
                                   "StockOrderParameters");
method.addProperty("Symbol", "XYZ");
method.addProperty("From", "Michael Yuan");
method.addProperty("Shares", new Integer (1000));
method.addProperty("Buy", new Boolean (true));
method.addProperty("LimitPrice",
                   new SoapPrimitive ("http://www.w3.org/2001/XMLSchema",
                                      "float",
                                      "123.4"));
// Assemble "method" into an enveloped SOAP message
// and then export to a String
ByteArrayOutputStream bos = new ByteArrayOutputStream ();
XmlWriter xw = new XmlWriter (new OutputStreamWriter (bos));
// Use default mapping between Java objects and Soap elements
SoapEnvelope envelope = new SoapEnvelope (new ClassMap (Soap.VER12));
envelope.setBody (method);
envelope.write (xw);
xw.flush ();
bos.write ('\r');
bos.write ('\n');
byte [] requestData = bos.toByteArray ();
String requestSOAPmesg = String (requestData);

The code constructs a SoapObject with several properties, all of which are primitive data elements, resulting in only one level of SoapObject hierarchy. The SoapObject's Name determines the root element's name, as we noted in the kSOAP root element-writing rule. Each child element's name is determined by each property's Name, which the first parameter in the SoapObject.addProperty() method specifies. As you learned earlier, the default kSOAP parser/writer understands four default types. Those objects automatically convert to SOAP elements. Note that we have to construct a SoapPrimitive object manually for the xsd:float SOAP type since the default kSOAP setup doesn't support Java type Float. As you will see later, we can also use data Marshal to process unknown types.

The resultant SOAP string stored in requestSOAPmesg:

Listing 3. Stock-order request message

<SOAP-ENV:Envelope xmlns:SOAP-ENC="http://www.w3.org/2001/12/soap-encoding" 
                   xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope" 
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <SOAP-ENV:Body 
    SOAP-ENV:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
  <StockOrderParameters id="o0" SOAP-ENC:root="1">
   <Symbol xsi:type="xsd:string">XYZ</Symbol>
   <From xsi:type="xsd:string">Michael Yuan</From>
   <Shares xsi:type="xsd:int">1000</Shares>
   <Buy xsi:type="xsd:boolean">true</Buy>
   <LimitPrice xsi:type="xsd:float">123.45</LimitPrice>
  </StockOrderParameters>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Now, we turn to this article's main focus: the response message parsing.

A simple response

The response we received from the server over the HTTP connection could look like this:

Listing 4. Simple stock-order response message

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <SOAP-ENV:Body>
      <result>
        <OrderStatus>
          <CustomerName xsi:type="xsd:string">Michael Yuan</CustomerName>
          <Symbol xsi:type="xsd:string">XYZ</Symbol>
          <Share xsi:type="xsd:int">1000</Share>
          <Buy xsi:type="xsd:boolean">true</Buy>
          <Price xsi:type="xsd:float">123.45</Price>
          <ExecTime xsi:type="xsd:dateTime">
            2002-07-18T23:20:50.52Z
          </ExecTime>
        </OrderStatus>
      </result>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

We store the SOAP XML response message (Listing 4) in string variable soapRespMesg and pass it to a kSOAP parser. As I discussed, the parser returns a SoapObject, which contains the SOAP document's data and structure. We access the properties by passing the XML element name or property index to the SoapObject.getProperty() method. When we access properties by their XML element names, the SoapObject.getProperty() first compares the element names stored in PropertyInfo objects and then returns the matching property object. As we can see below, the properties are already cast into the appropriate Java types according to the mapping. The unknown SOAP types automatically convert to SoapPrimitive objects:

// For Listing 4.
ByteArrayInputStream bis = new ByteArrayInputStream (soapRespMesg.getBytes ());
InputStreamReader reader = new InputStreamReader (bis);
XmlParser xp = new XmlParser (reader);
// Use default mapping between Java objects and Soap elements.
SoapEnvelope envelope = new SoapEnvelope (new ClassMap (Soap.VER12));
envelope.parse (xp);
// Get the parsed structure.
SoapObject orderStatus = (SoapObject) envelope.getResult();
// Retrieve the values as appropriate Java objects. 
String customerName = (String) orderStatus.getProperty ("CustomerName");
String symbol = (String) orderStatus.getProperty ("Symbol");
Integer share = (Integer) orderStatus.getProperty ("Share");
Boolean buy = (Boolean) orderStatus.getProperty ("Buy");
// Since MIDP has no "Float" type, there is no corresponding
// Java object type for "xsd:float". So, as any unknown type,
// this element is mapped to a "SoapPrimitive".
SoapPrimitive price = (SoapPrimitive) orderStatus.getProperty ("Price");
SoapPrimitive execTime = (SoapPrimitive) orderStatus.getProperty ("ExecTime");

The above example is simple since it has only one SoapObject with primitive properties. I discuss a more structured response in the next example.

A more complex response

Presenting response information linearly as we did in the last example proves adequate for simple occasions. But for complex responses, we might desire more structures in the SOAP response message to improve clarity and readability. An example of such a complex response is:

Listing 5. Stock-order response message with complex structures

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:n="http://www.javaworld.com/ksoap/test">
    <SOAP-ENV:Body>
      <result>
        <OrderStatus xsi:type="n:orderStatus">
          <CustomerName xsi:type="xsd:string">Michael Yuan</CustomerName>
          <Transaction xsi:type="n:transaction">
            <Symbol xsi:type="xsd:string">XYZ</Symbol>
            <Share xsi:type="xsd:int">1000</Share>
            <Buy xsi:type="xsd:boolean">true</Buy>
            <Price xsi:type="xsd:float">123.45</Price>
          </Transaction>
          <ExecTime xsi:type="xsd:dateTime">2002-07-18T23:20:50.52Z</ExecTime>
        </OrderStatus>
      </result>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

We store the above SOAP message in string variable complexSoapRespMesg and use the following Java code to read the embedded data:

// For Listing 5
ByteArrayInputStream bis = new ByteArrayInputStream (complexSoapRespMesg.getBytes ());
InputStreamReader reader = new InputStreamReader (bis);
XmlParser xp = new XmlParser (reader);
// Use default mapping between Java objects and Soap elements
SoapEnvelope envelope = new SoapEnvelope (new ClassMap (Soap.VER12));
envelope.parse (xp);
 
// Get the parsed structure
SoapObject orderStatus = (SoapObject) envelope.getResult();
String customerName = (String) orderStatus.getProperty ("CustomerName");
// Second layer of SoapObject 
SoapObject transaction = (SoapObject) orderStatus.getProperty ("Transaction");
String symbol = (String) transaction.getProperty ("Symbol");
Integer share = (Integer) transaction.getProperty ("Share");
Boolean buy = (Boolean) transaction.getProperty ("Buy");
SoapPrimitive price = (SoapPrimitive) transaction.getProperty ("Price");
 
SoapPrimitive execTime = (SoapPrimitive) orderStatus.getProperty ("ExecTime");

The process is rather straightforward. We just trace down the hierarchy tree using element names and the SoapObject.getProperty() method.

The simple parser in the above two examples gives the SOAP message's structure in a hierarchy of SoapObjects and automatically converts primitive SOAP elements of default types to Java objects. However, there are only four default types. All other types are simply wrapped in generic SoapPrimitive objects. SoapPrimitive's simple wrapper does not take advantage of SOAP's rich type presentation at all. In the next section, we look at more advanced techniques to improve custom type handling beyond the default four.

Understand data marshal

To further automate the SOAP type-mapping process, we must prepare the parser for two tasks:

  1. The parser must know the mapping relationship between custom SOAP types to custom Java types. We complete custom mapping by adding matching type pairs to the current parser's ClassMap object.
  2. Since all SOAP types are presented in plaintext strings, the parser must know how to convert the string to a desired Java object. The parser converts the string through a Marshal object, which is registered with the parser's corresponding custom SOAP and Java type pair in the ClassMap object.

In the following example, I illustrate how to marshal the ExecTime element in the stock-order response message (Listings 4 and 5). ExecTime specifies the time the order executes and has a SOAP type xsd:dateTime. We try to marshal ExecTime's content automatically into a java.util.Date object.

But before we can marshal the Date object, we must equip the parser with new capabilities described in the above two steps. kSOAP provides a standard way to add custom type-mapping and -marshaling capabilities through the implementation of interface Marshal. The source code for the Marshal interface is:

public interface Marshal {
  public Object readInstance (SoapParser parser,
                              String namespace, String name,
                              ElementType expected) throws IOException;
  public void writeInstance (SoapWriter writer,
                             Object instance) throws IOException;
 
  public void register (ClassMap cm);
}

The kSOAP download package provides three Marshal implementations as both convenience tools and programming examples. Luckily, Date type's marshal is among the three. Below, I will use MarshalDate as an example to illustrate how to implement the Marshal interface so you can write your own Marshal objects beyond the three implementations provided by kSOAP. You can find its source code in class org.ksoap.marshal.MarshalDate. Method Marshal.readInstance() actually reads the text string from the SOAP element and then converts it to a Java object. In the case of the MarshalDate, readInstance()'s source code is:

public Object readInstance (SoapParser parser,
                            String namespace, String name,
                            ElementType expected) throws IOException {
 
    parser.parser.read (); // Start tag
    Object result = IsoDate.stringToDate
        (parser.parser.readText (), IsoDate.DATE_TIME);
    parser.parser.read (); // End tag
    return result;
}

where the working method IsoDate.stringToDate() is in package org.kobjects.isodate and the source code is:

public class IsoDate {
 
    public static final int DATE = 1;
    public static final int TIME = 2;
    public static final int DATE_TIME = 3;
 
// Other methods
    public static Date stringToDate (String text, int type) {
 
        Calendar c = Calendar.getInstance ();
 
        if (type != DATE_TIME)
            c.setTime (new Date (0));
 
        if ((type & DATE) != 0) {
            c.set (Calendar.YEAR, Integer.parseInt
                   (text.substring (0, 4)));
            c.set (Calendar.MONTH, Integer.parseInt
                   (text.substring (5, 7)) - 1 + Calendar.JANUARY);
            c.set (Calendar.DAY_OF_MONTH, Integer.parseInt
                   (text.substring (8, 10)));
 
            if (type == DATE_TIME)
                text = text.substring (11);
        }
 
        if ((type & TIME) == 0)
            return c.getTime ();
        c.set (Calendar.HOUR_OF_DAY, Integer.parseInt
               (text.substring (0, 2)));              // -11
        c.set (Calendar.MINUTE, Integer.parseInt
               (text.substring (3, 5)));
        c.set (Calendar.SECOND, Integer.parseInt
               (text.substring (6, 8)));
 
        int pos = 8;
        if (pos < text.length() && text.charAt (pos) == '.') {
            int ms = 0;
            int f = 100;
            while (true) {
                char d = text.charAt (++pos);
                if (d < '0' || d > '9') break;
                ms += (d-'0') * f;
                f /= 10;
            }
            c.set (Calendar.MILLISECOND, ms);
        }
        else
            c.set (Calendar.MILLISECOND, 0);
 
        if (pos < text.length ()) {
            if (text.charAt (pos) == '+' || text.charAt (pos) == '-')
 
                c.setTimeZone (TimeZone.getTimeZone
                               ("GMT"+text.substring (pos)));
 
            else if (text.charAt (pos) == 'Z')
                c.setTimeZone (TimeZone.getTimeZone ("GMT"));
            else
                throw new RuntimeException ("illegal time format!");
        }
 
        return c.getTime ();
    }
}

Similarly, method Marshal.writeInstance() describes how to serialize the Java object to a SOAP text string. In kSOAP writers, the method offers a more elegant alternative to using SoapPrimitive objects to handle unknown types. Interested readers can find the source code for that function from kSOAP source distribution.

Method Marshal.register() adds the matching custom SOAP type and Java type pair, as well as their processing Marshal object, to a ClassMap object:

public void register (ClassMap cm) {
    cm.addMapping
        (cm.xsd, "dateTime",
         MarshalDate.DATE_CLASS, this);
}

Inside method ClassMap.addMapping(), the custom SOAP type is added as an empty SoapPrimitive object:

/** Defines a direct mapping from a namespace and name to a Java
    class (and vice versa), using the given marshal mechanism */
 
public void addMapping (String namespace, String name,
                        Class clazz, Marshal marshal) {
    qNameToClass.put (new SoapPrimitive (namespace, name, null),
                      marshal == null ? (Object) clazz : marshal);
    classToQName.put (clazz.getName (),
                      new Object [] {namespace, name, null, marshal});
    if (prefixMap.getPrefix (namespace) == null)
        prefixMap = new PrefixMap (prefixMap, "n"+(cnt++), namespace);
}

Once a Marshal is implemented for a specific types pair, its use proves trivial. We need to ensure only that the desired Marshal is registered with the current parser. The following code segment illustrates the use of MarshalDate for Listing 4's SOAP message:

// For Listing 4
ByteArrayInputStream bis = new ByteArrayInputStream (mesg.getBytes ());
InputStreamReader reader = new InputStreamReader (bis);
XmlParser xp = new XmlParser (reader);
 
// Register Marshal for "xsd:dateTime" type
ClassMap cm = new ClassMap (Soap.VER12);
Marshal md = new MarshalDate ();
md.register (cm);
SoapEnvelope envelope = new SoapEnvelope (cm);
envelope.parse (xp);
 
SoapObject orderStatus = (SoapObject) envelope.getResult();

As the following code segment shows, the parsed ExecTime property is now directly a Date type Java object, rather than a SoapPrimitive, as in previous examples.

Date execTime = (Date) orderStatus.getProperty ("ExecTime"); 

In addition to the MarshalDate implementation, kSOAP provides two other Marshal implementations: MarshalBase64 and MarshalHashtable. Base64 is a method for encoding a binary stream into an ASCII string so that it can transport through email or XML/SOAP. MarshalBase64 marshals a xsd:based64Binary element into a Java byte array. kSOAP does not support SOAP attachments, but MarshalBase64 should allow users to send/receive binary data chunks. MarshalHashtable marshals a SOAP Map element into a Java hashtable.

Now, you can use this marshaling technique to extend the kSOAP parser and marshal any SOAP types to your custom Java data objects.

Advanced features

I now discuss two advanced kSOAP features: array handling and SoapObject templates.

Arrays

As I discussed previously, SOAP's strength lies in its power to represent rich data with complex types. One of the important data types in any programming language is array. SOAP supports array. For example, the stock-trade Web service might execute several orders together and return the following SOAP response message:

Listing 6. Stock-order response message with an array

<SOAP-ENV:Envelope 
  xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"
  xmlns:SOAP-ENC="http://www.w3.org/2001/12/soap-encoding"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:n="http://www.javaworld.com/ksoap/test">
    <SOAP-ENV:Body>
      <result>
        <OrderStatus xsi:type="n:orderStatus">
          <CustomerName xsi:type="xsd:string">Michael Yuan</CustomerName>
          <Transactions xsi:type="SOAP-ENC:Array" 
                        SOAP-ENC:arrayType="n:transaction[2]">
            <Transaction xsi:type="n:transaction">
              <Symbol xsi:type="xsd:string">ABC</Symbol>
              <Share xsi:type="xsd:int">500</Share>
              <Buy xsi:type="xsd:boolean">true</Buy>
              <Price xsi:type="xsd:float">43.21</Price>
            </Transaction>
            <Transaction xsi:type="n:transaction">
              <Symbol xsi:type="xsd:string">XYZ</Symbol>
              <Share xsi:type="xsd:int">1000</Share>
              <Buy xsi:type="xsd:boolean">true</Buy>
              <Price xsi:type="xsd:float">123.45</Price>
            </Transaction>
          </Transactions>
          <ExecTime xsi:type="xsd:dateTime">2002-07-18T23:20:52.52Z</ExecTime>
        </OrderStatus>
      </result>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

kSOAP reads a SOAP array into a Java java.util.Vector object. Method Vector.elementAt(i) extracts the array's ith object. Depending on the arrayType, this object could be a SoapObject, a SoapPrimitive, a default Java type, or a marshaled Java object. In the above code, the array components are SoapObject objects. The following code segment parses and retrieves the data. The code assumes that the SOAP response message with array (Listing 6) is stored in string variable arraySoapRespMesg:

// For Listing 6
ByteArrayInputStream bis = new ByteArrayInputStream (arraySoapRespMesg.getBytes ());
InputStreamReader reader = new InputStreamReader (bis);
XmlParser xp = new XmlParser (reader);
 
// Register Marshal for "xsd:dateTime" type
ClassMap cm = new ClassMap (Soap.VER12);
Marshal md = new MarshalDate ();
md.register (cm);
SoapEnvelope envelope = new SoapEnvelope (cm);
envelope.parse (xp);
 
SoapObject orderStatus = (SoapObject) envelope.getResult();
 
String customerName = (String) orderStatus.getProperty ("CustomerName");
 
Vector transactions = (Vector) orderStatus.getProperty ("Transactions");
// First element in the array
SoapObject transaction0 = (SoapObject) transactions.elementAt(0);
String symbol0 = (String) transaction0.getProperty ("Symbol");
Integer share0 = (Integer) transaction0.getProperty ("Share");
Boolean buy0 = (Boolean) transaction0.getProperty ("Buy");
SoapPrimitive price0 = (SoapPrimitive) transaction0.getProperty ("Price"); 
// Second element in the array
SoapObject transaction1 = (SoapObject) transactions.elementAt(1);
String symbol1 = (String) transaction1.getProperty ("Symbol");
Integer share1 = (Integer) transaction1.getProperty ("Share");
Boolean buy1 = (Boolean) transaction1.getProperty ("Buy");
SoapPrimitive price1 = (SoapPrimitive) transaction1.getProperty ("Price");

However, kSOAP does not support the full SOAP array specification. For example, kSOAP lacks support for multidimensional arrays because of small devices memory considerations.

SoapObject template

In the above examples, we take in only SOAP documents and parse them into SoapObjects as they are. However, in many cases, we require the response message to follow certain formats and wish the parser to validate it during the parsing. For example, we might require that the n:transaction-type SOAP elements in Listings 5 and 6 contain a xsd:string value Symbol, a xsd:int value Share, a xsd:boolean value Buy, and a xsd:float value Price.

Our old friend, the ClassMap class, can validate the message. We must add into the ClassMap object a SoapObject template associated with the current parser. The SoapObject template is an empty SoapObject with information about the parent SOAP type, children (properties) element names, and their Java types. Again, the children can be templates themselves, which allows us to construct arbitrarily complex templates.

You add the SoapObject template by calling the ClassMap.addTemplate() method (examine the method's source code for its inner workings). Similar to the data marshal example's ClassMap.addMapping() method, the SOAP type is added as an empty SoapPrimitive object. However, ClassMap.addTemplate() adds a preconstruct SoapObject instance here rather than a marshal instance. Our example's ClassMap maneuver is illustrated below:

// For Listings 5 or 6
ByteArrayInputStream bis = new ByteArrayInputStream (arraySoapRespMesg.getBytes ());
InputStreamReader reader = new InputStreamReader (bis);
XmlParser xp = new XmlParser (reader);
 
ClassMap cm = new ClassMap (Soap.VER12);
 
// Register Marshal for "xsd:dateTime" type
Marshal md = new MarshalDate ();
md.register (cm);
SoapObject so = new SoapObject ("http://www.javaworld.com/ksoap/test",
                                "transaction");
so.addProperty ("Symbol", new String (""));
so.addProperty ("Share", new Integer (0));
so.addProperty ("Buy", new Boolean (true));
so.addProperty ("Price", new SoapPrimitive ("xsd", "float", ""));
cm.addTemplate (so);
 
SoapEnvelope envelope = new SoapEnvelope (cm);
envelope.parse (xp);
 
SoapObject orderStatus = (SoapObject) envelope.getResult();

If the parsing succeeds, we can proceed to extract data from orderStatus as illustrated in other examples. If the SOAP message's n:transaction element fails to conform to the corresponding SoapObject template, the parser throws an exception and stops.

Use HttpTransport

You have learned how kSOAP works on serialized SOAP messages, yet I have not shown you how to use Web services on real networks. We could use the network APIs in MIDP directly to send/receive SOAP messages via HTTP. But that proves unnecessary since kSOAP provides a powerful utility class HttpTransport. Class HttpTransport does the network plumbing and provides a way to bypass the serialized messages completely in our Java programs. A HttpTransport object can be constructed using the RPC end point and SoapAction URIs (Uniform Resource Identifiers). Method HttpTransport.call() takes in a KvmSerializable object, serializes it to SOAP message, sends the message to the end point, and receives the response. Then HttpTransport.call() parses the response SOAP message, calls the SoapEnvelope.getResult() method to get the response data in a Java object, and returns the Java object.

The kSOAP download page offers simple examples on how to use HttpTransport. Its usage is trivial once you have a solid understanding on how things work under the hood on the serialized message level. The following code segment illustrates how to access the XMethods stock quote Web service using HttpTransport:

// ... ...
//
// Object to display results
StringItem resultItem = new StringItem ("", "");
// ... ...
String symbol = symbolField.getString ();
resultItem.setLabel (symbol);
// Set up a serializable object for a SOAP message
SoapObject rpc = new SoapObject 
        ("urn:xmethods-delayed-quotes", "getQuote");
rpc.addProperty ("symbol", symbol);
// Call the Web service and retrieve the result
resultItem.setText (""+new HttpTransport 
        ("http://services.xmethods.net/soap",
         "urn:xmethods-delayed-quotes#getQuote").call (rpc));

Interested readers should read

HttpTransport

's source code to study how it works. The source code comments prove helpful.

Now it's your turn

kSOAP provides a powerful and flexible tool for parsing and composing SOAP messages into/from Java objects. It offers all the features essential to SOAP parsing and data access. I did not cover some issues, such as the handling of SoapFault, but this article's examples should get you started.

Moreover, since the SOAP community is developing kSOAP under the open source model, you can always look at its source code to figure out how things work. Or even better, you can develop the features you need, fix bugs, and provide feedback to the community.

SOAP-based Web services are gaining popularity. Information-service giants like Google.com and Amazon.com have recently provided SOAP interfaces to their services. I wrote simple a kSOAP client for the Google API, which is freely available for download from Resources. Now it is your turn to develop some cool applications for your wireless devices!

I wish to thank kSOAP lead developer Stefan Haustein for reviewing this article and its examples.

Michael Yuan is a PhD candidate at the University of Texas, where he is a research associate at the Center for Research in Electronic Commerce and an open source Java developer.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies