The Web at your (machine’s) service
Use Web services to deliver information to a cell phone via SMS
The World Wide Web changes constantly. Since its inception, it has expanded exponentially, grown easier to navigate, and adapted effortlessly to new and innovative uses. However, thus far the Web’s interface has been largely friendly to humans but unfriendly to machines.
Current technologies open up the Web only to humans through HTML links and URLs. Just imagine the services we could provide if machines could suddenly discover, traverse, and navigate the myriad of Web servers to facilitate true wide-area collaboration. Current approaches prove somewhat awkward: a common technique screen-scrapes HTML and mimics HTTP sessions to transfer data. Techniques such as these are not only incomplete (e.g., they lack a discovery mechanism) and unscalable (e.g., if one field on an HTML form changes, the whole interface can crash, so each server needs its own custom coding); they also prove difficult to maintain. Web services, on the other hand, provide a well-defined discovery and communication framework based on XML and HTTP that facilitates interaction between machines; they offer true peer-to-peer distributed computing over the Web. The advent of Web services has made the World Wide Web truly complete; they provide the missing link — a machine-friendly user interface (UI) to the Web.
Admittedly, these concepts are not entirely new; distributed object-oriented technologies like CORBA and Remote Method Invocation (RMI) sought to provide similar services. Such core concepts as a service registry and TCP/IP-based communication protocol are common to both CORBA (registry/agent and Internet Inter-ORB Protocol (IIOP)) and Web services (UDDI and SOAP). But CORBA failed because it proved quite complicated and expensive to implement — both in terms of cost and performance. Furthermore, the object bus could not create a genuine heterogeneous environment consisting of multiple hardware and software platforms. Web services, on the other hand, rely on ubiquitous technologies like XML and HTTP, and thus stand a fighting chance of being that ultimate Web glue.
This article develops a clearer understanding of Web services and illustrates a hands-on approach to developing an example service. We use our CellQuotes
application presented in “Deliver Cellular Text Messages with SMS” (JavaWorld, March 2001) and demonstrate how to publish it as a Web service. Many expect SMS to be the next killer app for mobile phones; if Web services can provide a seamless coupling between the Web and mobile phones, we’ll have an application that rocks, don’t you think?
Web services 101
Web services offer the following advantages:
- They are based on open and ubiquitous standards: Web services use omnipresent protocols such as HTTP for communication and XML for information exchange. Thus, they can work across hardware and software boundaries. For example, a Java Web service could easily talk to a COBOL Web service as long as HTTP and XML are in the mix.
- They allow creation of business service stacks: By simply adding another layer, Web services expose software systems over the Internet. That allows the creation of value-added service stacks by melding discrete Web services over a network.
- They enjoy broad-based industry support: Efforts like Microsoft’s Hailstorm and Sun’s ONE (Open Net Environment) are slowly turning Web services into a reality. IBM and Oracle are also major players in the Web services space. In addition, the open source movement’s solid support ensures widespread developer interest.
Web services’ greatest potential lies in their ability to provide business service stacks that truly integrate individual offerings. Imagine writing your own Web service agent that every morning collects and delivers information relevant to you (e.g., the HP-Compaq merger) from resources like the Wall Street Journal, the Economist, and BusinessWeek. Similar services exist today, but Web services help standardize machine-to-machine interaction to create new service breeds that integrate the Web as one large computer. On the supply side, you could publish your revolutionary service to millions of people worldwide without worrying about custom coding and interoperability issues.
Core enabling technologies
Several new technologies support Web services, but because they build on pre-existing technologies like XML and HTTP, they’re not as new as you might think. Lets briefly look at some:
SOAP (Simple Object Access Protocol)
SOAP is the transport that facilitates the exchange of XML messages between machines. It performs business method requests as XML documents and supports a variety of lower-level protocols, such as HTTP(S) and SMTP (Simple Mail Transfer Protocol). The communication mechanism consists of request and response XML documents that have an envelope over them.
WSDL (Web Services Description Language)
An XML language that permits standardized description of Web services, WSDL describes the interface, semantics, and administration-related information of a Web service call. It allows simple services to be quickly and easily described, documented, and discovered. Several freely available tools permit automatic WSDL generation from actual code (such as a Java class), thus making the developer’s job easier.
UDDI (Universal Description Discovery and Integration)
UDDI provides a specification that permits the publication and location of services in a universal service registry. It publishes an available service along with its call interface and semantics, thereby providing a discovery and meeting point for service providers and consumers. The UDDI specification defines four key types of information that might be needed to facilitate service use:
- Business information (
businessEntity
element): Contains information such as the business name, contact details, and so on. It might also include information categorized in a way similar to the format of the Yellow Pages. - Service information (
businessService
element): Contains information about services relevant to a particular business function or a given service category. EachbusinessEntity
might hold severalbusinessService
s. - Binding information (
bindingTemplate
element): Contains information required to call a Web service, such as network access point, supported interfaces, and so on. EachbusinessService
might hold severalbindingTemplate
s. - Specification information (
tModel
s): To invoke a Web service, you must know a service’s location and the kind of interface the service supports. ThebindingTemplate
indicates the specifications or interfaces a service supports through references to specification information. Such a reference is called atModelKey
, and the data structure encapsulating the specification information is called atModel
.tModel
s are reusable due to their use of references.
Figure 1 illustrates the relationship between the different components explained above.
WSDL and UDDI
WSDL and UDDI together provide a powerful mechanism for the automatic description, discovery, and integration of Web services. UDDI features a universal registry that can hold any information about a Web service. Different people might use this information for different purposes. For example, a developer could use the registry to obtain the technical information required to build her own service and/or consume an existing service. A market researcher could use it to understand what services are most in demand. WSDL enables the uniform description of a Web service in an open manner — it is UDDI’s lingua franca. Thus, when used together, UDDI (which provides global visibility and accessibility) and WSDL (which provides standardized and portable service descriptions) pack quite a punch. Usually, developers use WSDL to describe Web services’ semantics, such as method signatures, return types, and so on, in an implementation-independent way. This WSDL description is then embedded in the service’s UDDI entry (usually through tModel
s) along with the information needed to actually call that service (through bindingTemplate
s).
The process for using a Web service consists of discovery, connection, and consumption. We outline the steps here in greater detail:
- A service provider creates a Web service and uses WSDL to publish the service in the UDDI registry.
- A service consumer locates and requests the registered service by querying the UDDI registry.
- Using SOAP, the service consumer writes an application to bind to the registered service and establishes a communication channel. The WSDL description provides the method signatures.
Figure 2 illustrates the steps outlined above.
SMS Web service
Now, let’s get back to the nitty-gritty of developing a real-world Web service — namely, a service that sends text messages to cellular phones. To this end, we will convert the CellQuotes
application presented in “Deliver Cellular Text Messages with SMS” into an application that exploits Web services. For the benefit of those unfamiliar with the application, a brief recap follows.
The CellQuotes
system periodically sends stock quotes to a user’s cell phone using Short Message Service (SMS). The user provides the stocks for which she wants quotes as well as cellular details, such as provider and number. Here are some of the solution’s classes:
CellProvider.java
: The interface that must be implemented to add cellular service providersCellProviderSelector.java
: The factory that returns the appropriateCellProvider
implementation based on the user’s inputsUSCellOne.java
:CellProvider
‘s concrete implementation for the Cellular One service provider
We want to expose the USCellOne.java
implementation as a Web service and suitably modify the application so that it can send SMS messages. Figure 3 shows the major components and their interaction. The blue squares represent servers.
We use the following software for developing the Web service:
- IBM UDDI4J API
- IBM UDDI Test Registry
- Apache SOAP
- Apache Tomcat
To develop the Web service, we follow these steps:
- Generate a WSDL document for the
CellProvider
interface - Publish the WSDL description as a
tModel
- Expose the
USCellOne
implementation as a Web service, deployed on Apache Tomcat - Publish information about this implementation as a
businessService
Generate WSDL document
We used the template provided in the WSDL and UDDI best practices document and changed it to create a description of our CellProvider
interface. You can also use tools, like the Web Services Description Language for Java Toolkit (WSDL4J) or CapeStudio from Cape Clear, to generate the WSDL.
The CellProvider
WSDL description is listed below. While a detailed explanation of the document reaches beyond this article’s scope, if you have a basic familiarity with XML, you should be able to follow it. For further information, please read the WSDL spec in Resources.
<?xml version="1.0"?>
<definitions name="CellText"
targetNamespace="
interface.wsdl"
xmlns:tns="interface.wsdl"
xmlns:soap="
xmlns="
xmlns:xsd="
<message name="getName_IN"/>
<message name="getName_OUT">
<part name="Result" type="xsd:string"/>
</message>
<message name="sendMessage_IN">
<part name="number" type="xsd:string"/>
<part name="message" type="xsd:string"/>
</message>
<message name="sendMessage_OUT">
<part name="Result" type="xsd:boolean"/>
</message>
<message name="isMe_IN">
<part name="id" type="xsd:string"/>
</message>
<message name="isMe_OUT">
<part name="Result" type="xsd:boolean"/>
</message>
<portType name="CellTextPortType">
<operation name="getName">
<documentation>Returns User-friendly Provider name</documentation>
<input message="tns:getName_IN"/>
<output message="tns:getName_OUT"/>
</operation>
<operation name="sendMessage" parameterOrder="number message">
<documentation>Sends a text message to specified number</documentation>
<input message="tns:sendMessage_IN"/>
<output message="tns:sendMessage_OUT"/>
</operation>
<operation name="isMe">
<documentation>Checks whether the implementation is for specified ID</documentation>
<input message="tns:isMe_IN"/>
<output message="tns:isMe_OUT"/>
</operation>
</portType>
<binding name="CellTextSoapBinding" type="tns:CellTextPortType">
<soap:binding style="rpc"
transport="
<operation name="getName">
<soap:operation soapAction="urn:celltext-service"/>
<input>
<soap:body use="encoded" namespace="urn:celltext-service"
encodingStyle= "
</input>
<output>
<soap:body use="encoded" namespace="urn:celltext-service"
encodingStyle="
</output>
</operation>
<operation name="sendMessage">
<soap:operation soapAction="urn:celltext-service"/>
<input>
<soap:body use="encoded" namespace="urn:celltext-service"
encodingStyle="
</input>
<output>
<soap:body namespace="urn:celltext-service" use="encoded"
encodingStyle="
</ouput>
</operation>
<operation name="isMe">
<soap:operation soapAction="urn:celltext-service"/>
<input>
<soap:body use="encoded" namespace="urn:celltext-service"
encodingStyle="
</input>
<output>
<soap:body use="encoded" namespace="urn:celltext-service"
encodingStyle="
</output>
</operation>
</binding>
</definitions>
Publish the WSDL description
Next, we need to publish the CellProvider
WSDL document to a UDDI registry. To do that, we use the IBM UDDI Test Registry. First, set up a free account at the IBM UDDI Test Registry Website. Then use the registry’s Web interface to manually add the tModel
for this interface specification. (You can also do this programmatically.)
Expose the Web service
Contrary to popular perception, exposing the Web service is the easiest part of the process (at least with Apache SOAP). You only need to ensure that the USCellOne
class is accessible via the CLASSPATH
. Assuming that you are using Tomcat with Apache SOAP, simply point the browser to and use the Web interface to deploy the service.
Apache SOAP requires the following basic information to establish a communication channel:
- ID: The service uniform resource name (URN) that identifies the Web service
- Methods: The list of exposed methods
- Class: The class name containing method implementations (of exposed method(s))
In addition to the requirements above, other options are available. As the Apache SOAP documentation provides a full-fledged user’s guide devoted to those options, we skip the details here.
Once the service deploys, you can call your service’s methods with a SOAP request to /servlet/rpcrouter
along with the URN assigned to your service.
Publish the Web service
To facilitate the automatic discovery and correct use of our service, we need to not only publish the router URL and service URN, but also indicate CellProvider
interface’s implementation. Additionally, for the purpose of our CellQuotes
application, we need to designate the cellular service providers to which our service can send text messages. We assume that cell phone users subscribing to a particular cellular service provider have a common prefix in their cell phone numbers when represented in the International Dialing Format. For example, Cellular Provider A’s subscribers might have numbers of the form 001-123-456-xxxx, while Cellular Provider B’s subscribers could have numbers of the form 001-123-789-xxxx. Our Web service publishes a number prefix list to indicate the cellular providers to which it can send messages. Thus, in the above example, if this list includes 001-123-456, our service can send text messages to all of Cellular Provider A’s subscribers.
While this approach proves less than perfect, it suffices for this exercise. However, we would love to learn of any other approaches you might care to present.
To publish a service to a UDDI registry, a businessEntity
must already exist. Assuming one already does, the following code illustrates how to publish a Web service:
public class SaveCellTextService
{
public static void main (String args[])
{
// Publishing requires HTTPS to ensure security
// The following lines set up the environment to use HTTPS
// using JSSE
System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
// The proxy acts as a front to all registry operations
UDDIProxy proxy = new UDDIProxy();
try
{
proxy.setInquiryURL("
proxy.setPublishURL("
// The credentials
AuthToken token = proxy.get_authToken("userid", "password");
/*********************************************
Various data structures that hold all the information
for a Web service. */
Vector tModelInstanceInfosVector, businessServicesVector;
Vector bindingTemplatesVector;
TModelInstanceDetails tModelInstanceDetails;
TModelInstanceInfo interfaceTModel, providerTModel;
InstanceDetails providerDetails;
InstanceParms providerParams;
BindingTemplates bindingTemplates;
BindingTemplate bindingTemplate;
AccessPoint accessPoint;
BusinessServices businessServices;
BusinessService businessService;
/***********************************************/
// Indicate that this service implements CellProvider interface
// by referencing tModel using tModelKey
interfaceTModel = new TModelInstanceInfo("UUID:15DE8C40-991F-11D5-BA0D-0004AC49CC1E");
/**********************************************
Indicate that this service supports the number
prefix mechanism of identifying cellular providers,
and also pass in the list of supported providers */
providerTModel = new TModelInstanceInfo("UUID:80177E70-97F6-11D5-8471-0004AC49CC1E");
providerParams = new InstanceParms("001240271");
providerDetails = new InstanceDetails();
providerDetails.setInstanceParms(providerParams);
providerTModel.setInstanceDetails(providerDetails);
tModelInstanceInfosVector = new Vector();
tModelInstanceInfosVector.addElement(interfaceTModel);
tModelInstanceInfosVector.addElement(providerTModel);
/***********************************************/
tModelInstanceDetails = new TModelInstanceDetails();
tModelInstanceDetails.setTModelInstanceInfoVector(tModelInstanceInfosVector);
/**********************************************
Publish location of Web service and service URN */
bindingTemplate = new BindingTemplate("", tModelInstanceDetails);
accessPoint = new AccessPoint("
rpcrouter#urn:celltext:cingular","http");
bindingTemplate.setAccessPoint(accessPoint);
bindingTemplatesVector = new Vector();
bindingTemplatesVector.addElement(bindingTemplate);
bindingTemplates = new BindingTemplates();
bindingTemplates.setBindingTemplateVector(bindingTemplatesVector);
/************************************************/
// This is the top-level businessService structure
// The business key identifies the service producer
businessService = new BusinessService("","Cingular Wireless Washington (US) CellText Service",bindingTemplates);
businessService.setBusinessKey("BLAH-BLAH");
businessServicesVector = new Vector();
businessServicesVector.addElement(businessService);
// Perform actual publishing to registry
proxy.save_service(token.getAuthInfoString(),businessServicesVector);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
The above code simply creates a businessService
object populated with all the data we want to publish and then publishes the service to the registry. Again, an explanation of all businessService
‘s data structures reaches beyond this article’s scope. See the various UDDI specifications in Resources for more information.
Voilà! Our Web service is now exposed to the whole wide world.
Consume Web services
To send SMS messages through this Web service, we need to consume the service. To do that, we retrofit our CellQuotes
application to dynamically discover and connect to this Web service, which meets our interface specifications.
We first write a default local implementation of CellProvider
, which uses SOAP to perform remote procedure calls (RPCs) on a deployed service. Note that this default implementation is required only because we are retrofitting an existing application; otherwise, the SOAP-RPC code could directly integrate with the application. This default implementation assumes that the called services are implementations of the CellProvider
interface. The constructor requires only the router URL and service URN. The following code illustrates the default implementation:
public class ApacheSOAPCellProviderServiceTalker implements CellProvider
{
private URL accessPointURL;
private String soapAction;
/* Constructor requires the URL of the SOAP RPC Router, and the URN of the
target Web service. It is assumed that the target service implements the
CellProvider interface.
*/
public ApacheSOAPCellProviderServiceTalker(String accessPointURL, String soapAction) throws MalformedURLException
{
this.accessPointURL = new URL(accessPointURL);
this.soapAction = soapAction;
}
/* Sends a text message to specified cell number
*/
public boolean sendMessage(String number,String message) throws Exception
{
// Prepare the invocation
Call call = new Call();
call.setTargetObjectURI(soapAction);
call.setMethodName( "sendMessage" );
call.setEncodingStyleURI( Constants.NS_URI_SOAP_ENC );
Vector params = new Vector();
params.addElement( new Parameter( "number", String.class,
number, null ) );
params.addElement( new Parameter( "message", String.class, message,
null ) );
call.setParams( params );
try
{
// Perform RPC
Response response = call.invoke(accessPointURL,"");
// Has an error occurred ?
if(response.generatedFault())
{
Fault f = response.getFault();
System.err.println( "Could not complete RPC. Fault="
+ f.getFaultCode() + ", " + f.getFaultString());
return(false);
}
// All OK, get result
Boolean result = (Boolean) response.getReturnValue().getValue();
return(result.booleanValue());
}
catch( SOAPException e )
{
// Uh-Oh
e.printStackTrace();
return(false);
}
}
...
}
Thus, the default implementation acts as a proxy for the remote service.
Next, we need to modify our CellProviderSelector
so it can talk to a UDDI registry and obtain the list of services that implement our CellProvider
interface. Once the list is obtained, the CellProviderSelector
must determine which of these services can send messages to the target cell phone number. To do that, it uses the number prefix.
The presented application can consume the registered Web service only if the Web service implements our CellProvider
interface and supports the number prefix provider-identification mechanism. Thus, a UDDI registry service complies with our specification if it references the two registered tModel
s (one for the CellProvider
interface and the other for cellular provider identification). In UDDI-speak, a collection of tModelKey
s used while searching the registry is called a tModelBag
. It represents a technical fingerprint for our desired service. Our CellProviderSelector
uses the following algorithm to obtain the list of compliant services:
- Obtain a list of all businesses that provide services matching our
tModelBag
- For each of these businesses, get the list of services that match our
tModelBag
-
- For each service, obtain further details from the registry, such as the binding information, supported cellular providers list, and so on
- Using the obtained details, determine if the service matches our target cell phone number
-
- If yes, create an instance of our local
CellProvider
proxy, passing in the requisite binding information - Otherwise, continue the search
- If yes, create an instance of our local
The following code illustrates this algorithm in action:
public abstract class CellProviderSelector
{
/* Returns appropriate CellProvider implementation for specified ID.
* Null if none was found.
*/
public static CellProvider getProvider(String ID)
{
try
{
// The registry proxy
UDDIProxy proxy = new UDDIProxy();
proxy.setInquiryURL("
/*******************************************
Setup the tModelBag */
Vector tModelKeyStringsVector = new Vector();
TModelBag tModelBag;
tModelKeyStringsVector.addElement("UUID:15DE8C40-991F-11D5-BA0D-0004AC49CC1E");
tModelKeyStringsVector.addElement("UUID:80177E70-97F6-11D5-8471-0004AC49CC1E");
tModelBag = new TModelBag(tModelKeyStringsVector);
/*********************************************/
// Get list of businesses
BusinessList bl = proxy.find_business(tModelBag,null,10);
Vector businessInfoVector = ((BusinessInfos) bl.getBusinessInfos()).getBusinessInfoVector();
System.out.println("nNumber of businesses providing CellText services = " + businessInfoVector.size());
// For each business, get list of compliant services
for(int i=0; i < businessInfoVector.size(); i++)
{
System.out.println("nCurrent Business = " + ((BusinessInfo) businessInfoVector.elementAt(i)).getNameString());
ServiceList sl = proxy.find_service(((BusinessInfo) businessInfoVector.elementAt(i)).getBusinessKey(),tModelBag,null,10);
Vector serviceInfoVector = sl.getServiceInfos().getServiceInfoVector();
System.out.println("tNo. of CellText services = " + serviceInfoVector.size());
// Get details for each compliant service
for(int j=0; j<serviceInfoVector.size(); j++)
{
ServiceDetail sd = proxy.get_serviceDetail(((ServiceInfo) serviceInfoVector.elementAt(j)).getServiceKey());
Vector businessServiceVector = sd.getBusinessServiceVector();
for(int k=0; k<businessServiceVector.size(); k++)
{
BusinessService bs = (BusinessService) businessServiceVector.elementAt(k);
System.out.println("ttService Name = " + bs.getNameString());
Vector bindingTemplateVector = ((BindingTemplates) bs.getBindingTemplates()).getBindingTemplateVector();
// Determine if the service is appropriate
for(int l=0; l<bindingTemplateVector.size(); l++)
{
BindingTemplate bt = (BindingTemplate) bindingTemplateVector.elementAt(l);
AccessPoint ap = (AccessPoint) bt.getAccessPoint();
// Verify the service uses our chosen transport
if(!"http".equalsIgnoreCase(ap.getURLType()))
{
continue;
}
System.out.println("ttAccess Point = " + ((AccessPoint) bt.getAccessPoint()).getText());
Vector tModIIV = ((TModelInstanceDetails) bt.getTModelInstanceDetails()).getTModelInstanceInfoVector();
for(int m=0; m<tModIIV.size(); m++)
{
if("UUID:80177E70-97F6-11D5-8471-0004AC49CC1E".equalsIgnoreCase(((TModelInstanceInfo) tModIIV.elementAt(m)).getTModelKey()))
{
InstanceDetails id = ((TModelInstanceInfo) tModIIV.elementAt(m)).getInstanceDetails();
InstanceParms ip = id.getInstanceParms();
System.out.println("ttSupported Prefixes = " + ip.getText());
// Check if this service can serve our target cell phone number
if(isSuitableService(ip.getText(),ID))
{
// Create instance of local CellProvider proxy
return(createCellProvider(ap.getText()));
}
}
}
}
}
}
}
return(null);
}
catch(Exception e)
{
e.printStackTrace();
return(null);
}
}
/* Creates local CellProvider proxy instance
*/
private static CellProvider createCellProvider(String accessPoint)
{
if(accessPoint == null || accessPoint.indexOf("#") == -1)
return(null);
try
{
String router = accessPoint.substring(0,accessPoint.indexOf("#"));
String urn = accessPoint.substring(accessPoint.indexOf("#")+1);
CellProvider cp = new ApacheSOAPCellProviderServiceTalker(router,urn);
return(cp);
}
catch(Exception e)
{
e.printStackTrace();
return(null);
}
}
/* Checks whether a service can serve our target cell phone
*/
private static boolean isSuitableService(String csvVals, String id)
{
StringTokenizer st = new StringTokenizer(csvVals, " ,:");
String token;
while(st.hasMoreTokens())
{
token = st.nextToken().trim();
if(id.startsWith(token))
{
return(true);
}
}
return(false);
}
}
As evident from the code above, getting to a service’s inner details is slightly tedious. Also, it is sometimes inconvenient to first obtain the list of businesses that provide compliant services, then cycle through each business, and so on. Why not allow access to compliant services directly? Well, enter into the UDDI programmer’s world — that’s just the way the UDDI API is designed right now!
Limitations
Although UDDI and WSDL represent a significant step toward facilitating the automatic discovery and consumption of dynamic Web services, they leave several questions unanswered, and there are areas for improvement. In our example, we can automatically interact only with services known to implement our CellProvider
interface. Could we have a system that looks for remote services to invoke based only on a general purpose? In our example, for instance, the service would not necessarily look for the CellProvider
interface, but would rather search for services that sent cellular text messages.
There are other gray areas associated with Web services:
- How should a service producer charge for a service?
- How do you implement consumer-level security?
- How do you provide distributed transaction semantics?
The absence of a standardized system that addresses these and other concerns hampers the tremendous potential of Web services.
Web services are here to stay
In this article, we converted an existing Java application into a Web service and exposed it to a global audience. We showed how to use UDDI and WSDL to facilitate optimal use of Web services. In addition, we demonstrated how UDDI makes possible the dynamic discovery and consumption of global services. We also illustrated how to modify an existing application client into one that can consume Web services.
Web services maximize business goals by producing smart applications that dynamically discover the best ways to perform business functions. They represent the next leap in distributed object-oriented computing and are certainly here to stay. Although Web services feature some limitations, as the technology grows and matures, those issues will eventually resolve themselves.