Acme Search Service Implementation in a Top-Down Approach

   

Base Service Implementation

This discussion surrounds the base implementation.

Figure 13.3. Recommended development process.

graphics/13fig03.gif

GWSDL Creation

In a top-down development effort, we must start with a GWSDL, and the necessary artifacts that the service should expose to the external world. These artifacts include service interfaces and their hierarchy, public operations, messages exchanged between a client and service, and the exposed public state data information. This information is coded (i.e., except for public state data, which we will add later) in the GWSDL description shown in Listings 13.1, 13.2, and 13.3.

Listing 13.1 describes the complete GeneralSearchPortType.gwsdl.

Listing 13.1. The complete GeneralSearchPortType.gwsdl.
 <?xml version="1.0" encoding="UTF-8"?> <definitions name="GeneralSearch"             targetNamespace="http://org.ph.gridbook.sample/base/general"             xmlns:general="http://org.ph.gridbook.sample/base/general"             xmlns="http://schemas.xmlsoap.org/wsdl/"             xmlns:ogsi="http://www.gridforum.org/namespaces/2003/03/OGSI"             xmlns:gwsdl="http://www.gridforum.org/namespaces/2003/03/                          gridWSDLExtensions"             xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <! The below WSDL fragment imports ogsi.gwsdl, which is part of the core GT3 distribution and defines the OGSI-defined service portTypes.  - -> <import location="../../ogsi/ogsi.gwsdl" namespace="http://www.gridforum.org/namespaces/2003/03/OGSI"/> <types>   <xsd:schema targetNamespace="http://org.ph.gridbook.sample/base/general"               xmlns:general="http://org.ph.gridbook.sample/base/general"               xmlns:xsd="http://www.w3.org/2001/XMLSchema"    attributeFormDefault="qualified" elementFormDefault="qualified">     <xsd:complexType name="SearchType">       <xsd:sequence>         <xsd:element name="phrase" type="xsd:string"/> </xsd:sequence>     </xsd:complexType>     <xsd:complexType name="SearchResponseType">       <xsd:sequence> <xsd:element name="URL" type="xsd:string"/>         <xsd:element name="summary" type="xsd:string"/>               <xsd:element name="cached" type="xsd:boolean"/>     </xsd:sequence>     </xsd:complexType>    <xsd:element name="search"> <xsd:complexType>       <xsd:sequence>         <xsd:element name="searchInput" type="general:SearchType"/>       </xsd:sequence>     </xsd:complexType>    </xsd:element>    <xsd:element name="searchResponse"> <xsd:complexType>       <xsd:sequence>         <xsd:element name="searchResult" type="general:SearchResponseType"/>       </xsd:sequence>     </xsd:complexType>     </xsd:element> </xsd:schema> </types> <message name="SearchInputMessage">   <part name="parameters" element="general:search"/> </message> <message name="SearchOutputMessage">   <part name="parameters" element="general:searchResponse"/> </message> <gwsdl:portType name="GeneralSearchPortType" extends="ogsi:GridService">   <operation name="search">     <input message="general:SearchInputMessage"/>         <output message="general:SearchOutputMessage"/>   </operation> </gwsdl:portType> </definitions> 

Listing 13.2 describes the complete CacheSearchPortType GWSDL.

Listing 13.2. The complete CacheSearchPortType GWSDL.
 <?xml version="1.0" encoding="UTF-8"?> <definitions name="GeneralSearch"              targetNamespace="http://org.ph.gridbook.sample/base/cache"              xmlns:cache="http://org.ph.gridbook.sample/base/cache"              xmlns="http://schemas.xmlsoap.org/wsdl/"              xmlns:ogsi="http://www.gridforum.org/namespaces/2003/03/OGSI"              xmlns:gwsdl="http://www.gridforum.org/namespaces/2003/03/                           gridWSDLExtensions"              xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <! The below wsdl fragment imports ogsi.gwsdl, which is part of the core GT3 distribution and defines the OGSI-defined service portTypes.  - -> <import location="../../ogsi/ogsi.gwsdl" namespace="http://www.gridforum.org/namespaces/2003/03/OGSI"/> <types>   <xsd:schema targetNamespace="http://org.ph.gridbook.sample/base/cache"               xmlns:general="http://org.ph.gridbook.sample/base/cache"               xmlns:xsd="http://www.w3.org/2001/XMLSchema" attributeFormDefault="qualified" elementFormDefault="qualified">     <xsd:complexType name="CacheSearchMessageType">       <xsd:sequence>         <xsd:element name="cacheSearch" type="xsd:boolean"/>         <xsd:element name="phrase" type="xsd:string"/>       </xsd:sequence>     </xsd:complexType>     <xsd:complexType name="CacheSearchMessageResponseType">       <xsd:sequence>         <xsd:element name="URL" type="xsd:string"/>         <xsd:element name="summary" type="xsd:string"/>         <xsd:element name="cached" type="xsd:boolean"/> </xsd:sequence>     </xsd:complexType>    <xsd:element name="cacheSearch"> <xsd:complexType>       <xsd:sequence>         <xsd:element name="cacheSearchInput" type="cache:CacheSearchMessageType"/>       </xsd:sequence>     </xsd:complexType>    </xsd:element>    <xsd:element name="cacheSearchResponse"> <xsd:complexType>       <xsd:sequence>         <xsd:element name="cacheSearchResult" type="cache:CacheSearchMessageResponseType"/>       </xsd:sequence>     </xsd:complexType>     </xsd:element> </xsd:schema> </types> <message name="CacheSearchInputMessage">   <part name="parameters" element="cache:cacheSearch"/> </message> <message name="CacheSearchOutputMessage">   <part name="parameters" element="cache:cacheSearchResponse"/> </message> <gwsdl:portType name="CacheSearchPortType" extends="ogsi:GridService">   <operation name="cacheSearch">     <input message="cache:CacheSearchInputMessage"/>     <output message="cache:CacheSearchOutputMessage"/>   </operation> </gwsdl:portType> </definitions> 

Listing 13.3 describes the complete AcmeSearchPortType GWSDL.

Listing 13.3. The complete AcmeSearchPortType GWSDL.
 <?xml version="1.0" encoding="UTF-8"?> <definitions name="AcmeSearch" targetNamespace="http://org.ph.gridbook.sample/base/acme"    xmlns:acme="http://org.ph.gridbook.sample/base/acme"              xmlns:general="http://org.ph.gridbook.sample/base/general"              xmlns:cache="http://org.ph.gridbook.sample/base/cache"              xmlns="http://schemas.xmlsoap.org/wsdl/"              xmlns:ogsi="http://www.gridforum.org/namespaces/2003/03/OGSI"              xmlns:gwsdl="http://www.gridforum.org/namespaces/2003/03/                           gridWSDLExtensions"              xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <! The below code imports ogsi.gwsdl and other gwsdl's defined earlier  - -> <import location="../../ogsi/ogsi.gwsdl" namespace="http://www.gridforum.org/namespaces/2003/03/OGSI"/> <import location="../../gridbook/base/general_search_port_type.gwsdl" namespace="http://org.ph.gridbook.sample/base/general"/> <import location="../../gridbook/base/cache_search_port_type.gwsdl" namespace="http://org.ph.gridbook.sample/base/cache"/> <types>   <xsd:schema targetNamespace="http://org.ph.gridbook.sample/base/acme"               xmlns:acme="http://org.ph.gridbook.sample/base/acme"               xmlns:xsd=http://www.w3.org/2001/XMLSchema attributeFormDefault="qualified" elementFormDefault="qualified">      <xsd:complexType name="SpellCheckMessageType"> <xsd:sequence>         <xsd:element name="word" type="xsd:string"/>       </xsd:sequence>     </xsd:complexType>      <xsd:complexType name="SpellCheckMessageResponseType">       <xsd:sequence>         <xsd:element name="word" type="xsd:string"/>         <xsd:element name="correct" type="xsd:boolean"/>         <xsd:element name="correctedWord" type="xsd:string"/>       </xsd:sequence>     </xsd:complexType>    <xsd:element name="spellCheck"> <xsd:complexType>       <xsd:sequence>         <xsd:element name="spellCheckInput" type="acme:SpellCheckMessageType"/>       </xsd:sequence>     </xsd:complexType>    </xsd:element>   <xsd:element name="spellCheckResponse">  <xsd:complexType> <xsd:sequence> <xsd:element name="spellCheckResult" type="acme:SpellCheckMessageResponseType"/>       </xsd:sequence>     </xsd:complexType>    </xsd:element> </xsd:schema> </types> <message name="SpellCheckInputMessage">   <part name="parameters" element="acme:spellCheck"/> </message> <message name="SpellCheckOutputMessage">   <part name="parameters" element="acme:spellCheckResponse"/> </message> <gwsdl:portType name="AcmeSearchPortType" extends="ogsi:GridService general:GeneralSearchPortType cache:CacheSearchPortType">   <operation name="spellCheck">     <input message="acme:SpellCheckInputMessage"/>     <output message="acme:SpellCheckOutputMessage"/>   </operation> </gwsdl:portType> </definitions> 

