The new kid on the Internet technology block is Web services and its implementation technology, SOAP. Simple Object Access Protocol
is an XML vocabulary used to describe messaging and remote procedure calls
between distributed components.
The power of SOAP is its flexibility, which comes from SOAP's foundation
in XML, and its ubiquity, which comes from the use of HTTP as a primary
transport mechanism.
Any modern language that works with the Internet is likely to support
both XML and HTTP, and Java is no exception. There are, in fact, many good
toolkits for using SOAP with Java and Apache Axis is one of the most
popular. Choices abound for the developer working with J2SE or J2EE.
What About the J2ME Developer?
In the world of mobile devices, a world still constrained by hardware
and network limitations, the concepts behind distributed computing are slowly taking root. Why cram all the functionality of a stock ticker or m-commerce application into one little MIDlet when you could offload much of the functionality to a server with
more resources? Not to mention the flexibility it provides for system and
database administrators. How often do they wish they could fix simple
problems by logging in from a Palm Pilot or BlackBerry?
SOAP provides distributed functionality in a manner RMI and CORBA
cannot. It's language agnostic, so a MIDlet can converse with a legacy COBOL
application wrapped within a PHP SOAP service. It's also lightweight;
there's no need for the resource-hungry ORB platform to support it. One
small toolkit, kSOAP, weighing in at a whopping 41KB, is all that's needed
to bring wireless Web services to your MIDlets.
Application Architecture
The Web service I'll be looking at is a simple example of what can be
accomplished. The taxCalc service takes two parameters, a tax rate and a
subtotal, and calculates a total for a given purchase.
The service is written in PHP using the NuSOAP toolkit, a simple and
easy way to prototype a Web service. In a production environment, once I had
the definition of the service API frozen, I would then deploy a more robust
version in a J2EE environment using Axis. For the purposes of this article,
however, the use of PHP is handy to illustrate just how flexible and
language-neutral SOAP can be.
The power of using a MIDlet is that it's deployable across a wide range
of mobile devices, from a Motorola handset to a Palm Pilot or RIM
BlackBerry. This provides the same SOAP client with a wide range of
platforms for its deployment, and opens the door for a greater use of the
Web service.
As can be seen in Figure 1, the architecture of our Web service is quite
simple. On the server side we have an Apache Web server hosting the PHP
service, which exports one interface function: taxCalc(). The endpoint for
this service is available via www.whytewolf.ca/ws/taxCalc.php.
On the client side we have our J2ME client device running a MIDlet,
which uses the kSOAP library to access the SOAP service. Messaging is done
through the HTTP protocol, which is supported by all MIDP-compliant devices.
While you might think that a more complex Web service would require a
more complex architecture, that's not necessarily the case. Any given
application that uses Web services would only need to access a separate node
for each service it consumes. If the same server or application provides
multiple services especially if multiple functions are exported by the
same service the application architecture is greatly simplified.
Ultimately, no matter which services are used or how many are available
to the client, SOAP is about XML messages and the data they contain. It is
these messages that the entire SOAP protocol is built upon.
SOAP Messages
As stated earlier, SOAP messages are simply a specific XML vocabulary
used to encapsulate document or procedure calls. As can be seen from Listing 1 , the root element of any SOAP message is the envelope tag. Inside this envelope the request and parameters are transmitted to the server and the
response is transmitted back.
Note the use of XML Schema data types to encode the data that's being
sent as parameters. In Listing 1 the function call to taxCalc is made using
a taxCalc element to enclose the two parameters, rate and sub. Each
parameter element is given a type via the xsi:type attribute. The xsd:string
type is an XML Schema string that, once the server receives it, is
dynamically cast via PHP's internal mechanism to a numeric value.
The request is transmitted as an HTTP POST request and sends a special
header: SOAPAction. This header indicates the specific service and function
being called by including that information as a URI. In the case of taxCalc,
what is used is a URN that indicates the service and function. This
information is often useful, but the SOAP spec doesn't require that the
server use it. The same URN is also used as a namespace URI for the service
request element taxCalc.
Looking at the response envelope in Listing 2 we see that again the data
being transmitted back to the client is wrapped within an envelope element's
body tag and encoded using XSD data types. The response element taxCalcResponse wraps the actual value transmitted, in this case stored in a noname element of type xsd:float, a floating-point number.
What if there's a problem? How does SOAP indicate a service failure and
not leave the client hanging? The SOAP spec calls for the use of a Fault
framework to indicate exceptional conditions in the execution of a service.
Listing 3 shows the anatomy of a SOAP fault. Like the request and
response, a fault is transmitted using a SOAP envelope element, but instead
of providing RPC calls or responses, the envelope's body contains the fault
information. Four fault tags are transmitted: faultcode, faultstring,
faultactor, and faultdetail. Faultcode and faultstring are mandatory. These
two describe what type of fault occurred if it was bad data from the
client, a server failure, a version mismatch, or a miscommunication and
provide a message explaining the problem. As can be seen in Listing 3, the
faultcode is client, meaning the client sent invalid data. The faultstring
explains: taxCalc() can accept only nonzero subtotal values. The other two
fault tags, faultactor and faultdetail, are optional and provide extended
information on the cause of failure.
As can be seen in the examples, the SOAP 1.1 specification provides a
highly detailed and flexible way to transmit data in both directions as well
as indicate problems with the execution. However, with that flexibility
comes a price: complexity. Parsing out the required details and marshaling
the required data can be quite a task. Luckily for MIDlet developers, the
kSOAP toolkit simplifies matters greatly.
For more information on SOAP, check out the Resources section at the end
of this article.
The kSOAP Toolkit
The kSOAP project is based on kXML, a lightweight pull parser designed
specifically for use with MIDP. Enhydra, a provider of J2ME and J2EE
solutions, hosts both kSOAP and kXML. Both toolkits are available through
the Enhydra Public License, and come packaged in a single JAR for use in a
SOAP environment handy if the MIDlet needs to parse additional,
non-SOAPrelated XML.
One of kSOAP's biggest strengths is its relative simplicity. Most
enterprise-level SOAP toolkits often rely on the use of a Web Services
Description Language (WSDL) generated proxy object to make function calls.
This is useful if a service has a published description and when the toolkit
requires the instantiation of several different client and transport
objects. While kSOAP doesn't support WSDL, it does make calling a service
relatively painless. Only two objects are required: the SOAPObject and
Http-Transport.
In addition, kSOAP makes it very easy to capture fault data. The toolkit
maps all SOAP faults to an exception object known as SoapFault. In this
manner, a Fault can be caught and handled like any other exception in the
MIDlet.
The development environment used for this article is Sun's J2ME Wireless
Toolkit. To build an application using kSOAP, first download the
ksoap-midp.jar file from
ksoap.enhydra.org. Place this JAR in the
application's lib/ directory. The WTK will add all JARs in lib/ to the
classpath.
Import all the necessary classes by inserting the following statements:
import org.ksoap.*;
import org.ksoap.transport.*;
Now your MIDlet is ready to use kSOAP.
The taxCalcClient MIDlet
Up to now I've been laying the foundation for developing the
taxCalcClient MIDlet. Now I'll dissect the construction of this application.
While only specific relevant portions of the source code will be explored,
you can download the complete application with source code from below.
As shown in Figure 2, the first screen the user interacts with is a
TextBox requesting the amount of the purchase. This is the subtotal value
that will be passed to the taxCalc() function on the SOAP server. Looking at
the MIDlet source we see the private variable rate has been set to +7%. This
is the tax percentage that will be passed to the taxCalc() function as the
rate parameter.
The Calculate event is fired when the right soft key is pressed. This
calls the getTax() function (see Listing 4), which provides MIDlet's SOAP
functionality. The heart of this function is:
try {
SoapObject client = new
SoapObject("urn:soap-whytewolf-ca:
taxcalc","taxCalc");
client.addProperty("rate",rate);
client.addProperty("sub",amount);
HttpTransport ht = new
HttpTransport("http://www.whytewolf
.ca/ws/taxCalc.php",
"urn:soap-whytewolf-ca:taxcalc#taxCalc");
taxMsg.setText("$" + ht.call(client));
}
To prepare our client to use a SOAP server we create a new SoapObject,
passing the constructor the Namespace URI for the SOAP call and the name of
the function being called. This SoapObject also needs to be prepared with the parameters the function accepts.
Recall our taxCalc function took two strings: rate and sub. Each of these
properties and the values to be passed through them are added to the client
object using the addProperty method, which takes the name of the property
and the value of the property as parameters.
While these are provided as name-value pairs, some SOAP servers don't
check the property name and instead use the values in the order they were
passed. For this reason I suggest adding properties in the order the
function would expect the parameters, in this case, rate first and then sub
(see Figure 1 for the function's signature).
We now create a new HttpTransport object, which will provide the needed
functionality to actually call the SOAP service. We pass the constructor the
endpoint URL for the service and the SOAPAction URI we'll be calling the
service with. To execute the service, call it using the call() method,
passing call() the SoapObject that will invoke the service. Call() returns whatever value is returned from the SOAP service, in
this case, a float that we cast to a string and use in the StringItem of our
second screen to display the results of the tax calculation. Figure 3 shows
the resulting screen, assuming all goes well.
As has already been indicated, the taxCalc SOAP service will not accept
negative or zero-valued rates or subtotals. Should a client send anything
other than a nonzero value for the rate or subparameters, the service will
return a SOAP fault.
The first catch clause in getTax() will catch any SOAP fault exception
the call() method throws. As seen in the following code, retrieving the SOAP
fault information is quite simple:
catch (SoapFault sf){
taxMsg.setLabel("FAULT:\n");
String faultString = "Code: " + sf.faultcode + "\nString: " +
sf.faultstring;
taxMsg.setText(faultString);
}
The SOAP fault is stored inside an exception object known as SoapFault
(quite a convenient name). Any of the four fault fields can be retrieved
through their named properties, sf.faultcode, sf.faultdetail, sf.faultactor,
or sf.fault string. As shown in the example, it's quite easy to output these
into the StringItem to indicate to the user a fault has occurred (see Figure
4).
Having handled the fault, the MIDlet allows the user to go back to the
first screen and correct the data.
Conclusion
SOAP can be a very complex realm to explore, especially the XML
mechanisms used to transfer data between disparate systems, languages, and
toolkits. The wireless world of J2ME and MIDlets requires tools of small
stature and great power. Luckily when it comes to SOAP, the kSOAP toolkit
provides not only small size and great functionality, but also relative
simplicity and ease of use for the developer. Using kSOAP, a J2ME developer
can develop complex SOAP Web services clients in a remarkably short time.
Resources
SOAP 1.1W3C Note:
www.w3.org/TR/SOAP
kSOAP toolkit:
http://ksoap.enhydra.org
Java 2 Platform, Micro Edition: http://java.sun.com/products/j2me
Mobile Information Device Profile:
http://java.sun.com/products/midp
Sun J2ME Wireless Toolkit:
http://java.sun.com/products/j2mewtoolkit
Gupta, S. "Combining RMI with SOAP." Java Developer's Journal. SYS-CON
Media. Vol. 7, issue 6.
NuSOAP PHP SOAP toolkit:
http://dietrich.ganx4.com/nusoap/index.php
Apache Axis:
http://xml.apache.org/axis
Author Bio
S.D. Campbell is a senior consultant with ContractBank (www.contractbank.
com/CWIC) and heads the Web development
program at Applied Multimedia Training Centers in Calgary, Alberta. His
consulting company, Whyte.Wolf, helps small businesses move into wireless
and Web services.
scampbell@whytewolf.ca
Listing 1: SOAP request
POST /ws/taxCalc.php HTTP/1.1
SOAPAction: urn:soap-whytewolf-ca:taxcalc#taxCalc
Content-Type: text/xml
Content-Length: 557
User-Agent: kSOAP/1.0
Host: www.whytewolf.ca
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body SOAP ENV:encodingStyle="http:
//schemas.xmlsoap.org/soap/encoding/">
<taxCalc xmlns="urn:soap-whytewolf-ca:taxcalc" id="o0" SOAP-ENC:root="1">
<rate xmlns="" xsi:type="xsd:string">7</rate>
<sub xmlns="" xsi:type="xsd:string">856</sub>
</taxCalc>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Listing 2: SOAP response
HTTP/1.1 200 OK
Date: Mon, 12 Aug 2002 01:31:10 GMT
Server: Apache/1.3.14 (Unix) mod_perl/1.24 PHP/4.0.6 FrontPage/4.0.4.3
mod_ssl/2.7.1 OpenSSL/0.9.6
X-Powered-By: PHP/4.0.6
Status: 200 OK
Connection: Close
Content-Length: 510
Content-Type: text/xml; charset=UTF-8
<?xml version="1.0"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:si="http://soapinterop.org/xsd">
<SOAP-ENV:Body>
<taxCalcResponse>
<noname xsi:type="xsd:float">915.92</noname>
</taxCalcResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Listing 3: SOAP fault
HTTP/1.1 200 OK
Date: Mon, 12 Aug 2002 01:32:12 GMT
Server: Apache/1.3.14 (Unix) mod_perl/1.24 PHP/4.0.6 FrontPage/4.0.4.3
mod_ssl/2.7.1 OpenSSL/0.9.6
X-Powered-By: PHP/4.0.6
Status: 500 Internal Server Error
Connection: Close
Content-Length: 607
Content-Type: text/xml; charset=UTF-8
<?xml version="1.0"?>
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:si="http://soapinterop.org/xsd">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>Client</faultcode>
<faultactor></faultactor>
<faultstring>Must supply a non-zero subtotal.</faultstring>
<faultdetail></faultdetail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Listing 4: getTax() function
private void getTax() {
amount = input.getString();
try{
SoapObject client = new
SoapObject("urn:soap-whytewolf-ca:taxcalc","taxCalc");
client.addProperty("rate",rate);
client.addProperty("sub",amount);
HttpTransport ht = new
HttpTransport("http://www.whytewolf.ca/ws/taxCalc.php",
"urn:soap-whytewolf-ca:taxcalc#taxCalc");
taxMsg.setText("$" + ht.call(client));
}
catch (SoapFault sf){
taxMsg.setLabel("FAULT:\n");
String faultString = "Code: " + sf.faultcode + "\nString: "
+ sf.faultstring;
taxMsg.setText(faultString);
} catch (Exception e) {
e.printStackTrace();
taxMsg.setLabel("ERROR:\n");
taxMsg.setText(e.toString());
}
getForm.addCommand(backCommand);
getForm.setCommandListener(this);
display.setCurrent(getForm);
}