Level: Intermediate Shu Fang Rui (shufangrui@gmail.com), Software Engineer
03 Jan 2006 From when to choose mobile Web services to the overall design guidelines to the value types to use in mobile Web services, this article addresses many of the design considerations you need to ponder when developing Web services for mobile devices. It also covers many of the best practices for designing mobile Web services. Learn how to decide when to use Web services, what things to consider when you design Web services, and what to keep in mind when planning mobile Web services.
Web services is an integration technology. It best demonstrates its value when integrating heterogeneous systems because it supports many kinds of programming languages, run times, and networks. When there is a need to connect applications from incompatible environments, the stage is set for Web services. Through Web services you can connect your business applications from Java™ 2 Platform, Enterprise Edition (J2EE) to .NET. You can also integrate an application in the Windows™ operating system with one running in Linux™. In this article, I offer some important design considerations for mobile Web services and show you how to use the best practices to enable them.
First, I'll talk about what to consider before you start your design.
Before you start
Before you begin to design the architecture of your whole system, you have to make the following decision -- when to use mobile Web services and when not to.
For mobile devices, Web services is one of the best ways to use the powerful computing capabilities of workstations. Java Specification Request 172 (JSR-172) defines the Web services API for the Java 2 Platform, Micro Edition (J2ME) platform. Because mobile devices are programmed mainly from the perspective of a client, and is the service consumer, only a subset of remote service invocation API (JAX-RPC) and a subset of JAXP (Java API for XML Parsing) are needed for this article.
Mobile Web services is mainly designed so embedded devices can consume the service provided by the server; in other words, mobile Web services are designed from the perspective of the Web services consumer, to enable lightweight devices to share the computing capability and database with the server.
Mobile Web services seamlessly integrates two different applications running on different platforms and provides interoperability between them. In general, there are three kinds of integration techniques that can be applied when concerned with the participation of mobile devices:
- Socket communication
- Web services
- Messaging techniques (such as WebSphere® MQ Everyplace)
Compared with socket communication and messaging techniques, there are some advantages that make Web services stand out. Web services uses Extensible Markup Language (XML) to transfer messages (which contain data information that is well structured) and Simple Object Access Protocol (SOAP), which provides Web services with the capability to transfer objects. If you are using socket communication, you are fully responsible for defining the structure of data to be transferred. And, if the client and server are written using different programming languages (such as in C++ and Java programming), your workload gets larger -- you have to take charge of the data-transfer and the encoding details in C++ and Java programming.
Messaging software can be a solution, but if performance is a concern, and you're not worried about the level of transaction and security, messaging software really is not a very good option. It takes a lot of time and effort to manage the security issues and it is quite possible that your customer will be standing outside your door asking "Why it is so slow?".
I'm not going to dictate what your choice should be, instead I gave you some reasons why Web services can be a good choice.
One reason might be for server-side programming. As good a mechanism as Web services is, it is still incapable of fulfilling critical real-time processing requirements due to the cost of XML parsing, and transmitting and receiving SOAP messages. This leads to two general design considerations:
- Each Web service invocation costs significantly more than a comparable generic HTTP access or proprietary messaging approach, so when you are mostly concerned with performance, you might need to choose another technique first.
- Because of overhead, you don't want to choose Web services when you just want to communicate between layers of an application. For example, don't put it between the view and the controller layers of an application.
Now, let's pretend you've decided to use Web services. What are the overall design considerations?
After you've decided to use Web services
The next issue you need to consider is the overall design for Web services. These general design guidelines apply:
- Manage the granularity of your Web services.
- Define the Web service interface first, then implement it.
- Use Document/literal as your encoding style.
- Give preference to JavaBean components over Enterprise JavaBean (EJB) components as the service provider.
- Avoid deep nesting of XML elements that may greatly prolong the time of parsing, marshalling, and demarshalling.
Take a look at these design considerations in detail.
Manage Web services granularity
A few things to remember about granularity management:
- Always give preference to coarse-grained Web services; never use fine-grained Web services invocation between distributed systems.
- Web services is a good toolkit, but it can greatly affect the performance of your application when you use it in a fine-grained fashion because the overhead of the XML parsing, serialization, and deserialization is really high; it can take up more than 30 percent of your processing time.
I use a logon function in a Task Management system as an example in this article. A driver first logs into the system, and if the login is successful, there will be two lists of tasks displayed on the screen.
There are two solutions for this logon issue:
- One is to invoke the logon method first, and when the method invocation returns true, invoke a method to fetch tasks consequently.
- The other is get things done in a single step. If the driver has logged in the Task Management system, return the role of the driver as well as the assigned tasks and initiated tasks. If the logon fails, return the information indicating the driver has not logged in to the system.
For the first solution, it is a fine-grained logon system, and displaying the task list requires two invocations of the Web service; this can result in significant delays. The second solution is a large-grained one that returns all the information you need in a single invocation and ensures minimal effects due to network latency and system I/O.
Define the Web service interface first, then implement it
This is not only applicable from the view of mobile Web services, but also from the perspective of Web services design and from a higher level, the perspective of object-oriented software design. As you know, an interface is the contact between clients and service providers, and it is important that it stays stable (because, as you know, implementations are prone to change).
The definition of an interface of a function is quite direct and intuitive -- just think about:
- your goal
- what information you want to take in
- what kind of result you want to return to the client
Then you just write down the interface and it guides you through the implementation details.
This is a simple design guideline. Knowing your goal is very important, it can drive you to write your test case down and direct you to write the function implementation, and this is also the reason why test driven development (TDD) makes its way in the world of various development techniques.
Use Document/literal as your encoding style
Currently, there are three operation modes supported by standard JAX-RPC:
- RPC/encoded. Its strengths are its straightforward nature and that the receiver has an easy time dispatching this message to the implementation of the operation. Its weakness is that its type encoding info is usually just overhead, which degrades throughput performance.
- RPC/literal. Its strengths are the same as the encoded version and it is compliant with the WS-I Organization specifications (see Resources).
- Document/literal. Its strengths are that there is no type encoding info, and any XML validator can validate the messages. Its weakness is that the operation name in the SOAP message is lost, so dispatching can range from difficult to impossible.
In the literature, you may also find a mode called Document/encoded (not many use this) and Document/literal wrapped (defined by Microsoft but with no specification; its major weakness is that it is more complex than the other modes). A thorough explanation of these operation modes can be found in "Which style of WSDL should I use?" in Resources.
In these modes, only RPC/literal and Document/literal are supported by the WS-I standards. For mobile Web services, the JAX-RPC implementation must use Document/literal for the mapping of a Web Service Definition Language (WSDL)-based service description to the corresponding Java representation. Therefore, you are safest if you only use Document/literal as your encoding style.
JavaBeans components over EJB components as a service provider
There are two kinds of service providers to think about when exposing your service using the Java programming language:
- Generating your Web service from a JavaBean component
- Using a stateless session EJB component
Though EJB components are really useful in some conditions, most of the time JavaBeans components are a better choice, especially when developing mobile Web services. Generating a service provider using a JavaBean component is simple and easy to implement, and it's more stable than its counterpart session EJB components. However, if you want to expose the Web service from an existing J2EE application developed using EJB components, then use EJB components.
Avoid deep nesting of XML elements
Deep nesting, like an array of arrays, array of complex types, a complex type containing another self-defined complex type, and so on, will greatly influence the performance of Web services. Listing 1 shows the XML description as an example -- an array of a self-defined datatype Task class:
Listing 1. A self-defined data type
import java.io.Serializable;
public class Task implements Serializable {
/**
* The id of the task
*/
private int taskID = 0;
/**
* Owner name of the task
*/
private String ownerName;
/**
* public default non-argument constructor
*
*/
public Task() {
}
/**
* Constructor of the Task class
*
* @param taskID
* id of the task
* @param ownerName
* owner name of the task
*/
public Task(int taskID, String ownerName) {
this.taskID = taskID;
this.ownerName = ownerName;
}
/**
* @return Returns the ownerName.
*/
public String getOwnerName() {
return ownerName;
}
/**
* @param ownerName
* The ownerName to set.
*/
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
/**
* @return Returns the taskID.
*/
public int getTaskID() {
return taskID;
}
/**
* @param taskID The taskID to set.
*/
public void setTaskID(int taskID) {
this.taskID = taskID;
}
}
|
If there is a method returning an array of Task as defined in Listing 1, and the source code of the method is in the following listing, the method getTasks() returns an array of five Task objects as shown in Listing 2.
Listing 2. Method returning an array of self-defined data type
public Task[] getTasks(String name){
Task[] tasks = new Task[5];
for(int i=0; i<5; i++){
tasks[i] = new Task(i, name);
}
return tasks;
}
|
When exposing a Web service using the JavaBean component to which getTasks() belongs (as in Listing 3), the Task class is mapped to tn2:Task in which tn2 is the namespace of the Task class.
Listing 3. XML datatype in WSDL definition
<complexType name="Task">
<sequence
<element name="ownerName" nillable="true" type="xsd:string"/>
<element name="taskID" type="xsd:int"/>
</sequence>
</complexType>
|
Also, the datatype Task[] is mapped to ArrayOf_tn2_Task; the XML description for ArrayOf_tn2_Task is shown in Listing 4:
Listing 4. XML definition for ArrayOf_tn2_Task
<complexType name="ArrayOf_tns2_Task">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="Task"
nillable="true" type="tns2:Task"/>
</sequence>
</complexType>
|
As shown in Listing 4, long XML descriptions are generated for a simple array of a self-defined complex type. As a contrast, a single String type in the Java language is mapped to xsd:string without generation of a complexType element; primitive types like boolean, int, and byte are mapped to xsd:boolean, xsd:int, and xsd:byte, respectively.
You may have noticed that there is a conflict between the nesting of XML elements (avoiding deep nesting) and granularity considerations (using coarse granularity). In practical use, there should be a balance between nesting and granularity. When you are more concerned with the performance of the application, you should fine-tune these two considerations to make a better solution.
Let's get mobile
I've discussed some guidelines for designing Web services, now I will focus on design considerations for mobile Web services. Mostly, they are the things you need to think about when using the JAX-RPC value type for mobile Web services. A JAX-RPC value type (in JSR-101) is a Java class whose value can be moved between a service client and service endpoint. To be a conformant value type, a series of rules must be followed. I only list several of them, the ones most relevant to this article:
- You must have a public default constructor.
- You must have a setter and getter method for the fields that need to transfer over the network.
- You should use an array when dealing with a collection of data.
- There are some preferred datatypes in mobile Web services.
- Watch for pitfalls when dealing with input and output parameters.
You must have a public default constructor
The default constructor is used by the SOAP run time environment to construct the object during the deserialization process. If you try to write a value type (also called a data transfer object) without a public default constructor, an error occurs when the JAX-RPC run time tries to serialize and deserialize the data object. For an IDE like IBM Rational® Application Developer (RAD) 6.0, the serialization and deserialization helper classes (the Java classes generated by RAD with the suffix _Helper, _Ser, and _Deser) won't be generated for the datatype, so it generates a serialization error when invoking the method related with self-defined datatypes. A non-argument constructor ensures the object can be constructed remotely from a serialized state.
You must have a setter and getter method for network-transfer fields
First, take a look at the source code for class FailTask in Listing 5:
Listing 5. Definition for FailTask class
public class FailTask {
/**
* The owner of the task
*/
private int ownerid;
/**
* The name of the task
*/
private String name;
/**
* Default public non-argument constructor
*
*/
public FailTask(){
}
/**
* Constructor of FailTask class
* @param ownerid Owner of the task
* @param name Name of the task
*/
public FailTask(int ownerid, String name){
this.ownerid = ownerid;
this.name = name;
}
/**
* Getter method
* @return the ownerid of the task
*/
public int getOwnerid(){
return ownerid;
}
/**
* Setter method
* @param ownerid the ownerid to be set
*/
public void setOwnerid(int ownerid){
this.ownerid = ownerid;
}
}
|
You can add the method shown in Listing 6 to a Web service, which will return the single FailTask object.
Listing 6. Method returning FailTask object
public FailTask getFailTask(int ownerid, String name){
return new FailTask(ownerid, name);
}
|
When invoking the getFailTask() method with parameters 1 and Rachel in the Universal Test Client provided in RAD 6.0, the response is like the one shown in Figure 1.
Figure 1. The response view in Universal Test Client
Where does the name field go? It's not there because I didn't provide the name field with the getter and setter method. Setter and getter methods are two methods that must be provided. As in the FailTask_Ser class, the name field getter method is used to write the name field value to the SOAP message. In the FailTask_Deser class, the name field setter method is used to set the name value of the deserialized FailTask object.
You should use an array when dealing with a collection of data.
More or less, you must work with a collection of data objects to efficiently use Web services. But, here's a warning: Things get a little trickier when dealing with a lot of value types, so here are some issues to take into consideration.
When a dynamic length of an array is needed, think ArrayList. You've heard again and again that ArrayList is more efficient than Vector if you don't have synchronization as a consideration. Unfortunately, the JSR-101 JAX-RPC specification does not mandate the support for Java Collection types. Some Web services engines may not provide support for ArrayList. For example, the IBM Web services engine only officially provides support for a small subset of classes in the Java Collection Framework, including java.util.Vector, java.util.HashTable, andjava.util.HashMap.
Then, how about trying Vector, another dynamic array? If generating the stub files in the same platform, it works fine. But if you're generating in a different platform, you'll see problems. For example, in the Web Service Definition Language (WSDL) file, the Vector or other Collection types are mapped to ArrayOfAnyType. The other platform may not know which Collection type it should map it to, and the data elements contained in Vector are also mapped to AnyType in WSDL. (The big problem here is that the other platform may not know what type AnyType stands for.) For the detailed information on this topic, see "Web services programming tips and tricks: Improve the interoperability between J2EE and .NET" in Resources.
The last reason for using an array, which will make all the other explanation unnecessary, is that in mobile Web services, Java Collection types are not supported. This means you will probably fail to generate stub files for mobile Web services clients from a well-formed WSDL file.
Some preferred datatypes in mobile Web services
Use primitive type long to transfer your Date or Calendar representation
For standard JAX-RPC run time implementations, there are two supported standard-type mappings:
- Java types to XML datatypes
- XML datatypes to Java types
In the JAX-RPC subset specification, only the second type of mapping is required. Table 1 shows the brief list of mappings from supported XML datatypes to Java types; for more information, see JSR-172.
Table 1. Mappings from XML datatypes to Java types
| Simple XML type | Java type |
|---|
xsd:string | java.lang.String | xsd:int | int | xsd:long | long | xsd:short | short | xsd:boolean | boolean | xsd:byte | byte | xsd:float | java.lang.String or float | xsd:double | java.lang.String or double | xsd:QName | javax.xml.namespace.QName | xsd:base64Binary | byte[] | xsd:hexBinary | byte[] |
You can see clearly in Table 1 that no such elements as xsd:dateTime, xsd:date, or xsd:time exist in the list, while these three elements are exactly the XML types mapped to java.util.Calendar in a standard JAX-RPC specification. Note that in Java datatypes to XML types mapping defined in JAX-RPC1.1, java.util.Date is mapped to xsd:dateTime.
Then what should you use when trying to transfer date or time representation? Use the long type time instead. The long type date format is independent of the time representation in different time zones and because it is a primitive type, it is more efficient than other kinds of java objects.
Watch for the use of float and double type
First, as you know, the CLDC 1.0 (Connected Limited Device Configuration) does not provide the float and double native types for performance reasons, even though both CLDC 1.1 and CDC provide support for them. Then what do you do if you must work with a Web services client targeting CLDC 1.0? The JSR-172 gives you part of the answer.
To support xsd:float and xsd:double on CLDC 1.0, by default, implementations must generate code that maps these types to java.lang.String. To support configurations and platforms (CLDC 1.1 and CDC) that do provide native support for float and double, stub generator implementations must also be able to generate code that maps these types to the appropriate native Java type. (See Resources for a link to the JSR-172: J2ME Web Services Specification for more information.)
I'll demonstrate with a simple Web service that adds two float numbers (Listing 7).
Listing 7. Adding two float numbers
public class TaskWs {
public TaskWs() {
}
/**
* Adding two float numbers and return their sum
* @param a First number to add,
* @param b Second number to add
* @return The sum of a and b.
*/
public float addTwo(float a, float b) {
return a + b;
}
}
}
|
The XML datatype definition in the generated WSDL definition looks like Listing 8.
Listing 8. The corresponding WSDL definition for Listing 7
<wsdl:types>
<schema targetNamespace="http://ws.test.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://ws.test.ibm.com" xmlns:intf="http://ws.test.ibm.com"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element name="addTwoResponse">
<complexType>
<sequence>
<element name="addTwoReturn" type="xsd:float"/>
</sequence>
</complexType>
</element>
<element name="addTwo">
<complexType>
<sequence>
<element name="a" type="xsd:float"/>
<element name="b" type="xsd:float"/>
<element name="b" type="xsd:float"/>
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>
|
For the Web service client targeting CLDC 1.0, the generated stub is like that in Listing 9.
Listing 9. Generated client stub for CLDC 1.0
public interface TaskWs extends java.rmi.Remote {
public java.lang.String addTwo(java.lang.String _a, java.lang.String _b)
throws java.rmi.RemoteException, javax.xml.rpc.JAXRPCException;
}
|
So when invoking the Web service on CLDC 1.0, you must provide two String parameters with the addTwo() method, while for a Web service client targeting platform CLDC 1.1, the generated service interface is like the one described in Listing 10:
Listing 10. Generated client stub for CLDC 1.1
public interface TaskWs extends java.rmi.Remote {
public float addTwo(float _a, float _b)
throws java.rmi.RemoteException, javax.xml.rpc.JAXRPCException;
}
|
This maps xsd:float to the float type in the generated client stub. See the differences between stubs generated for CLDC 1.0 and CLDC 1.1?
Be careful with float and double types when developing Web services for mobile devices, because the stubs generated for CLDC 1.1 that use the native mapping to float and double cannot be loaded by the CLDC 1.0 virtual machine implementations. Developers of Java 2 Platform, Micro Edition (J2ME) applications targeting both CLDC 1.0 and CLDC 1.1 should use the default mapping to java.lang.String to achieve the most reusability.
Pitfalls when dealing with input and output parameters.
The JSR-172 specifies that all parameters that are passed as copy and return values are created as copy. But when dealing with a collection of data, the distinction between a null array (which returns null) and an empty array (which returns itself) needs closer attention.
My suggestion is to avoid using empty arrays as much as you can. When dealing with mobile Web services, an empty array can be a problem.
Pretend that you need to return an array of task objects. The original code is shown in Listing 11.
Listing 11. A simple value object
public class SimpleTask {
/**
* The name of the task
*/
private String name;
/**
* The default constructor
*
*/
public SimpleTask() {
}
/**
* @return Returns the name of the task.
*/
public String getName() {
return name;
}
/**
* @param name
* The name to set.
*/
public void setName(String name) {
this.name = name;
}
}
|
A Web service implementation looks like Listing 12.
Listing 12. Method returning an array of value objects
1 public SimpleTask[] getSimpleTasks(){
2 SimpleTask[] tasks = null;
3 /*
4 * Your code dealing with DB goes here
5 * ....
6 * tasks = ...
7 */
8 return tasks;
9 }
|
Everything works great in this example when generating Web service stubs and when testing the Web service using the generated stub. But because you are required to do a complete code review using Jtest before the ending of the iteration one phase, when you run the Jtest on the code snippet, you see a recommendation saying "Return zero-length arrays instead of null." After dwelling on that for a while, you agree with Jtest's advice. If you return a null array, the clients of the code must write extra code to check whether the return value is null (as shown in Listing 13).
Listing 13. Client code snippet for invoking the Web service
SimpleTask[] tasks = service.getSimpleTasks();
if(tasks != null){
int length = tasks.length;
//do something here
}
|
When you modify the SimpleTask[] tasks = null; (line 2 in Listing 12) to SimpleTask[] tasks = new SimpleTasks[0];, you will only need to write Listing 13 to look like:
SimpleTask[] tasks = service.getSimpleTasks();
int length = tasks.length;
|
After the modifcation, you believe there is no change in the logic of the code and run the client stub to invoke the Web service again, but now exceptions are thrown. Up until now, you've made so many little patches according to the advice of Jtest that you've forgotten what has been modified -- this can cause extra time spent struggling to find what went wrong. What a long, sufferable journey.
So what's the problem? Generally speaking, for a null array of objects such as SimpleTask, the returned SOAP message will look like Listing 14.
Listing 14. SOAP message when returning a null array
<soapenv:Body>
<p147:getSimpleTasksResponse xmlns:p147="http://ws.test.ibm.com">
<getSimpleTasksReturn xsi:nil="true" />
</p147:getSimpleTasksResponse>
</soapenv:Body>
|
For an empty array such as SimpleTask[] tasks = new SimpleTask[0], the SOAP message looks like Listing 15.
Listing 15. SOAP message when returning an empty array
<soapenv:Body>
<p147:getSimpleTasksResponse xmlns:p147="http://ws.test.ibm.com">
<getSimpleTasksReturn />
</p147:getSimpleTasksResponse>
</soapenv:Body>
|
The difference lies between <getSimpleTasksReturn/> and <getSimpleTasksReturn xsi:nil = true>. Figure 2 shows you that an empty array parameter is invalid most of the time. For a self-defined data type (including an array of another self-defined type), don't initialize the class variable to an empty array -- instead, initialize it to null even though the generated WSDL definition of empty array and null array are the same.
Figure 2. Encoding of null and empty array parameters from JSR-172
In conclusion
When dealing with mobile Web services, you need to be much more cautious, because only a subset of the API is supported by the mobile Web services specification. I've shown you some tricks to use when dealing with value types and collection types when you're planning to develop mobile Web services. Also, I've provided the following:
- Pre-design considerations such as when to use Web services and examining the tradeoffs between using Web services or sockets or messaging techniques
- Design considerations for Web services after you've decided to use them, including
- Managing granularity
- Defining the Web services interface
- Using Document/literal as your encoding style
- Why you should use JavaBeans components instead of EJB technology as your service provider
- Why you should avoid deep nesting of XML elements
- The balancing act between nesting and granularity
- Design considerations for mobile Web services when using the JAX-RPC value type, including
- Public default constructors
- Setter and getter methods
- Using array type instead of Java Collection type
- Determining the preferred datatypes
- Dealing with input and output parameters
Resources Learn
-
"Web services programming tips and tricks: Improve the interoperability between J2EE and .NET" (developerWorks, January 2005) shows you how to manage collections, arrays, and primitive data types when dealing with standard Web services.
-
The JSR-172: J2ME Web Services Specification defines an optional package that provides standard access from J2ME to Web services.
-
"Which style of WSDL should I use?" (developerWorks, May 2005) describes the operation modes (plus two more) mentioned in this article: RPC/encoded, RPC/literal, Document/encoded, Document/literal, and Document/literal wrapped pattern.
-
The Web Services - Interoperability Organization (WS-I) is an open industry organization chartered to promote interoperability of Web services across platforms, operating systems, and programming languages.
-
"Tips and tricks: XML does the job" (developerWorks, March 2002) explains how to use XML-RPC to define your mobile Web services client.
-
"Cross-platform programming with Java technology and the IBM Web Services Toolkit for Mobile Devices" (developerWorks, February 2003) helps you make sure your Java apps run on as many platforms as possible without modification.
-
JSR-101: Java APIs for XML-based RPC discusses using the JAX-RPC value type.
-
"Deliver Web services to mobile apps" (developerWorks, January 2003) shows you how to access Web services using J2ME-enabled mobile devices and the kSOAP library.
-
"Develop Web services clients for mobile devices" (developerWorks, March 2004) guides you through the necessary steps to build mobile Web services clients on J2ME MIDP devices.
-
The Connected Limited Device Configuration 1.0 (CLDC; JSR-30 and JSR-139) defines the base set of APIs and a virtual machine for resource-constrained devices. Delivers a powerful Java platform for developing applications to run on devices with limited memory, processing power, and graphical capabilities when combined with the Mobile Information Device Profile (MIDP).
-
The Best practices for Web services series (developerWorks, October 2002) details design considerations for Web services.
-
Peruse the Safari eReference Bookstore for many more helpful titles specific to Mobile and other technologies.
-
The developerWorks Wireless technology zone specializes in articles covering all manners of mobile- and pervasive-based solutions.
Get products and technologies
-
Jtest automates Java unit testing and code compliance, auto-generates JUnit test cases from a running application, and tests individual classes or large, complex applications.
-
The Web Services Tool Kit for Mobile Devices (alphaWorks) is a "graduated" technology -- find out where you can see it in action.
Discuss
About the author  | 
|  | Shu Fang Rui is a graduate student from Shanghai Jiaotong University, China. She is interested in wireless technology and Web services. Besides traveling, she also likes several kinds of sports. |
Rate this page
|