The design of WSDL is a complex process. Therefore, care needs to be taken and we must follow some design patterns for creating interoperable, standards-based, and error-free GWSDL/WSDL. What follows are some of the important design decisions we take into account when we create the above service description language. These considerations are:

  • While creating grid services for OGSI, it is recommended to use GWSDL/OGSI namespaces for declaring and use OGSI- related concepts including service data, open and extendable portTypes, and common data types.

  • We must import the OGSI behaviors from the GT3-provided "ogsi.gwsdl" declaration file.

  • We need to create only "portType" declarations (as shown in the above listings) in the GWSDL files: There is no need to create a service and the binding declarations. The GT3 tools (GWSDL2WSDL and GenerateBinding) generate the necessary WSDLs for services, bindings, and portTypes from the GWSDL portType definition. The default binding support is SOAP/HTTP with "document/literal" encoding.

  • Utilize the "document-literal" approach when designing WSDL for interoperability among implementations . Since there are some anomalies with the AXIS tools, one must embellish the appropriate design patterns in order to get the "best" generated code. This code generates SOAP messages that are interoperable, and could be validated with WS-I basic profile tools. [4]

  • The "targetNamespace" declaration must be done with care and this value is used to create the default package structure. We will see later how we can override this default tool generation feature.

  • XSD types must be defined with the XSD schema attribute of "elementFormDefault" and "attributeFormDefault" to get qualified with the appropriate namespace declarations.

Recommended WSDL Coding Pattern for Document/Literal Messages

As shown in Listing 13.3, the <wsdl:message> must contain <wsdl:parameters> referring to XML elements with the same name as the <wsdl:operation> name. These XML elements in turn may contain xsd:ComplexType/SimpleType types to hold the real message type. This is analogous to sending message documents with the name of the operation within a SOAP body as the first element.

For example, Listing 13.4 is the SOAP message generated for the "search" operation based on the above WSDL schema. The highlights indicate that we are sending an XML document based on its schema definition.

Listing 13.4. A sample SOAP message for document/literal WSDL definition.
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body>  <search xmlns="http://org.ph.gridbook.sample/base/general">   <searchInput>   <phrase>IBM Grid computing</phrase>   </searchInput>   </search>  </soapenv:Body> </soapenv:Envelope> 

Generating WSDL from GWSDL

We have now completed our service portType definition in GWSDLs, and it's time to focus on creating the real WSDL.

This is a transformation process from GWSDL to WSDL and is done using the GWSDL2WSDL tool, which we have discussed in the last chapter. Listing 13.5 shows the generated WSDL file with the generated WSDL portType declaration.

Listing 13.5 addresses the WSDL file generated from the GWSDL transformation operation.

Listing 13.5. WSDL file generated from the GWSDL transformation.
 <?xml version="1.0" encoding="UTF-8"?> <definitions name="AcmeSearch" targetNamespace="http://org.ph.gridbook.sample/base/acme" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:acme="http://org.ph.gridbook.sample/base/acme" xmlns:cache="http://org.ph.gridbook.sample/base/cache" xmlns:general="http://org.ph.gridbook.sample/base/general" xmlns:gwsdl="http://www.gridforum.org/namespaces/2003/03/gridWSDLExtensions" xmlns:ogsi="http://www.gridforum.org/namespaces/2003/03/OGSI" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <import location="../../ogsi/ogsi.gwsdl" namespace="http://www.gridforum.org/namespaces/2003/03/OGSI"/> <import location="../../gridbook/base/general_search_port_type.gwsdl" namespace="http://org.ph.gridbook.sample/base/general"/> <import location="../../gridbook/base/cache_search_port_type.gwsdl" namespace="http://org.ph.gridbook.sample/base/cache"/> <types>   <!omitted for clarity ; Same as what we have defined in the AcmeSearchPortType - -> </types> <message name="SpellCheckInputMessage">   <part element="acme:spellCheck" name="parameters"/> </message> <message name="SpellCheckOutputMessage">   <part element="acme:spellCheckResponse" name="parameters"/> </message> <portType name="AcmeSearchPortType"> <operation name="spellCheck">     <input message="ns0:SpellCheckInputMessage" xmlns:ns0="http://org.ph.gridbook.sample/base/acme"/>     <output message="ns1:SpellCheckOutputMessage" xmlns:ns1="http://org.ph.gridbook.sample/base/acme"/>   </operation> <operation name="search">     <input message="ns31:SearchInputMessage" xmlns:ns31="http://org.ph.gridbook.sample/base/general"/>     <output message="ns32:SearchOutputMessage" xmlns:ns32="http://org.ph.gridbook.sample/base/general"/>   </operation> <operation name="cacheSearch">     <input message="ns62:CacheSearchInputMessage" xmlns:ns62="http://org.ph.gridbook.sample/base/cache"/>     <output message="ns63:CacheSearchOutputMessage" xmlns:ns63="http://org.ph.gridbook.sample/base/cache"/>   </operation> <operation name="setServiceData">     <input message="ns70:SetServiceDataInputMessage" xmlns:ns70="http://www.gridforum.org/namespaces/2003/03/OGSI"/>     <output message="ns71:SetServiceDataOutputMessage" xmlns:ns71="http://www.gridforum.org/namespaces/2003/03/OGSI"/>     <fault message="ns72:ExtensibilityNotSupportedFaultMessage" name="ExtensibilityNotSupportedFault" xmlns:ns72="http://www.gridforum.org/namespaces/2003/03/OGSI"/>   <!Other faults are omitted for clarity ; -->   </operation> <operation name="destroy">     <input message="ns89:DestroyInputMessage" xmlns:ns89="http://www.gridforum.org/namespaces/2003/03/OGSI"/>     <output message="ns90:DestroyOutputMessage" xmlns:ns90="http://www.gridforum.org/namespaces/2003/03/OGSI"/>   <!Other faults are omitted for clarity ; -->   </operation> <operation name="requestTerminationAfter">     <input message="ns81:RequestTerminationAfterInputMessage" xmlns:ns81="http://www.gridforum.org/namespaces/2003/03/OGSI"/>     <output message="ns82:RequestTerminationAfterOutputMessage" xmlns:ns82="http://www.gridforum.org/namespaces/2003/03/OGSI"/> </operation> <operation name="requestTerminationBefore">     <input message="ns85:RequestTerminationBeforeInputMessage" xmlns:ns85="http://www.gridforum.org/namespaces/2003/03/OGSI"/>     <output message="ns86:RequestTerminationBeforeOutputMessage" xmlns:ns86="http://www.gridforum.org/namespaces/2003/03/OGSI"/>   <!Other faults are omitted for clarity ; - ->   </operation> <operation name="findServiceData">     <input message="ns64:FindServiceDataInputMessage" xmlns:ns64="http://www.gridforum.org/namespaces/2003/03/OGSI"/>     <output message="ns65:FindServiceDataOutputMessage" xmlns:ns65="http://www.gridforum.org/namespaces/2003/03/OGSI"/>   <!Other faults are omitted for clarity ; - ->   </operation> </portType> <gwsdl:portType extends="ogsi:GridService general:GeneralSearchPortType cache:CacheSearchPortType" name="AcmeSearchPortType"> </gwsdl:portType> </definitions> 

In Listing 13.5, we note that a new <wsdl:AcmeSearchPortType> was created with all the operations of the portTypes that we have extended in the GWSDL. This process is explained in the UML diagram below in Figure 13.4.

Figure 13.4. The GWSDL to WSDL transformation process.

graphics/13fig04.gif

One item to note regarding this process is that the generated WSDL is still keeping the GWSDL portType information for backward transformation purposes, and to help at runtime with the GT3 tools.

Generating Other WSDL Files

Once we have completed the above process, the next step in the process is to create the necessary WSDL files for service and binding. These files are created from the WSDL portType created in the above step. GT3 provides a helper class to assist us in this step. We are using the "GenerateBinding" tool to create default binding and service implementation.

Tools Used in the Above Two Steps Are:

1. GWSDL2WSDL

Usage : Java GWSDL2WSDL

acme_search_port_type.gwsdl

acme_search_port_type.wsdl

2. GenerateBinding

Usage : Java GenerateBinding

acme_search_port_type.wsdl

acme_search_service.wsdl


We now have all of the WSDLs we require for our service. In our sample, the generated files are:

  1. acme_search_bindings.wsdl

  2. acme_search_port_type.wsdl

  3. acme_search_port_type.xsd

  4. acme_search_service.wsdl

Generating Stubs, Helper Classes

So far we have been working with XML, WSDL, and XSD schema. Let us now begin to generate the native language implementation artifacts from the WSDL files. We need to make sure that the default implementation is using the correct name for the Java package, because the default code generation process generates the source information in a package, constructed from the targetNamespace of the WSDLs. The sidebar below illustrates how we can override the targetNamespace to a meaningful package name of our choice, should we need to do this.

Overriding the Default Package Creation Process in the Apache Axis WSDL2JAVA Tool

In WSDL2JAVA , there are three options to map the namespace to some user -defined package name:

-N, --NStoPkg <argument>=<value>

Mapping of namespace to package

-f, --fileNStoPkg <argument>

File of NStoPkg mappings (default NStoPkg.properties)

-p, --package <argument>

Override all namespace to package mappings, use this package name instead

