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:
- 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.
- 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.
- 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="
xmlns:xsi="
xmlns:xsd="
<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="
xmlns:xsi="
xmlns:xsd="
<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:
- 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.
- 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.
- 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:
|
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:
- 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.
- 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 theSoapPrimitive.getNamespace()
andSoapPrimitive.getName()
methods. You can access the element’s string value from theSoapPrimitive.toString()
method. - 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 discussSoapObject
usage in this article. Similar toSoapPrimitive
objects, you can retrieve the element’s original SOAP type information from theSoapObject.getNamespace()
andSoapObject.getName()
methods. - 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 associatedPropertyInfo
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 SoapObject
s 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.
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=" 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( ", "MyRoot");
. The kSOAP writer will give a SOAP element:
<MyRoot xmlns=">...</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:
- The client composes a SOAP request message with invocation parameters.
- The client sends the SOAP request to a Web services server through a standard messaging protocol such as HTTP.
- The server invokes the requested service with the parameters extracted from the request SOAP message.
- 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.
- 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 ("
"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="
xmlns:SOAP-ENV="
xmlns:xsi="
xmlns:xsd="
<SOAP-ENV:Body
SOAP-ENV:encodingStyle=">
<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="
xmlns:xsi="
xmlns:xsd="
<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="
xmlns:xsi="
xmlns:xsd="
xmlns:n="
<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 SoapObject
s 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:
- 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. - 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 theClassMap
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="
xmlns:SOAP-ENC="
xmlns:xsi="
xmlns:xsd="
xmlns:n="
<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 SoapObject
s 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 ("
"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
("
"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.