Here, we are using the default NStoPkg.properties file. The new package definitions are:

http\://org.ph.gridbook.sample/base/acme=org.ph.gridbook.base

http\://org.ph.gridbook.sample/base/acme/bindings=org.ph.gridbook.base.bindings

http\://org.ph.gridbook.sample/base/acme/service=org.ph.gridbook.base.service


Once we have decided on the package names , the next step is the process of generating the Java sources for stubs, endpoint interface, helpers, and types. GT3 comes with a number of tools to help us accomplish this. We have already discussed the details on these tools in the last chapter. Readers can see how these tools are used for this sample.

GWSDL2Java

Usage : GWSDL2Java acme_search_service.wsdl


This results in a number of Java classes. We will see some of the important Java classes that are generated, and we will then spend some time analyzing these Java files. These files form the base implementation artifacts for the grid services at both the client and the server side. So, familiarity with these Java classes is a must for proper service and client-side development.

AcmeSearchPortType

This is the service endpoint interface generated from the WSDL portType definition. Service developers extend their service implementations from these service endpoint interfaces. This class is generated in accordance with the JAX-RPC specification, in the service endpoint interface definition. Listing 13.6 shows the interface generated for our sample.

Listing 13.6. The Acme Search interface.
 package org.ph.gridbook.base; public interface AcmeSearchPortType extends org.gridforum.ogsi.GridService {    // Most of the Exceptions are not shown for code clarity.............     public org.gridforum.ogsi.ExtensibilityType findServiceData(org.gridforum.ogsi.ExtensibilityType queryExpression) throws java.rmi.RemoteException, ..................................;     public org.ph.gridbook.base.SearchResponseType search(org.ph.gridbook.base.SearchType searchInput) throws java.rmi.RemoteException;     public void destroy() throws java.rmi.RemoteException,.........;     public org.ph.gridbook.base.CacheSearchMessageResponseType cacheSearch(org.ph.gridbook.base.CacheSearchMessageType cacheSearchInput) throws java.rmi.RemoteException;     public org.ph.gridbook.base.SpellCheckMessageResponseType spellCheck(org.ph.gridbook.base.SpellCheckMessageType spellCheckInput) throws java.rmi.RemoteException; // some methods are not shown for code readability................... } 

We should not try to code this "by hand." We must use the tools because the code generation is dependent on a number of WSDL constructs. This includes encoding (SOAP encoding/literal), style (rpc/document), and the operation parameter direction (in/out or inout). We could see JAX-RPC holder classes getting generated for the <wsdl:inout> operation parameter type.

Another helpful feature of using the tools is the creation of the corresponding Java types for the message parameters, and for the XSD elements defined in the WSDL. For example, a SearchType Java class is generated for the XSD complex type "SearchType" defined in the earlier WSDL listing. These objects are XML-serializable Java classes, and the framework can marshal/demarshal them into SOAP message body elements. Now let's move on to the other valuable construct, "AcmeSearchService," an implementation of javax.xml.rpc.Service.

AcmeSearchService

The JAX-RPC client at runtime creates "dynamic proxy" stubs using the javax.xml.rpc.Service interface. The client has a priori knowledge of the WSDL, the service it is going to invoke, and the service ports defined. It uses the "ServiceFactory" classes to create the service and get the proxy. In the last chapter, we have discussed this JAX-RPC service implementation class and its programming model. Listing 13.7 shows the service class for Acme Search Service.

Listing 13.7. The JAX-RPC service implementation class.
 public interface AcmeSearchService extends javax.xml.rpc.Service {     public java.lang.String getAcmeSearchPortAddress();     public org.ph.gridbook.base.AcmeSearchPortType getAcmeSearchPort() throws javax.xml.rpc.ServiceException;     public org.ph.gridbook.base.AcmeSearchPortType getAcmeSearchPort(java.net.URL portAddress) throws javax.xml.rpc.ServiceException; } 
AcmeSearchServiceGridLocator

This locator class is the most important GT3 specific helper class. This class is generated to help the grid service client by hiding the complexity of grid service message exchange patterns including GSH to GSR conversion, GSR validation, handle extraction from GSR, and so on. Listing 13.8 shows the grid locator class for the Acme Search Service. By careful observation on the class definition described in Listing 13.8, we could see that there are three public operations with different signatures available to the client.

It enables the client to pass the following information:

  • A handle (GSH) for a service instance

  • A locator of a service instance. This locator is normally returned on a service creation (Factory.createService).

  • A normal URL for the service instance.

As listed above, the utilization of this client helper class is based on the service instance availability information. In the last chapter, we discussed the internals of these operations. We will see the value of this helper class, and its utilization model, when we start working with our client code.

Listing 13.8. AcmeSearchServiceGridLocator, which is a grid service specific helper class.
 public class AcmeSearchServiceGridLocator extends org.globus.ogsa.impl.core.service.ServiceLocator implements org.globus.ogsa.GridLocator {     public org.ph.gridbook.base.AcmeSearchPortType getAcmeSearchPort (org.gridforum.ogsi.HandleType handle)  throws org.gridforum.ogsi.FaultType, org.globus.ogsa.GridServiceException { setStubClass(org.ph.gridbook.base.bindings.AcmeSearchSOAPBindingStub.class);         return (org.ph.gridbook.base.AcmeSearchPortType) getServicePort(handle);     }     public org.ph.gridbook.base.AcmeSearchPortType getAcmeSearchPort (org.gridforum.ogsi.LocatorType locator) throws org.gridforum.ogsi.FaultType, org.globus.ogsa.GridServiceException { setStubClass(org.ph.gridbook.base.bindings.AcmeSearchSOAPBindingStub.class);         return (org.ph.gridbook.base.AcmeSearchPortType) getServicePort(locator);     }     public org.ph.gridbook.base.AcmeSearchPortType getAcmeSearchPort(java.net.URL url) throws  org.globus.ogsa.GridServiceException { setStubClass(org.ph.gridbook.base.bindings.AcmeSearchSOAPBindingStub.class);         return (org.ph.gridbook.base.AcmeSearchPortType) getServicePort(url);     } } 
AcmeSearchServiceLocator

This is also a service locator helper class located at the client side. This is mainly utilized for Web service clients . This locator class (Listing 13.9) is different from the previously discussed locator class. We can see the difference by a careful examination of the name of the locator. The GT3 grid service specific locator has a name, <portType>GridServiceLocator, whereas the normal Web service locator has a name of <portType>ServiceLocator.

Listing 13.9. The Web service locator class.
 public class AcmeSearchServiceLocator extends org.apache.axis.client.Service implements org.ph.gridbook.base.service.AcmeSearchService {     // Use to get a proxy class for AcmeSearchPort     private final java.lang.String AcmeSearchPort_address = "http://localhost:8080/ogsa/services/";     public java.lang.String getAcmeSearchPortAddress() {         return AcmeSearchPort_address;     }     public org.ph.gridbook.base.AcmeSearchPortType getAcmeSearchPort() throws javax.xml.rpc.ServiceException {     } ............................. } 

This previous class implements AcmeSearchService to expose the JAX-RPC dynamic proxy behaviors.

AcmeSearchSOAPBindingStub

This is a core client-side static stub, which contains all the information about the service, and implements the service endpoint interface for compatibility verifications. The code in Listing 13.10 illustrates these concepts. This stub connects to the Apache SOAP client "Call" object to create a SOAP message for the corresponding method invocation. This stub contains the entire type-mapping and serialization information to map Java types in the operation signatures to the corresponding SOAP body elements, and vice versa.

Listing 13.10. The client-side static SOAP binding stub.
 public class AcmeSearchSOAPBindingStub extends org.apache.axis.client.Stub implements org.ph.gridbook.base.AcmeSearchPortType {     private java.util.Vector cachedSerClasses = new java.util.Vector()     public AcmeSearchSOAPBindingStub() throws org.apache.axis.AxisFault {          this(null);     }     public AcmeSearchSOAPBindingStub(java.net.URL endpointURL, javax.xml.rpc.Service service) throws org.apache.axis.AxisFault {          this(service);          super.cachedEndpoint = endpointURL;     }     public AcmeSearchSOAPBindingStub(javax.xml.rpc.Service service) throws org.apache.axis.AxisFault{ ........................     }     public org.gridforum.ogsi.ExtensibilityType setServiceData(org.gridforum.ogsi.ExtensibilityType updateExpression) throws java.rmi.RemoteException, org.gridforum.ogsi.MutabilityViolationFaultType, {     }     public org.ph.gridbook.base.SearchResponseType search(org.ph.gridbook.base.SearchType searchInput) throws java.rmi.RemoteException { // Implementation calls the Apace Axis call object    }    // A number of operations are not shown ....................... } 

Since we have completed the code generation process, it is time to move on to the real service creation constructs. Utilizing these tools to continue to generate code, we will now be able to write a service skeleton implementation quickly.

Implementing Search Grid Service

We will now begin with a simple server-side implementation to explain the basic service constructs. We will move on to the complex features later in this chapter.

Listing 13.11 illustrates how we are implementing this basic service. "AcmeSearchServiceImpl" implements AcmeSearchPortType, the endpoint service interface. It also implements the Globus-provided GridServiceImpl class, which provides the default implementations for the grid service behaviors, service data set, and service properties. We already covered the details on this helper implementation class in the previous chapter.

Listing 13.11. The Acme Search Service implementation.
 package org.ph.gridbook.impl.base; import org.globus.ogsa.impl.ogsi.GridServiceImpl; import org.globus.ogsa.GridContext; import org.globus.ogsa.GridServiceException; import org.globus.ogsa.ServiceProperties; import org.apache.axis.MessageContext; import java.rmi.RemoteException; import org.ph.gridbook.base.AcmeSearchPortType; import org.ph.gridbook.base.*; public class AcmeSearchServiceImpl extends GridServiceImpl implements AcmeSearchPortType {     public AcmeSearchServiceImpl () {         super("Simple Search Service");     }     public AcmeSearchServiceImpl(String name) {         super(name);     }     public void postCreate(GridContext context) throws GridServiceException {         super.postCreate(context);     }     public SearchResponseType search(SearchType searchInput) throws java.rmi.RemoteException{ // Ignore the input // create the output object SearchResponseType response = new SearchResponseType(); response.setURL("www.ibm.com"); response.setSummary("IBM"); response.setCached(true); return response;     }     public CacheSearchMessageResponseType cacheSearch(CacheSearchMessageType cacheSearchInput) throws java.rmi.RemoteException{     return null;     }     public SpellCheckMessageResponseType spellCheck(SpellCheckMessageType spellCheckInput) throws java.rmi.RemoteException{    return null;    } } 

This is a fully functional grid service implementation for the Acme search engine. The only functional method here is the search method, which accepts the search request and returns the search responses. We will see the SOAP message flows, the instance creation process, and other details in the client-side code described in the next section. Prior to this, let us explore the configuration of this service for the deployment of the service to the AXIS container.

Grid Service Configuration

As of now, the GT3 supports the Apache AXIS as their Web service engine. Due to this fact, we have to utilize the AXIS service configuration and deployment process. AXIS uses the WSDD to configure service for an AXIS engine. For our sample service, see the configuration listed in Listing 13.12.

Listing 13.12. The Acme Search Service deployment configuration.
  1.  <service name="ph/acme/AcmeSearchService" provider="Handler" style="wrapped">  2.  <parameter name="name" value="Acme Search Service Factory"/>  3.  <parameter name="instance-name" value="Acme Search service Instance"/>  4.  <parameter name="instance-schemaPath" value="schema/gridbook/base/acme_search_service.wsdl"/>  5.  <parameter name="instance-baseClassName" value="org.ph.gridbook.impl.base.AcmeSearchServiceImpl"/>  6.  <!-- Start common parameters -->  7.  <parameter name="allowedMethods" value="*"/>  8.  <parameter name="persistent" value="true"/>  9.  <parameter name="className" value="org.gridforum.ogsi.Factory"/>  10.  <parameter name="baseClassName" value="org.globus.ogsa.impl.ogsi.PersistentGridServiceImpl"/>  11.  <parameter name="schemaPath" value="schema/ogsi/ogsi_factory_service.wsdl"/>  12.  <parameter name="handlerClass" value="org.globus.ogsa.handlers.RPCURIProvider"/>  13.  <parameter name="factoryCallback" value="org.globus.ogsa.impl.ogsi.DynamicFactoryCallbackImpl"/>  14.  <parameter name="operationProviders" value="org.globus.ogsa.impl.ogsi.FactoryProvider"/>  15.  </service> 

We have previously discussed these configuration properties. The Acme Search Service specific parameters are seen in lines 25, which lists the name, instance name, WSDL schema path , and service implementation class. The rest are default configurations with the GT3-provided default factory provider (FactoryProvider), a callback (DynamicFactoryCallbackImpl) to create the service, and the pivot handler (RPCURIProvider). Another important point is the use of a "wrapped" style and using a "handler" provider.

Once we have the service and the configuration ready, we can deploy them into the service container by updating the class path and extending the existing server-config.wsdd with the above configuration fragment. And, from this point forward, when the container starts up, the Acme Search Service will be available for the client to use.

NOTE

Normally, we will use the deployment files "client-config.wsdd" and "server-config.wsdd," respectively, for client and server Web service configuration. These default names and locations can be overridden by providing a Java system property variable with axis.ServerConfigFile and axis.ClientConfigFile. For example, to use the configuration file "my-server-config.wsdd," one should provide the Java system property, Daxis.ServerConfigFile=my-server-config.wsdd.


Simple Client Implementation

Let us list the client-side requirements, first, and then start coding for those requirements.

  1. Create an Acme Search Service using the OGSI-defined factory pattern.

  2. Call the "search" operation on the service using the locator returned from the above step.

  3. After some time we need to call the service again. Now use a service handle we got during the first step.

  4. Now get some grid service specific properties or service data.

  5. Finally, we are now done with the service, by calling an explicit destroy on the service.

This process is shown in Figure 13.5.

Figure 13.5. The client-side interaction UML.

graphics/13fig05.gif

We will discuss each of these steps in detail, with the annotated code in Listing 13.13. We must pay close attention to the comments and the sequence of the process.

Listing 13.13. The Acme Search Service simple client.
 package org.ph.gridbook.impl.base.client; import org.globus.ogsa.wsdl.GSR; import org.gridforum.ogsi.*; import org.globus.ogsa.utils.*; import java.net.URL; import javax.xml.namespace.QName; import org.ph.gridbook.base.service.AcmeSearchServiceGridLocator; import org.ph.gridbook.base.AcmeSearchPortType  ; import org.ph.gridbook.base.*; public class AcmeSearchClient1 {     public static void main(String[] args) { try{ // Get command-line arguments URL GSH = new URL("http://localhost:9080/ogsa/ph/acme/AcmeSearchService"); // Get the GT3 provided Grid locator helper for OGSI service OGSIServiceGridLocator gridLocator = new OGSIServiceGridLocator(); // Get the factory using the URL to the factory Factory factory = gridLocator.getFactoryPort(GSH); GridServiceFactory acmeSearchFactory = new GridServiceFactory(factory); // Create a new AcmeService instance using the Grid service factory interface // Get a locator pointing to the Acme Search service LocatorType locator = acmeSearchFactory.createService(); // Done creating an Acme Grid service instance //Now we got an Acme search service instance, ready to invoke search on it // Get the GT3 created Grid locator helper AcmeSearchServiceGridLocator acmeSearchLocator =   new AcmeSearchServiceGridLocator(); AcmeSearchPortType  searchService = acmeSearchLocator.getAcmeSearchPort(locator); // Call remote method 'search' SearchResponseType response = searchService.search(null); System.out.println("Search response url = " + response.getURL()); System.out.println("Search response summary = " + response.getSummary()); // Now we are running a handle resolution to the service before making a service call // Get the GSR from the locator created returned by the factory GSR reference = GSR.newInstance(locator); // Get the handle from the reference using GSR helper class String location = reference.getHandle().toString(); HandleType handle = new HandleType(location); // We got a handle to the instance now. Let us run a handle resolution process before service call searchService = acmeSearchLocator.getAcmeSearchPort(handle); // The above call will go through a Handle resolution process // Finally will return a service reference //Now we can call remote method 'search' response = searchService.search(null); // Produces the results System.out.println("Search response url = " + response.getURL()); System.out.println("Search response summary = " + response.getSummary()); // Now we can explore some Grid service specific calls //1. Based on OGSI spec, every Grid service has "serviceDataNames" service data element // Get the above service data value from our acme search grid service. // Names of Service Data offered by this Grid Service extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("serviceDataName")); serviceData = AnyHelper.getAsServiceDataValues(extensibility); Object[] serviceDataNames = AnyHelper.getAsObject(serviceData); for(int i=0; i<serviceDataNames.length; i++){ QName serviceDataName = (QName) serviceDataNames[i]; System.out.println("service data name: " + serviceDataName); } // interfaces // Names of interfaces exposed by this Grid Service extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("interface")); serviceData = AnyHelper.getAsServiceDataValues(extensibility); Object[] interfaces = AnyHelper.getAsObject(serviceData); for(int i=0; i<interfaces.length; i++){  QName iface = (QName) interfaces[i];  System.out.println("interface name: " + iface);  } // Finally get a reference to the GridService PortType and destroy the instance GridService gridService = gridLocator.getGridServicePort(locator); gridService.destroy();       }catch(Exception e){ e.printStackTrace();      }    } } 
  1. Create an Acme Search Service. The client uses the normal GT3-provided "OGSIServiceGridLocator" class to get hold of the factory stub and calls create service on that stub using the well-known Acme Search Service factory URL. To better understand, we will show the SOAP messages flowing through the wire. This will give us a clear understanding of the process. Listing 13.14 shows the SOAP message generated for the "createService" operation on the factory. The target address URL we used to connect to the factory is http://localhost:9080/ogsa/ph/acme/AcmeSearchService."

Listing 13.14. The SOAP request message for the Factory createService operation.
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>   <createService xmlns="http://www.gridforum.org/namespaces/2003/03/OGSI">    <terminationTime ns1:before="infinity" ns1:after="infinity"  xmlns:ns1="http://www.gridforum.org/namespaces/2003/03/OGSI"/>    <creationParameters xsi:nil="true"/>   </createService>  </soapenv:Body> </soapenv:Envelope> 

Listing 13.15 describes the response from the server after the creation of the Acme Search Service using the default GT3 factory provider. This listing illustrates the encoded WDL reference information of the Acme Search Service (acme_search_service.wsdl). The GSR helper class can convert the WSDL reference to the corresponding "WSDLReferenceType" class and can retrieve the handle to the service instance.

Listing 13.15. The SOAP response message for the Factory createService operation.
 <soapenv:Body>   <createServiceResponse xmlns="http://www.gridforum.org/namespaces/2003/03/OGSI">    <locator>     <reference xsi:type="ns1:WSDLReferenceType" xmlns:ns1="http://www.gridforum.org/namespaces/2003/03/OGSI"> <definitions name="AcmeSearch" targetNamespace=http://org.ph.gridbook.sample/base/acme/service xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:acmesearchbinding="http://org.ph.gridbook.sample/base/acme/bindings" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"> <import location="http://localhost:9080/ogsa/schema/gridbook/base/acme_search_bindings.wsdl" namespace="http://org.ph.gridbook.sample/base/acme/bindings"/> <service name="AcmeSearchService" xmlns:gsdl="http://ogsa.globus.org/"> <gsdl:instanceOf handle="http://9.56.37.43:9080/ogsa/services/ph/acme/AcmeSearchService /hash-262089504-1058895681694" xmlns=""/> <gsdl:instanceOf handle="http://9.56.37.43:9080/ogsa/services/instance" xmlns=""/> <port binding="acmesearchbinding:AcmeSearchSOAPBinding" name="AcmeSearchPort"> <soap:address location="http://9.56.37.43:9080/ogsa/services/ph/acme/AcmeSearchService/hash- 262089504-1058895681694"/> </port> </service> </definitions> </reference>    </locator>    <ns2:currentTerminationTime xsi:type="ns2:TerminationTimeType" ns2:before="infinity" ns2:timestamp="2003-07-22T17:41:22.405Z" ns2:after="infinity" xmlns:ns2="http://www.gridforum.org/namespaces/2003/03/OGSI"/>    <extensibilityOutput xsi:nil="true"/>   </createServiceResponse>  </soapenv:Body> </soapenv:Envelope> 

Paying close attention to Listing 13.15, we can observe that:

  • The factory creates the handle of the service instance.

  • The SOAP address location is updated with the new instance address and the necessary instance information.

  • The current termination time element is related to the service instancecreated time, and with a termination time of infinity.

Once the client can establish a relationship with this reference, it can always go back to the service until the explicit termination of the service by client/container, or the soft state service expiration occurs.

  1. Call the "search" operation on the service using the locator returned from the above step. We have seen earlier that the <portType>GridServiceLocator class can be called with three types of parameters, LocatorType, HandleType, and URL, respectively. We will invoke the "search" operation on the server using the LocatorType class returned from the above "createService" call. Listing 13.16 shows how it is done.

Listing 13.16. The client invoking the "search" operation on the service.
 AcmeSearchServiceGridLocator acmeSearchLocator =   new AcmeSearchServiceGridLocator(); AcmeSearchPortType  searchService = acmeSearchLocator.getAcmeSearchPort(locator); SearchType searchRequest = new SearchType(); searchRequest.setPhrase("IBM Grid computing"); SearchResponseType response = searchService.search(searchRequest); 

Note that the target address utilized is http://localhost:9080/ogsa/ph/acme/AcmeSearchService/hash-262089504-1058895681694. This address points to a specific instance of the search service created by the client. The above grid service call resulted in the SOAP message in Listing 13.17.

Listing 13.17. The SOAP message trace on the "search" method call to the Acme Search Service.
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>   <search xmlns="http://org.ph.gridbook.sample/base/general">    <searchInput>     <phrase>IBM Grid computing</phrase>    </searchInput>   </search>  </soapenv:Body> </soapenv:Envelope> 

The results are retrieved from the SearchResponseType object. Listing 13.18 below describes the SOAP return message for the above call.

Listing 13.18. The SOAP message trace on the "search" method call return from the Acme Search Service.
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>   <searchResponse xmlns="http://org.ph.gridbook.sample/base/general">    <searchResult xsi:type="ns1:SearchResponseType" xmlns:ns1="http://org.ph.gridbook.sample/base/general">     <URL>www.ibm.com</URL>     <summary>IBM</summary>     <cached>true</cached>    </searchResult>   </searchResponse>  </soapenv:Body> </soapenv:Envelope> 

At this stage, we are done creating an acme search grid service instance, and we have invoked a "search" call on that instance using the service locator returned from the createService operation. Now, assume that we are holding the service instance for a long period of time. By now, the service GSR may have changed but the GSH holds validity. In this kind of situation, we may need to go back to the container to do a handle resolution process before we start accessing the service. This should happen without the client's involvement. Now, let us see how we can do this in a real-world scenario.

  1. After some time we need to call the service again. This time around, we will use a service handle that we received during the first step.

The client can still use the normal "AcmeSearchServiceGridLocator" class, however, the only difference is the use of a different operation signature. This time we will use the HandleType parameter. This example code is described in Listing 13.19.

Listing 13.19. Getting a handle from the locator and calling service.
 GSR reference = GSR.newInstance(locator); // Get the handle from the reference using GSR helper class String location = reference.getHandle().toString(); HandleType handle = new HandleType(location); // We got a handle to the instance now. Let us run a handle resolution process before service call searchService = acmeSearchLocator.getAcmeSearchPort(handle); 

The above operation results in a call to the container's handle revolver service to perform a handle to the reference resolution (GSH to GSR resolution) for that specific handle. This is happening in an autonomic sense. This handle resolution SOAP request, and the response message, is shown in Listings 13.20 and 13.21.

Listing 13.20. The invoking handle resolution service with the handle to resolve to a service instance reference.
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>   <findByHandle xmlns="http://www.gridforum.org/namespaces/2003/03/OGSI">    <handleSet>     <handle>http://9.56.37.43:9080/ogsa/services/ph/acme/AcmeSearchService/hash-262089504-1058895681694</handle>    </handleSet>    <gsrExclusionSet/>   </findByHandle>  </soapenv:Body> </soapenv:Envelope> 
Listing 13.21. A handle resolution result from the handle resolution service, with a GSR (WSDL reference).
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>   <findByHandleResponse xmlns="http://www.gridforum.org/namespaces/2003/03/OGSI">    <locator>     <reference xsi:type="ns1:WSDLReferenceType" xmlns:ns1="http://www.gridforum.org/namespaces/2003/03/OGSI"> <definitions name="AcmeSearch" targetNamespace="http://org.ph.gridbook.sample/base/acme/service" <!deleted for clarity - -> <service name="AcmeSearchService" xmlns:gsdl="http://ogsa.globus.org/"> <!deleted for clarity - -> <port binding="acmesearchbinding:AcmeSearchSOAPBinding" name="AcmeSearchPort"> <soap:address location="http://9.56.37.43:9080/ogsa/services/ph/acme/AcmeSearchService/hash-262089504-1058895681694"/> </port> </service> </definitions> </reference>    </locator>   </findByHandleResponse>  </soapenv:Body> </soapenv:Envelope> 

Now we are finished with this handle resolution, and the stubs are updated with the new location information. The client can begin using these stubs and start invoking operations on the service.

We have previously discussed the grid service client-side invocation models and the corresponding SOAP messages generated for each process. This information provides us with valuable information, including message interoperability, validation facility information, and derived information about the client service interaction. We can now move on to our next step of working with grid service behaviors.

  1. Get some grid servicespecific properties or service data. By now, readers are familiar with the OGSI-provided grid service behaviors, and can assert that the AcmeSearchService is a grid service based upon our server-side implementation and configuration options. Now we will spend time analyzing the grid service exposed state data and the operations provided to access these service behaviors.

First, let us explore the state data exposed by the grid service. We can use the OGSI-provided introspection API, "findServiceData," to get the list of exposed service data elements. We can do this by asking the grid service to send back the list of QName of service data elements kept in the "serviceDataName" SDE's service data values. This operation is invoked as shown in Listing 13.22.

Listing 13.22. Calling findServiceData operation on a service.
 extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("serviceDataName")); serviceData = AnyHelper.getAsServiceDataValues(extensibility); Object[] serviceDataNames = AnyHelper.getAsObject(serviceData); for(int i=0; i<serviceDataNames.length; i++){ QName serviceDataName = (QName) serviceDataNames[i]; System.out.println("serviceDataName: " + serviceDataName); } 

The above routine results in the output message in Listing 13.23.

Listing 13.23. Results of find service data operation on a service.
 Service data name: findServiceDataExtensibility Service data name: terminationTime Service data name: factoryLocator Service data name: serviceDataName Service data name: gridServiceReference Service data name: setServiceDataExtensibility Service data name: gridServiceHandle Service data name: interface 

This lists all the OGSI-defined service data elements. Later on we will see how we will add our own service-specific service data elements, including dynamic and static SDEs.

Details of findServiceData Operation Input Extensibility Element

The findServiceData input extensibility element is constructed using a query type "queryByServiceDataNames" query. This query type definition is simple, as shown below:

 <element name="queryByServiceDataNames" type="ogsi:QNamesType"/> <complexType name="QNamesType">       <sequence>           <element name="name" type="QName" minOccurs="0"               maxOccurs="unbounded"/>       </sequence> </complexType> 

The wire representation is shown below:

 <soapenv:Envelope        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"        xmlns:xsd="http://www.w3.org/2001/XMLSchema"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>        <findServiceData    xmlns="http://www.gridforum.org/namespaces/2003/03/OGSI">        <queryExpression>        <queryByServiceDataNames xsi:type="ns1:QNamesType"        xmlns:ns1="http://www.gridforum.org/namespaces/2003/03/OGSI">        <name>serviceDataName</name>           </queryByServiceDataNames>        </queryExpression>        </findServiceData>  </soapenv:Body> </soapenv:Envelope> 

GT3-supported QueryHelper provides helper functionalities such as this one to convert QName to a query expression with queryByServiceDataNames as inner XML elements.

We will see later on how we can extend the findServiceData to accept XPath queries.


As shown in Listing 13.24, now we can go back to the service and ask each of the above listed service data elements for its value using the same "findServiceData" operation and "queryByServiceDataNames" expression.

Listing 13.24. Find service data operations on a service to get the interfaces implemented by the service.
 extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("interface")); serviceData = AnyHelper.getAsServiceDataValues(extensibility); Object[] interfaces = AnyHelper.getAsObject(serviceData); for(int i=0; i<interfaces.length; i++){ QName iface = (QName) interfaces[i]; System.out.println("interface: " + iface); } 

The above client-side implementation code accesses the "interfaces" SDEs values defined for our service. The results are shown in Listing 13.25.

Listing 13.25. Results of the find service data operations to get the interfaces implemented by the service.
 interface name:  {http://org.ph.gridbook.sample/base/acme}AcmeSearchPortType interface name:  {http://org.ph.gridbook.sample/base/cache}CacheSearchPortType interface name:  GridService interface name:  {http://org.ph.gridbook.sample/base/general}GeneralSearchPortType 

Listing 13.25 illustrates a runtime behavior of a grid service whereby we can infer the interfaces exposed by the services.

So far our discussion was centered on the grid service's default public exposed state. We will see more complex behaviors later in this chapter when we cover advanced topics on service data and notification.

  1. Finally, we are now done with the service. We can remove the service instance by calling an explicit destroy on the service instance.

A client can control the service lifecycle using soft state mechanisms or by explicitly calling the Gridservice "destroy" operation on the service. Listing 13.26 illustrates the use of the explicit "destroy" operation by the client.

Listing 13.26. Calling explicit destruction on the grid service instance.
 GridService gridService = gridLocator.getGridServicePort(locator); gridService.destroy(); 

The success of the above routine depends on various factors including the service policies applicable to the client and the service hosting environment properties.

We can enable a soft state destruction by using the termination time of the createService call parameter or by explicitly modifying the modifiable service data element "terminationTime." In such cases the GT3 container will take the responsibility to destroy the service after the termination time expiration. The destruction semantic is depending on container implementation logic with the only assertion that, once destructed, the clients cannot access the service instance anymore using the same GSH.

By now we have been introduced to the basic service implementation, configuration, and grid service behaviors. Now we can move on to more complex service implementation concepts.

We must note that enabling the logging and tracing information helps us understand the message flow between the client and the service. In the previous chapter on the programming model, we have covered the details on the various options to enable this feature.

Advanced Grid Service

In this section we will go through some advanced concepts around grid services. These include the use of operation providers to implement service port types, different types of service data, its creation and usage patterns, and notification mechanisms. We will start with service data concepts.

Advanced Service Data Concepts

These are publicly available states of a service. There are two types of service data elements, static and dynamic.

The static service data elements are declared in the GWSDL and added to the service instance service data set upon service instance startup. The framework does this by reading the GWSDL descriptions of the service. These service data elements will be available in the data set of the instance throughout the lifetime of the service. However, their values and availability are depending on the configuration of these elements, including mutability attributes and lifetime attributes. In our example, we are going to create two service data element declarations, one in the CacheSearchPortType and the other in the GeneralSearchPortType. Listings 13.27 and 13.28 are extensions to the portType declarations we have created earlier in this chapter.

Listing 13.27. Extended GeneralSearchPortType portType with service data elements.
 <definitions name="GeneralSearch" .................................................. <types>   <xsd:schema .................................................. <xsd:complexType name="SearchProviderType">       <xsd:sequence>         <xsd:element name="name" type="xsd:string"/>         <xsd:element name="url" type="xsd:string"/>       </xsd:sequence>   </xsd:complexType> </xsd:schema> </types> .................................................. <gwsdl:portType name="GeneralSearchPortType" extends="ogsi:GridService">   <operation name="search">         ......................................................   </operation> <sd:serviceData name="searchProvider"                   type="general:SearchProviderType"                   minOccurs="1"                   maxOccurs="unbounded"                   mutability="static"                   modifiable="false"                   nillable="false"/> <sd:staticServiceDataValues>            <general:searchProvider>                              <name>Google</name>                              <url>www.google.com</url>            </general:searchProvider>            <general:searchProvider>                              <name>MSN</name>                              <url>www.msn.com</url>            </general:searchProvider>   </sd:staticServiceDataValues> </gwsdl:portType> </definitions> 

By careful observations we can see that this publicly exposed state "searchProvider" is a static service data element and needs to be initialized in the WSDL using staticServiceDataValues, as shown in Listing 13.27.

Listing 13.28. Extended CacheSearchPortType portType with service data elements.
 <?xml version="1.0" encoding="UTF-8"?> <definitions name="GeneralSearch" ....................................... <types>   <xsd:schema          .....................................     <xsd:complexType name="searchCacheSizeType">       <xsd:sequence>         <xsd:element name="newSize" type="xsd:int"/> </xsd:sequence>     </xsd:complexType> </xsd:schema> </types> .................................. <gwsdl:portType name="CacheSearchPortType" extends="ogsi:GridService">   <operation name="cacheSearch">         ............................................   </operation>   <sd:serviceData name="searchCacheSize"                   type="cache:searchCacheSizeType"                   minOccurs="1"                   maxOccurs="unbounded"                   mutability="mutable"                   modifiable="true"                   nillable="false"/> </gwsdl:portType> </definitions> 

As shown in Listing 13.28, the publicly exposed state " searchCacheSize " is a mutable service data element. This enables the service clients to modify (modifiable='true') this service data value using the "setServiceData" operation.

One thing to notice is the type of SDE and their corresponding XML schema type. The SDE values are initialized using those types. As we have noticed earlier, these types are converted to their corresponding Java types by the tool WSDL2Java. The above XSD types in the example generate " SearchCacheSizeType ", " SearchProviderType " classes.

By default, these service data elements are loaded to the Acme Search Service instance's service data set upon service initialization. We must be aware that the AcmeSearchPortType extends the above portTypes and thereby has all these SDEs with it. One thing to notice is that these service data elements defined by the super types can be overridden by the derived types. Refer back to our discussions in the OGSI chapter for more details on these capabilities.

On the assumption that we have deployed this service to the container, let us move to the client side to start working with these service data constructs.

Now, if running the client code shown in Listing 13.22, it must display two more additional service data elements in addition to the standard GridService defined ones. We can ask the service for the specific values of the static service data element " searchProvider " using the findServiceData method shown in Listing 13.29.

Listing 13.29. Calling find service data to get the statically configured search providers.
 // Get the service data values for SDE "searchProvider" extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("searchProvider")); // Get the base the service data value type serviceData = AnyHelper.getAsServiceDataValues(extensibility); // Convert the elements to the know type SearchProviderType Object[] providerValues = AnyHelper.getAsObject(serviceData,SearchProviderType.class); // display the search provider information for(int i=0; i<providerValues.length; i++){ SearchProviderType searchProvider = (SearchProviderType) providerValues[i]; System.out.println("searchProvider name: " + searchProvider.getName()); } 

The routine in Listing 13.29 should display the information in Listing 13.30, which are configured as static service data element values.

Listing 13.30. Results on calling the find service data operation.
 searchProvider name: Google searchProvider name: MSN 

One of the service data elements (searchCacheSize) we have added to the WSDL is changeable by the client. We have mentioned earlier that for this to be successful we need to set service data element attribute "modifiable" with a value of "true." This enables the client to invoke a setServiceData operation on this service data element with a new value for the " SearchCacheSizeType " object. The code in Listing 13.31 illustrates this process.

Listing 13.31. Set service data operation on a service data element.
 SearchCacheSizeType newCacheValue = new SearchCacheSizeType(); // set a new cache newCacheValue.setNewSize(34); org.apache.axis.message.MessageElement elem = (org.apache.axis.message.MessageElement)AnyHelper.toAny(newCacheValue); org.apache.axis.message.MessageElement setWrapper = new org.apache.axis.message.MessageElement(); setWrapper.setQName(org.globus.ogsa.GridConstants.SET_BY_NAME); setWrapper.addChild(elem); ExtensibilityType any = new ExtensibilityType(); any.set_any(new org.apache.axis.message.MessageElement[] { setWrapper }); // calls the setServiceData operation to update the service data searchService.setServiceData(any); 

To further illustrate this, let us examine the SOAP message in Listing 13.32 for the above update message.

Listing 13.32. SOAP request message for set service data.
 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">  <soapenv:Body>   <setServiceData xmlns="http://www.gridforum.org/namespaces/2003/03/OGSI">    <updateExpression>     <setByServiceDataNames>      <ns2:value xsi:type="ns1:searchCacheSizeType" xmlns:ns1="http://org.ph.gridbook.sample/servicedata/cache" xmlns:ns2="http://ogsa.globus.org/">       <ns1:newSize>34</ns1:newSize>      </ns2:value>     </setByServiceDataNames>    </updateExpression>   </setServiceData>  </soapenv:Body> </soapenv:Envelope> 

So far we have been dealing with the static service data elements declared as part of GWSDL. Now let's turn our attention to some dynamic service data elements created by the service.

The dynamic service data elements can be created at any time and can be added to the service data set. This is done with the help of an instance-specific service data set object. The routine in Listing 13.33 illustrates how our Acme Search Service instantiates a dynamic service data object and adds that to the service data set.

Listing 13.33. Creating dynamic service data element.
 //Create Service Data Element ServiceData serviceDataElement = this.getServiceDataSet().create("MyServiceData"); // Create a service data value and set that value // Can be complex objects or simple. What is shown here is a simple java 'string' serviceDataElement.setValue("my simple service data element"); //Add SDE to Service Data Set this.getServiceDataSet().add(serviceDataElement); // now onwards available for client discovery 

Once this dynamic service data is added to the service data set, the client can query for them and can work with them (Listing 13.34).

Listing 13.34. Accessing the dynamic service data element.
 extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("MyServiceData")); serviceData = AnyHelper.getAsServiceDataValues(extensibility); Object[] mySDValue = AnyHelper.getAsObject(serviceData); for(int i=0; i<mySDValue.length; i++){ String myValue = (String) mySDValue[i]; System.out.println("Dynamic sd value : " + myValue); } 

The clients can do dynamic service introspection on the service to find the available SDEs (static and dynamic) at any point in time. The problem with dynamic service data elements is the unavailability of the semantics of the service state data to the clients. This may get complicated on the processing of these dynamic service data unless the client knows or there is an a priori agreement on the semantics and type of this data.

Operation Providers

The concept of operation providers helps the server-side developers to separate the application logic external to the service implementation code. We have discussed the value-added features provided by this framework. Now we will see how we can come up with an extendable and manageable application design for our service. We decided that all our business logic should be external to the service implementation so that they can grow as the service gets matured.

For example, our caching search service is very primitive at this stage. However, later we may come up with more complex and valuable implementations for the same without disrupting the existing service implementation. We can plug in the new provider through the service configuration options. This provides a highly flexible approach.

We are going to implement the GeneralSearchPortType and CacheSearchPortType interfaces in different providers. We will create two providers, GeneralSearchProvider and CacheSearchProvider.

Our service implementation looks similar to the UML diagram shown in Figure 13.6.

Figure 13.6. Acme service implementation using OperationProvider.

graphics/13fig06.gif

Let us now take a look into the code of one provider, CacheSearchProvider in Listing 13.35.

Listing 13.35. Operation provider implementation.
 public class GeneralSearchProvider implements OperationProvider {     public static QName[] operations =         new QName[] {             new QName(http://org.ph.gridbook.sample/base/general, "search"         };     public QName[] getOperations() {         return operations;     }     public void initialize(GridServiceBase base) throws GridServiceException {     }     public GeneralSearchProvider () {     }     public SearchResponseType search(SearchType searchInput)                                       throws java.rmi.RemoteException{            SearchResponseType response = new SearchResponseType();            response.setURL("www.ibm.com");            response.setSummary("IBM");            response.setCached(true);            return response;     } } 

Listing 13.35 illustrates how we can separate our portType implementation out of the service code and move that to an operation provider. On careful examination, we can see that the public operations we are exposing are listed in the QName list. These operations correspond to their WSDL counterparts. These are exposed through the getOperations() method. Our service implementation only implements the spellCheck method. Since our AcmeSearchPortType lists all the abstract interfaces, we need to add these additional operations to our service code as templates to satisfy our compilers. The client code remains the same. However, we need to add the above provider to the configurations list, as shown in Listing 13.36.

Listing 13.36. Configuring operation provider with the service configuration.
 <service name="ph/acme/operationprovider/AcmeSearchService" provider="Handler" style="wrapped"> ............................ <parameter name=" instance-operationProviders"     value="org.ph.gridbook.impl.operationprovider.CacheSearchProvider          org.ph.gridbook.impl.operationprovider.GeneralSearchProvider "/> </service> 

We are now done with the service configuration and our client can start invoking operations on our service using the same client code shown earlier.

Providing Asynchronous Behaviors through Notification

The notification mechanism defined by OGSI is very flexible. It provides a flexible subscription mechanism by allowing different types of subscription expressions as specified by the service. All service is required to provide the "subscribeByServiceDataName" expression. This enables clients to register for notification on service data element value changes. The Globus GT3 framework supports this default behavior. In addition, it can be extensible to support a topic-based subscription, which is a derivative model from the above, "subscribeByServiceDataName" type of subscription. In the previous chapter, we have covered the details on the GT3 architectural and programming support for notifications. Now it's code time; we will discuss the topics we have covered in the last chapter with some sample code. We will extend our AcmeSearchService as a notification provider. These samples will illustrate:

  1. Subscribing for service data change using service data name element

  2. Subscribing for some interesting topics exposed by the AcmeSearchService.

Let us explore the details.

Subscribing for Service Data Change Using Service Data Name Element

Figure 13.7 illustrates the basic concept. Clients can subscribe for service data value change through subscription by providing the interested service data element name.

Figure 13.7. Notification based on service data change.

graphics/13fig07.gif

First and foremost, the change we have to make is to let the service implement the Notification Source portType as defined by OGSI. We can do this by changing the GWSDL portType definition of our service. Listing 13.37 shows how we will change the AcmeSearchPortType in the gwsdl.

Listing 13.37. Acme service extending notification source portType.
 <?xml version="1.0" encoding="UTF-8"?> <definitions name="AcmeSearch" targetNamespace="http://org.ph.gridbook.sample/base/acme" ..............................."> ..................................... <gwsdl:portType name="AcmeSearchPortType" extends="ogsi:GridService  ogsi:NotificationSource  general:GeneralSearchPortType cache:CacheSearchPortType">   <operation name="spellCheck">     <input message="acme:SpellCheckInputMessage"/>     <output message="acme:SpellCheckOutputMessage"/>   </operation> </gwsdl:portType> </definitions> 

As shown in Listing 13.37, we have extended our portType from ogsi:NotificationSource interface. While running the tools (GWSDL2WSDL, GenerateBinding, and then GWSDL2Java), we will be able to generate the necessary stubs and portTypes that can handle the notification subscription process.

In the previous chapter, we described these listed tools and their utilization model. The generated AcmeSearchPortType interface contains a "subscribe" operation, in addition to the other operations we saw earlier. This interface is in Listing 13.38.

Listing 13.38. Endpoint interface for Acme Search Service with notification operations.
 package org.ph.gridbook.notification; public interface AcmeSearchPortType extends org.gridforum.ogsi.GridService {    // Most of the Exceptions are not shown for code clarity..............     public org.gridforum.ogsi.ExtensibilityType findServiceData(org.gridforum.ogsi.ExtensibilityType queryExpression) throws java.rmi.RemoteException, ...................;     public void subscribe(org.gridforum.ogsi.ExtensibilityType subscriptionExpression, org.gridforum.ogsi.LocatorType sink, org.gridforum.ogsi.ExtendedDateTimeType expirationTime, org.gridforum.ogsi.holders.LocatorTypeHolder subscriptionInstanceLocator, org.gridforum.ogsi.holders.TerminationTimeTypeHolder currentTerminationTime) throws java.rmi.RemoteException,....; ..........................................     public void destroy() throws java.rmi.RemoteException,.............;     public org.ph.gridbook.notification.SpellCheckMessageResponseType spellCheck(org.ph.gridbook. notification.SpellCheckMessageType spellCheckInput) throws java.rmi.RemoteException; // some methods are not shown for code readability................. } 

The GT3 framework comes with a number of framework components to deal with the service data change notification. The default NotificationSourceProvider class provides most of the required functionality, so we need to configure our service to use the default notification source provider. Listing 13.39 shows how to add this new instance operation provider to our configuration.

Listing 13.39. Service configuration with notification source provider.
 <service name="ph/acme/operationprovider/AcmeSearchService" provider="Handler" style="wrapped"> ................................ <parameter name=" instance-operationProviders"         value="org.ph.gridbook.impl.operationprovider.CacheSearchProvider              org.ph.gridbook.impl.operationprovider.GeneralSearchProvider              org.globus.ogsa.impl.ogsi.NotificationSourceProvider"/> </service> 

At the client side we need to provide two implementation artifacts:

  • Create a sink listener to receive notification. A sink should implement the OGSI NotificationSink interface and expose the "deliverNotification" operation. This sink is a grid service with a unique handle (GSH) and the client sends this handle to the service during subscription in order to receive notification callbacks. This process is simpler if our client is a grid service. However, since our current client is a Java application, we need a container to support this at the client side to create the sink service, and to listen for the message delivery. For this to occur, we will make use of the lightweight client container included with GT3.

The routine in Listing 13.40 explains how we are initializing this container and listening on a port. We can always configure some of the properties, including the port, protocol, and hostname of this client-side container through the client-server-config.wsdd file.

Listing 13.40. Configuring the client sink container.
 // Create a sink manager, which provides a lightweight container capability NotificationSinkManager notifManager = NotificationSinkManager.getManager(); // Start listening on a port notifManager.startListening(NotificationSinkManager.MAIN_THREAD); // Need to create a sink callback object, which implements the NotificationSinkCallback interface NotificationSinkCallback client = new myNotificationSinkCallbackImpl(); 
  • Create a subscription with the service. The subscription process is simple, provided we are using the notification manager as shown above. The only requirement is to add a delivery message listener of type NotificationSinkCallback and add that listener to the manager along with the service data element name (identified using name or QName) for which we are listening for changes. This listener implements a "deliverNotification" operation, which will be called on notifications from the service. The routine in Listing 13.41 shows how we will be enabling the callback and sending a subscribe call to the source service.

Listing 13.41. Subscribing for service data change notifications.
 // Subscribe for service data notification by passing the service data name, handle of the service instance whose SD changes we are watching, and the notification sink callback method String sink  =  notifManager.addListener("MyServiceData", null, handle, client); 

Next, we will implement the notification receiver to receive change notification messages from the service instance (Listing 13.42).

Listing 13.42. Receiving messages from the service.
 Class myNotificationSinkCallbackImpl implements NotificationSinkCallback  {     public void deliverNotification (ExtensibilityType any) throws     RemoteException{         ServiceDataValuesType serviceData =         AnyHelper.getAsServiceDataValues(any);         Object[] mySDValue = AnyHelper.getAsObject(serviceData);         for(int i=0; i<mySDValue.length; i++){             String myValue = (String) mySDValue[i];             System.out.println("Received a notification from service                                with value: " + myValue);         }     } } 

It is important to understand whenever there is a change in the service data element (identified by "MyServiceData"), a notification message will be sent to the client. As shown in Listing 13.43, we can force this notification push from our service code using a "notifyChange()" message.

Listing 13.43. Service code to push an SD notification change to the client.
 /* We saw earlier that this is a dynamic service data element and it is already defined and created for the service. Now get the SDE and change its value */ ServiceData serviceDataElement= getServiceDataSet().get("MyServiceData"); serviceDataElement.setValue("A changed service data value");  //  Notify the service data element change serviceDataElement.notifyChange(); 

Paying close attention to the above implementation logic, we can infer that the above process is static in nature, based upon the service data and its type. We can extend this model to a more generalized solution for asynchronous messages, where the topics can be broader than the service data elements, and the data can be of any type.

Subscribing for Some Interesting Topics Exposed by the AcmeSearchService

Figure 13.8 explains a dynamic notification process using the concept of "topics." A service can expose some topics, to which it feels relevant for the clients to subscribe. These topics can be created dynamically upon service startup. These topics are added to the provider. The provider currently exposes these dynamic topics in the service data element set. The routine in Listing 13.44 explains how we can do this. There is no configuration change needed for the service.

Listing 13.44. Creating dynamic topics and adding to the provider.
 NotificationProvider provider =(NotificationProvider) getProperty(ServiceProperties.NOTIFICATION_SOURCE); // adding a topic of interest with the notification provider with a namespace and message type provider.addTopic("ServiceDestroyNotification", new QName("http://org.ph.gridbook.sample/notification/acme", "xsd:string") 
Figure 13.8. Dynamic topic-based subscription.

graphics/13fig08.gif

The clients can now discover this topic through the "findServiceData" operation and can register for that dynamic topic (Listing 13.45).

Listing 13.45. Finding the dynamic topics adding to the provider.
 extensibility = searchService.findServiceData(QueryHelper.getNamesQuery("serviceDataName")); serviceData = AnyHelper.getAsServiceDataValues(extensibility); Object[] serviceDataNames = AnyHelper.getAsObject(serviceData); for(int i=0; i<serviceDataNames.length; i++){ QName serviceDataName = (QName) serviceDataNames[i]; System.out.println("serviceDataName: " + serviceDataName); } 

The above call should list {http://org.ph.gridbook.sample/notification/acme: ServiceDestroyNotification} as one of the service data elements. This is the exposed "topic" and the clients can register their interest in this topic using the routine shown in Listing 13.46.

Listing 13.46. Adding a listener on the dynamic topics.
 QName sdname = new QName("http://org.ph.gridbook.sample/notification/acme", " ServiceDestroyNotification "); String sink  =  notifManager.addListener(sdname, null, handle, client); 

On running the code in Listing 13.46, the client is ready to receive notification from the service instance when it is trying to destroy the service.

Let us now see how the service is sending the change notifications related to the above topic. The routine in Listing 13.47 shows how we can do this.

Listing 13.47. Sending a notification.
 provider.notify("ServiceDestroyNotification","I am destroying..."); 

One thing we didn't mention is regarding the lifecycle of the subscription. On a subscription message from the client, the grid service instance creates a NotificationSubscription object with the necessary subscription information, including the subscription lifetime. This is a grid service instance and a GSH is passed back to the client. The client can control the lifecycle of a subscription through soft state management mechanisms. Once the client feels that the subscription is no longer needed, it can call the normal grid service "destroy" operation on the subscription object to get the subscription removed.

The above-described notification mechanisms can be further extended to support more complex mechanisms including the subscription criteria for filtering, message batching , and the use of a more reliable message exchange model.

EJB Delegation

As shown in Figure 13.9, GT3 provides a delegation model to support the business logic implemented in the EJB.

Figure 13.9. Delegation-based grid services.

graphics/13fig09.gif

The default factory can be configured to have an EJB callback implementation provided with GT3, which provides support to connect to EJB home and create an EJB remote object of choice. The configuration for the JNDI name for the home lookup is configured as a service configuration parameter. The service developers are required to derive their service implementations from the abstract EJBServiceImpl class to provide a constructor that can accept the EJB Home and EJB Remote objects.

The UML diagram (logical) in Figure 13.10 shows a delegation model.

Figure 13.10. EJB delegation model.

graphics/13fig10.gif

The internal implementation of the callback object is simple. Based on the type of EJB, it either creates the EJB or tries to find the EJB. This is done by introspection on the metadata provided by the EJB home object (through the call home.getEJBMetaData()). This introspection provides information such as the type (stateless, stateful, or entity) of EJB. Based on these metadata information it either creates the bean or tries to find the bean using the primary key. After the retrieval of the home and remote objects, the callback creates the grid service instance and initializes the service with the EJB home and remote objects.

Now let us see how our Acme service's cache can be implemented in an EJB entity bean. We won't talk about the EJB implementation. Our assumption is that we have already created an entity EJB bean object, and its corresponding Home object, and both are deployed to the J2EE container. The home is identified through the JNDI name "ph/cache/EjbCache."

Let us now create our Acme Search grid service, and the delegate calls to the "cacheSearch" for the corresponding EJB remote operation. Listing 13.48 shows how we can do this.

Listing 13.48. Grid service implemented with EJB delegation mechanism.
 public class myEJBServiceImpl extends EJBServiceImpl implements AcmeSearchPortType {     public myEJBServiceImpl(String name, EJBHome _home, EJBObject _remote) {         super(name, _home, _remote);     }    public CacheSearchMessageResponseType cacheSearch(CacheSearchMessageType cacheSearchInput) throws java.rmi.RemoteException{ // extract the search input data // convert to EJB acceptable serializable objects // call method on the remote object    getRemote().cacheSearch(); // get the results and convert that to CacheSearchMessageResponseType // send back the result return new CacheSearchMessageResponseType ();     } .................................... } 

The deployment of the service with the EJB home JNDI name is in the configuration fragment in Listing 13.49.

Listing 13.49. Grid service and delegation EJB home JNDI configuration.
 <service name="ph/acme/ejb/AcmeSearchService" provider="Handler" style="wrapped"> ..................................... <parameter name="ejbLookupString" value=" ph/cache/EjbCache"/> <parameter name="factoryCallback" value="org.globus.ogsa.impl.core.factory.EJBFactoryCallback"/> <parameter name="operationProviders" value="org.globus.ogsa.impl.ogsi.FactoryProvider"/> ..................................... </service> 

We can now call the service and start using the business methods. The calls to cacheSearch will be delegated to the corresponding EJB instance. The default operation removes the session EJBs on the service destroy, but it will not touch the entities. If we need to, we can override this feature by implementing the destroy method.

The EJB callback implementation, as we observed in this section, is sufficient for the simple delegation model we have discussed. Note that we can always plug in our own callback implementations for more sophisticated functionalities.



Grid Computing (IBM Press On Demand Series)
Windows Vista(TM) Plain & Simple (Bpg-Plain & Simple)
ISBN: 131456601
EAN: 2147483647
Year: 2002
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net