Java Programmer’s Guide Core Framework

Globus Toolkit 3.0 - Last Updated 10/24/2003

Contents

Introduction

Getting Started

1.  Writing a Service
2.  Writing a Client
3.  Testing the Service

Additional APIs

4.   Service Data
5.   Notifications
6.   Service Properties and Configuration
7.   Service Activation, Deactivation, and Recovery Framework
8.   Writing a Custom Factory
9.   Performance Profiling
10. Service Container
11. XPath Queries
12. WSIF Client
 

Introduction

This document is a guide to Programming with Grid services in Java. It covers both client- and server-side programming, and it focuses on explaining example code and use cases. It does not provide a complete reference to all of the APIs. Please refer to the javadocs for this information. The guide only covers the core framework, for more information on EJB, security, and higher-level services (such as Managed Job Service, Reliable File Transfer Service, and Index Service) support see separate documentation. For installation, deployment, and development environment documentation see the User’s Guide.

This guide is structured into two parts. The first part, Getting Started, describes how to write, deploy, and access a simple Grid service in the framework. The second part, Additional APIs, describes some additional APIs and features provided by our framework for more advanced service development.

Basic knowledge of Java and Ant (http://jakarta.apache.org/ant) is assumed in this guide. We also assume that you are familiar with the basic OGSA environment described in the User's Guide.

Part I: Getting Started

1 Writing a Service

The following steps are involved in writing a Grid service:

The complete source code for this example is available in the guide directory in your framework distribution.

Step 1. Provide a Service Interface

There are two approaches to providing an interface for your service that is to be exposed to remote clients. You can either write the interface in java, and generate the WSDL interface, or you can provide the interface using a WSDL port type definition. We will look closer at the two approaches next.

Java Interface

We define the following Java interface: 

package org.globus.ogsa.guide.impl;

public interface Counter {
    public int add(int value);
    public int subtract(int value);
    public int getValue();
} 

See guide/src/org/globus/ogsa/guide/impl/Counter.java for the full example.

This approach should be used with care because some complex java types do not map very well into WSDL and could thus impair the interoperability of your service. It is however useful if you want to expose legacy code written in Java as Grid services, without requiring any programmatic effort.

WSDL PortType Interface

When providing a WSDL interface you only need to provide the abstract definition of the service including the types, message, and portType parts of WSDL. The binding, and service part will be generated for you by our tools. This approach may at first glance look more complicated and verbose than the Java Interface approach. But the more complicated your service gets the likelier it is that you will come across types and constructs in Java that don't have a clear mapping to XML. Defining the interface in WSDL also makes it easier to reuse designs in a language neutral way. Someone might for instance write a Grid service in C, and define a set of types for that service in XML Schema. Using the WSDL PortType Interface approach will make it very easy to embed all these types and even extend the interfaces from this service in your new service to be developed in Java. 

Note in the example below that we define port types in the gwsdl namespace, this is done to circumvent the fact that WSDL 1.1 does not allow portType inheritance nor embedded extensions. WSDL 1.2, however, does allow these constructs, and when that specification is released and the OGSI specification is fully based on it, the gwsdl namespace will go away and be replaced by the wsdl 1.2 namespace.

Here are some excerpts from the wsdl definition:

<types>
...
<xsd:element name="add">
  <xsd:complexType>
    <xsd:sequence>
      <xsd:element name="value" type="xsd:int"/>
    </xsd:sequence> 
  </xsd:complexType>
  </xsd:element>
...
</types>
...
<message name="AddInputMessage">
  <part name="parameters" element="tns:add"/>
</message>
...
<gwsdl:portType name="CounterPortType" extends="ogsi:GridService">
  <operation name="add">
    <input message="tns:AddInputMessage"/>
    <output message="tns:AddOutputMessage"/>
    <fault name="Fault" message="ogsi:FaultMessage"/>
  </operation>
</gwsdl:portType>

The types section defines the add operation to have one parameter called value of type int. The message section ties the input message to the concrete xml element to be used as payload of the operation. Finally the portType section defines that this portType should also expose all the operations defined in the ogsi:GridService portType. Also note that the standard ogsi fault can be thrown from this operation, which is good practice to declare even if you don't have any application specific faults.

See guide/schema/counter_port_type.gwsdl for the full example

To produce a complete wsdl 1.1 definition  from this gwsdl port type definition you would need to run it through the GWSDL2WSDL, and generateBinding  tools as follows:

<ant antfile="${build.services}" target="GWSDL2WSDL">
  <property name="build.schema.dir" value="guide/Counter"/>
  <property name="wsdl.root" value="counter_port_type"/> 
 </ant>
 <ant antfile="${build.services}" target="generateBinding">
  <property name="binding.root" value="counter"/>
  <property name="build.schema.dir" value="guide/Counter"/>
  <property name="porttype.wsdl" value="counter_port_type.wsdl"/>
 </ant> 

GWSDL2WSDL creates a WSDL 1.1 portType containing all the operations inherited from the gwsdl definition. The generateBinding tool generates the wsdl:binding and wsdl:service parts for the portType definition. Currently we only support doc/literal SOAP 1.1 bindings.

Step 2. Generate Grid Service Support Code

We provide high level ant task and xml batch file based tools to simplify the generation of the required stub and support code for hosting your service as an OGSI compliant Grid service. Although you may not use these tools directly, all the tools are centered around two tools primitives generateWSDL and generateStubs, which are used to generate WSDL from a Java interface and Java stubs for a WSDL interface respectively. However in order to understand better how the higher level tools work we first briefly explain these two basic tools. For the best tools support we recommend that you use the GWSDL2WSDL, generateBinding, and generateStubs primitives when generating a service from a WSDL file. However, if you start from a java interface we recommend that you use the higher level bottomUp tool described below. The generateWSDL tool is described below for completeness.

Generate WSDL from Java

An existing Java interface can be run through a Java to WSDL tool using the following ant command:

<ant antfile="${build.services}" target="generateWSDL">
  <property name="interface.package" value="org.globus.ogsa.guide.impl"/>
  <property name="interface.name" value="Counter"/>
  <property name=”generated.dir” value=”guide”/>
</ant>

See guide/build.xml for the full example.

Note that the ${build.services} property has to point to the location of build-services.xml shipped with the framework. This command will generate a WSDL file and populate it with a binding supporting the required GridService PortType.

Generate Stubs form WSDL
After you have obtained a WSDL interface either by generating it from a java interface or from a wsdl portType definition (Step 2 above), the next step is to generate Java stubs to handle all the serialization/deserialization of your data to/from XML.

<ant antfile="${build.services}" target="generateStubs">
  <property name="schema.file.dir" value="guide/Counter"/>
  <property name="schema.file" value="counter_service.wsdl"/>
</ant> 

See guide/build.xml for the full example.

This command will generate the JAX-RPC compliant interfaces to be used both on the client and on the server side. Client side stub implementations of these interfaces will also be generated.

Bottom Up vs Top Down

We provide two higher level tools based on these primitives called Bottom Up and Top Down. Bottom Up refers to an approach when you start out from a legacy java application and want to generate a Grid service layer on top of it. Top Down refers to an approach when you either get the WSDL from an implementation in another environment or standards community, or you write the WSDL yourself, and want to generate the java mapping for this interface. For a detailed tutorial with examples of how to use these tools please see the Tools Guide. Note that if you use these high level tools you can skip step 3 and 4 below. It is all generated for you.

Step 3. Implement the Service

Inheritance Approach

public class CounterImpl extends GridServiceImpl implements CounterPortType {
    private int val = 0;

    public CounterImpl() {
        super("Guide Counter");
    }
    public int add(int val) throws RemoteException {
        this.val = this.val + val;
        return this.val;
    }
    public int subtract(int val) throws RemoteException {
        this.val = this.val - val;
        return this.val;
    }
    public int getValue() throws RemoteException {
        return this.val;
    }
}

See guide/src/org/globus/ogsa/guide/impl/CounterImpl.java and guide/src/org/globus/ogsa/guide/impl/WSDLCounterImpl.java for the full examples of implementations of the Java Interface approach and the WSDL PortType Interface approach respectively. Note the only thing that differs between the two implementations is the namespace from where you pick up the generated counter interface to implement.

Note that the CounterPortType is the endpoint interface generated in the previous step. All remotely available operations must be public and throw java.rmi.RemoteException as defined in the PortType interface. The GridServiceImpl class may be inherited from. It is provided by our framework and implements the OGSI defined GridService interface, along with other core Grid service behavior.
If your service inherits from GridServiceImpl and overrides any of the standard GridServiceCallback methods such as preCreate(), postCreate(), activate(), deactivate(), and preDestroy() always make sure to invoke the super method of the overridden method, e.g. in your postCreate() implementation make sure to call super.postCreate().

Operation Providers
If you do not want any code to depend on implementation classes in our container you can also implement the service using the operation provider (aka delegation) approach. This design makes it easy to plug in various implementations of wsdl operations at deployment time. As reference points, the OGSI defined NotificationSource and Factory interfaces have been implemented as operation providers in our framework. So to add in factory, or notification behavior to your service you only need to change the deployment descriptor of your service (described in more detail in the next section).

Below is the counter example above implemented as an operation provider:

public class CounterProvider implements OperationProvider, GridServiceCallback {
    // Use the double wild card with care - all operations not in the OGSI 
    // namespace will be delegated to this class by default
    private static final QName[] operations = new QName[]{new QName("", "*")};
    private GridServiceBase base;
    private int val = 0;

    // Operation Provider methods
    public void initialize(GridServiceBase base) throws GridServiceException {
        this.base = base;
    }
    public QName[] getOperations() {
        return operations;
    }
    // Counter PortType methods
    public int add(int val) throws RemoteException {
        this.val = this.val + val;
        return this.val;
    }
    public int subtract(int val) throws RemoteException {
        this.val = this.val - val;
        return this.val;
    }
    public int getValue() throws RemoteException {
        return this.val;
    }
    // GridServiceCallback methods (optional)
    public void preCreate(GridServiceBase base) throws GridServiceException {
    }
    public void postCreate(GridContext context) throws GridServiceException {
    }
    public void activate(GridContext context) throws GridServiceException {
    }
    public void deactivate(GridContext context) throws GridServiceException {
    }
    public void preDestroy(GridContext context) throws GridServiceException {
    }
}

A provider needs to specify all the operation QNames (namespace and local name as defined in wsdl) to implement. Note that we allow the wildcard '*' to be used to specify that all operations from a certain namespace are implemented. An empty string namespace means that operations in all namaspaces apart from the built-in OGSI namespace are implemented. These operations need to be returned in a getOperations() callback. The Provider also needs to provide an implementation of the initialize method which is used to bootstrap the provider and to associate it with a GridServiceBase. The GridServiceBase object is an implementation of the core Grid service behaviors. GridServiceImpl in the previous example is an example of a GridServiceBase implementation. 

See guide/src/org/globus/ogsa/guide/impl/CounterProvider.java for the complete example.

Step 4. Deploy the Service

This step consists of three sub tasks 1) write a deployment descriptor configuring your service, and 2) create a gar package of the configuration along with your implementation, 3) deploy the gar package into a Grid service hosting environment

1)      Write a deployment descriptor

<?xml version="1.0" encoding="UTF-8"?>
<deployment name="defaultServerConfig" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> 
 <service name="guide/counter/CounterProviderFactoryService" provider="Handler" style="wrapped">
  <parameter name="name" value="Guide Counter Provider Factory"/>
  <parameter name="instance-name" value="Guide Counter Proivider Counter"/>
  <parameter name="instance-schemaPath" value="schema/guide/Counter/counter_service.wsdl"/>
  <parameter name="instance-className" value="org.globus.ogsa.guide.Counter.wsdl.CounterPortType"/>
  <parameter name="instance-baseClassName" value="org.globus.ogsa.impl.ogsi.GridServiceImpl"/>
  <parameter name="instance-operationProviders" value="org.globus.ogsa.guide.impl.CounterProvider"/>
  <parameter name="persistent" value="true"/>
  <parameter name="schemaPath" value="schema/ogsi/ogsi_notification_factory_service.wsdl"/>
  <parameter name="baseClassName" value="org.globus.ogsa.impl.ogsi.GridServiceImpl"/>
  <parameter name="handlerClass" value="org.globus.ogsa.handlers.RPCURIProvider"/>
  <parameter name="className" value="org.gridforum.ogsi.NotificationFactory"/>
  <parameter name="allowedMethods" value="*"/>
  <parameter name="factoryCallback" value="org.globus.ogsa.impl.ogsi.DynamicFactoryCallbackImpl"/>
  <parameter name="operationProviders" value="org.globus.ogsa.impl.ogsi.FactoryProvider org.globus.ogsa.impl.ogsi.NotificationSourceProvider"/>
 </service
</deployment>

See guide/guide-config.wsdd for the complete example.

Here is a more detailed description of the various parts of the deployment descriptor:

Services are defined in a <service> tag. The name attribute of the <service> tag defines the remotely accessible name of the service. The service handle will have the form of <hosting environment URL>/foo, where hosting environment URL typically is
http://<host>:<port>/ogsa/services and foo is the name of the service (<service name="foo" ...>)
The configuration information for a service is defined by various parameter sub-elements within a <service> tag. The parameter names prefixed with "instance-" contain configuration formation for the instances created by the service (if the service is a factory service).

Note that for factories almost all the non-instance scoped parameters are boiler-plate configuration and will be the same for most of your factories.

Name Value Description
name <string>

The human readable description of this service to be exposed to e.g. admin clients (optional)

className <class> The className parameter specifies a class or an interface that has public methods corresponding to all wsdl operations. Note that all the operations available in operation providers and base implementations must be exposed in this class or interface.
baseClassName <class> The baseClassName parameter specifies what class implements this service. If it is the same as className it can be left out.(optional)
operationProviders <space separated list of classes>

The list of classes that are to be loaded into this service as operation providers. The list items are separated by spaces. The order of the providers is the order in which they will be initialized. (optional)

persistent <true/false>

This sets the Grid service apart from other web services you may have configured in your hosting environment. If this flag is not present your service will be treated as a regular Web service.
If true, it indicates a persistent service that will be instantiated at container start-up. A service of persistent type cannot be destroyed either via soft-state or explicitly. If false, it indicates a transient service created through a factory.

schemaPath <path to wsdl file>

This variable points the framework to a place where a template WSDL description of the service exists (in this examples, the standard factory description provided by the framework)

In case you are deploying a factory like in this example you need to set the instance-schemaPath to a location describing the interface of the service that the factory can create. In our case this wsdl description was generated in step 1 above

handlerClass <class>

This configuration specifies what dispatcher to use on the server side. The default one we provide in our framework dispatches into the local hosting environment based on the URI of the incoming request and is called org.globus.ogsa.handlers.RPCURIProvider

factoryCallback <class>

This parameter points to a class that implements the FactoryCallback interface used to create service instances. Only used with org.globus.ogsa.impl.ogsi.FactoryProvider operation provider.

lifecycle <true/false>

Flag indicating whether instances are to be checkpointed into the deployment descriptor to maintain state between server lifecycles. Discussed below (optional)


2)      Package your configuration, schemas and code into a gar package

<ant antfile="${build.packages}" target="makeGar">
  <property name="gar.name" value="${build.lib}/guide.gar"/>
  <property name="garlib.dir" value="${build.lib}"/>
  <property name="garserverdeployment.file" value="guide-config.wsdd"/>
  <property name="garschema.origin" value="${build.schema}/guide"/>
  <property name="garschema.path" value="guide"/>
</ant>

See guide/build.xml for the full example.

This sample ant task packages the code (which we assumed you have compiled, e.g with the ant javac target or manually, see full example in the distribution for details). The garserverdeployment.file property points to the deployment descriptor file provided in the previous step.

3)      Deploy the gar package into a Grid service Hosting Environment

In the distribution directory of your ogsa installation make the following command line call:  ant deploy –Dgar.name=<path to gar created in previous step>
To deploy the samples in this guide type ant deployGuide.

2 Writing a Client

The standard JAX-RPC interfaces can be used to access a Grid service. However, for convenience, since JAX-RPC currently has no knowledge of GSRs or GSHs we provide some extensions to simplify access to a Grid service.

2.1 JAX-RPC Example

OGSIServiceLocator factoryLocator = new OGSIServiceLocator();
Factory factory = factoryLocator.getFactoryPort(new URL(url));
GridServiceFactory gridFactory = new GridServiceFactory(factory);
LocatorType locator = gridFactory.createService();
...  
CounterServiceLocator counterLocator = new CounterServiceLocator();
CounterPortType counter = counterLocator.getCounterPort(new URL(instanceUrl));
int val = counter.add(2);  

The problem with this approach is that there is no easy way of feeding the GSR that the factory returns into the instance proxy generation, to make sure you are accessing the service you just created. Note the GridServiceFactory is a wrapper utility we provide to simplify invocations on OGSI factories.

2.2 Extended JAX-RPC Example

OGSIServiceGridLocator gridLocator = new OGSIServiceGridLocator();
Factory factory = gridLocator.getFactoryPort(handle);
GridServiceFactory gridFactory = new GridServiceFactory(factory);
LocatorType locator = gridFactory.createService();
CounterServiceGridLocator counterLocator =   new CounterServiceGridLocator();
CounterPortType counter = counterLocator.getCounterPort(locator);
int val = counter.add(2);  

See guide/src/org/globus/ogsa/guide/impl/CounterClient.java for the full example.

Note that the <service>GridLocator is also generated from the WSDL definition of the service together with the standard JAX-RPC ServiceLocator, so it does not involve any extra effort for a programmer to use this approach. The GridLocator is able to do OGSI based handle to reference resolution, and accepts both an OGSI Handle and an OGSI Locator (returned from a factory creation)  as input to the proxy creation.

3 Testing the Service

3.1 Command Line Client

  1. Make sure you have started a grid service container e.g. using globus-start-container
  2. Create service instance using ogsi-create-service <server url>/<sample factory service name>. The <server url> is typically http://<host>:<port>/ogsa/services. The <sample factory service name> must be the same name as defined in server-config.wsdd.
    Example: ogsi-create-service http://localhost:8080/ogsa/services/guide/counter/CounterFactoryService
  3. Run your command line client created as described in section 2, giving it the handle of the service created by the ogsi-create-service call (make sure your environment is set properly using the setenv scripts.
    Example: java org.globus.ogsa.guide.impl.CounterClient <the handle returned by CreateService> add 10

3.2 GUI client

If you want to test your service in the ServiceBrowser GUI framework you would have to provide a GUI panel implementation for your service port type(s). See org/globus/ogsa/gui/CounterPortTypePanel in the ogsa distribution samples for an example. You would also need to add a mapping to your panel in the <ogsa root>/client-gui-config.xml file. 

To test your GUI client:
  1. Start the gui client by typing globus-service-browser
  2. Locate your factory in the Service Group Entry Inspection panel, and double click on its entry
  3. Create an instance in the Factory panel and now your custom Panel should be displayed if all goes well

PART II: Additional APIs

This section describes some features and APIs available to Grid service developers.

Some APIs are divided into client and server side APIs. Note that client or server is a role played by a runtime component, and does not necessarily translate into a client process or server process, i.e. the communication is peer-to-peer, and anyone can act as either a client or a server.

4 Service Data

The core framework populates all Grid services with service data mandated by the Grid service specification. What service data you get in your service, hence depends on what Grid service PortTypes you implement. As an extension to the specification, we also allow you to expose the ServiceGroup, and NotificationSource service data in your factories (using the respective operation providers in your deployment descriptor) to make it easy to introspect and monitor the instances created by a factory. If you would like to add your own service data, in addition to the standard service data set, we provide an API to do so. We allow you to write an XML Schema type definition for your service data. You could then optionally generate a Java bean from the definition, or treat it as an XML Infoset (like DOM). Both the Bean and DOM can be used to populate your service data set at runtime. We also allow you to specify the service data in Java through annotations, without having to write an XML Schema definition, which will be discussed in section 4.2.1.

4.1 Sample XML Schema Definition

<complexType name="CounterStateType">
    <sequence>
      <element name="value" type="int"/>
      <element name="status" type="string"/>
    </sequence> 
    <attribute name="timestamp" type="dateTime"/>
  </complexType>

See guide/schema/counter_state.xsd for the full example.

This XML Schema fragment provides a definition of a Service Data Element. Note that the complex type must have one single root element. This element may however have many child elements.

4.2 Server APIs

public class ServiceDataCounterImpl extends GridServiceImpl
                                   implements CounterPortType {
    private int val = 0;
    private ServiceData stateData;
    private CounterStateType state = new CounterStateType();

    public ServiceDataCounterImpl() {
        super("Guide Service Data Counter");
    }

    public void postCreate(GridContext context) throws GridServiceException {
        super.postCreate(context);
        stateData = serviceData.create("CounterState");
        updateState();
        stateData.setValue(this.state);
        serviceData.add(stateData);
    }

    private void updateState() {
        state.setStatus(....);
        state.setTimestamp(Calendar.getInstance());
        state.setValue(this.val);
    }

    public int add(int val) throws RemoteException {
        this.val = this.val + val;
        updateState();
        return this.val;
    }
}

See guide/src/org/globus/ogsa/guide/impl/ServiceDataCounterImpl.java for the full example.

The service data element defined in the previous step is run through the stub generator as described in section 1, step 2. This results in a Bean (CounterStateType) that can be used when adding custom service data to your service.  The ServiceDataSet interface (serviceData instance)  is used to create, and add service data to the service data collection of a service. The ServiceData API provides a wrapper API for all service data. You can either set an arbitrary value using the setValue()/addValue() API or provide a value callback (not shown). The ServiceData object should be seen as a logical collection of service data values conforming to a serviceData declaration in WSDL. ServiceData added in this manner will automatically be made available to findServiceData queries on the service after the call serviceData.add() has been made. Note that the above example uses the inheritance approach fro implementing a service in which case the service data set will be available in the instance variable called serviceData. If you implement your service using the operation provider approach, the service data will be available by calling getServiceDataSet() on the GridServiceBase object passed in to to initialize callback.

The service data set  population is performed in a postCreate callback (see section 7 for details) to ensure that the framework has initialized the service data before any operation is called.

To test this example do the following:

  1. java org.globus.ogsa.client.CreateService http://localhost:8080/ogsa/services/guide/counter/ServiceDataCounterFactoryService
  2. java org.globus.ogsa.guide.impl.CounterClient <the handle returned by CreateService> state

4.2.1 Service Data Annotations
You can expose service data automatically by adding an @ogsa:service-data tag to the javadoc comment of a method that returns the service data.  The method must be accessible through the public service, it must be part of the port type interface.  

For example:

/**
* The current value of the counter.
* @ogsa:service-data
*/
public int getValue() throws RemoteException {
    return val;
}

@ogsa:service-data can be followed by optional parameters that will go in the generated wsd

The parameters with their default values are:

name     name of the method (without "get" if those are the first 3 letters, and without the last s if the method returns an array)
minOccurs    1 (0 if the method returns an array)
maxOccurs   1 ("unbounded" if the method returns an array)
mutability      "mutable"
/**
* The current value of the counter.
* @ogsa:service-data
*     name = "currentValue"
*     minOccurs = "1"
*     maxOccurs = "1"
*     mutability = "mutable"
*/
public int getValue() throws RemoteException {
    return val;
}

Then you generate the wsdl by calling two ant targets in build-services.xml

<ant antfile="${build.services}" target="serviceDataDoclet">
  <property name="service.source" value="${src.dir}/org/globus/ogsa/guide/impl/ServiceDataAnnotationCounterImpl.java"/>
  <property name="dest.dir" value="${build.dest}"/>
</ant>
<ant antfile="${build.services}" target="generateSDD">
   <property name="service.name" value="org.globus.ogsa.guide.impl.ServiceDataAnnotationCounterImpl"/>
     <property name="wsdl.dir" value="guide/TimedCounter"/>
     <property name="wsdl.file" value="TimedCounterService.wsdl"/>
     <property name="wsdl.file" value="TimedCounterService.wsdl"/>
</ant>

The method that exposes the service data will automatically be called whenever someone queries for the service data.
See guide/src/org/globus/ogsa/guide/impl/ServiceDataAnnotationCounterImpl.java for the full example.
To test the example do the following:

  1. java org.globus.ogsa.client.CreateService http://localhost:8080/ogsa/services/guide/counter/ServiceDataAnnotationCounterFactoryService
  2. java org.globus.ogsa.guide.impl.CounterClient <the handle returned by CreateService> add 5
  3. java org.globus.ogsa.client.FindServiceDataByName currentValue <the handle returned by CreateService>
  4. java org.globus.ogsa.client.FindServiceDataByName timestamp <the handle returned by CreateService>

4.3 Client APIs

OGSIServiceGridLocator locator = new OGSIServiceGridLocator();
GridService gridService = 
        locator.getGridServicePort(handle);

ExtensibilityType extensibility = gridService.findServiceData(QueryHelper.getNamesQuery("CounterState"));
ServiceDataValuesType serviceData = AnyHelper.getAsServiceDataValues(extensibility);
CounterStateType counterState = (CounterStateType) AnyHelper.getAsSingleObject(serviceData, CounterStateType.class);
System.out.println("Counter state:");
System.out.println("    status:" + counterState.getStatus());
System.out.println("    val:" + counterState.getValue());
System.out.println("    timestamp:" + counterState.getTimestamp().getTime());

See guide/src/org/globus/ogsa/guide/impl/CounterClient.java for the full example.

The GridService interface can be used to query service data for a service. The QueryHelper is used to construct a valid findServiceData query (in this case a OGSI compliant byServiceDataNames query). The query result can contain any arbitrary element, but in this case (as defined by OGSI) we know that it will return a ServiceDataValuesType containing our CounterState of type CounterStateType. Note, that because we know what kind of value is contained in the ServiceDataValuesType we can tell the AnyHelper API how to deserialize the object by passing the class of the object we are expecting. If no class is specified the typemapping registry (see next section) is used to determine how to deserialize the object.

4.4 TypeMappings for Custom Types

If you put custom types in your service data, as opposed to basic types like xsd:string, you will need to make the AnyHelper aware of what types you expect.  The easiest way of doing this is to pass in the type of the class you want to convert the any object to in the getAsObject() calls. If you do not know the type of the any object at compile time, you will need to add a typemapping declaration to the deployment descriptor. Below follows an example of a type mapping that you can add to your service. For full details please see the Axis deployment descriptor documentation.

<typeMapping xmlns:ns="http://www.example.org"
             qname="ns:MyType"
             type="java:org.example.MyType"
             serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
             deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"                  
             encodingStyle=""/>

5 Notifications

5.1 Server APIs

To enable notifications of service data in your server you have to specify NotificationSourceProvider as an operationsProvider in your deployment descriptor. Further,  the className, and the schemaPath interfaces will have to expose the Notification Source operations. The easiest way of achieving this is to extend from the NotificationSource portType in your gwsdl definition. See guide/schema/notification_counter_port_type.gwsdl. Now a notification is sent out to all subscribers of your service data whenever you call notifyChange() on your ServiceData wrapper.  (Complete source can be found in guide/src/org/globus/ogsa/guide/impl/NotificationCounterImpl.java)

5.2 Client APIs

In order to receive notifications from services a client will have to act as a service itself. To make it easy to expose notification sinks in lightweight clients we provide a NotificationSinkManager API. It is in essence a wrapper around a ServiceContainer (see section 10).

Here is an example of how to subscribe to a source:

NotificationSinkManager manager = NotificationSinkManager.getManager();

manager.startListening(NotificationSinkManager.MAIN_THREAD);

String sink  =  manager.addListener("CounterStatus", timeout, source, callback);  

When adding a listener you specify the service data name you want to subscribe to, the timeout of the subscription (if null infinite timeout is set), the handle of the service containing the service data, and a callback where notifications will be sent. The callback must implement the NotificationSinkCallback interface.

Here is an example of a NotificationSink callback implementation:

public void deliverNotification(ExtensibilityType any)
  throws RemoteException {
    try {
        ServiceDataValuesType serviceData = AnyHelper.getAsServiceDataValues(any);
        String counterStatus = (String) AnyHelper.getAsSingleObject(serviceData);
        System.out.println("Counter status:" + counterStatus)
    } catch (Exception e) {
        e.printStackTrace();
    }
}

See guide/src/org/globus/ogsa/guide/impl/StatusListener.java for the full example.
To test this example do the following:

  1. java org.globus.ogsa.client.CreateService http://localhost:8080/ogsa/services/guide/counter/NotificationCounterFactoryService
  2. In a new window start: java org.globus.ogsa.guide.impl.StatusListener <the handle returned by CreateService>
  3. java org.globus.ogsa.guide.impl.CounterClient <the handle returned by CreateService> add 1

6 Service Properties and Configuration

ServiceProperties are used in a similar way to ServiceData by a service to hold Grid service instance specific state. The main difference is that ServiceProperties are internal to the service, i.e. they are not automatically exposed to clients, and they do not have to be specified in an XML Schema.  The ServiceProperties interface, allows you to set and get arbitrary (potentially persistent) properties, keyed on strings. The interface is defined as follows:

package org.globus.ogsa;

public interface ServiceProperties {
  public Object getProperty(String name); 
  public void setProperty(String name, Object obj); 
  public Object getPersistentProperty(String name); 
  public void setPersistentProperty(String name, Object obj); 
  public void flush() throws ServicePropertiesException; 
};

The GridServiceImpl base class implements the ServiceProperties interfaces, and they can hence be used by all Grid services. Persistent property support must however be turned on in the configuration to enable checkpointing, see next section. The framework also uses the service properties API to set service specific context information and configuration. For instance, all the configuration parameters defined in your deployment descriptor will be made available through this API. Note that in the case of factories you configure your instance properties with the "instance-" prefix. These properties will then be automatically be made available to the instances through thair ServiceProperties interface.  

To access configuration global to a container, you can use the ContainerConfig API, like this:

import org.globus.ogsa.config.ContainerConfig;
...
String globalOption = ContainerConfig.getConfig().getOption("myGlobalOption");

All configuration put inside of  the globalConfiguration section in the deployment descriptor is made available through this API.

7 Service Activation, Deactivation, and Recovery Framework

Our container supports activation, deactivation, and recovery from restarts of service instances. The framework makes sure that these server side transitions are completely transparent to a client of the service in terms of all state required to support the Grid service behavior (i.e. Grid service specification required SDEs are always logically available to clients). If you maintain your own state outside of the framework that you would like to maintain in a similar way you can implement this behavior in framework provided callbacks. The interface below can optionally be implemented by operation providers, and factory callbacks. If you use the inheritance approach and extend the GridServiceImpl class you will automatically get these callbacks, but you would need to make sure that you don't disable the default implementation, which is why we recommend implementing these callbacks in operation providers where you don't have that problem.

package org.globus.ogsa;

public interface GridServiceCallback  {
  public void preCreate(GridContext context) throws GridServiceException; 
  public void postCreate(GridContext context) throws GridServiceException; 
  public void activate(GridContext context) throws GridServiceException; 
  public void deactivate(GridContext context) throws GridServiceException; 
  public void preDestroy(GridContext context) throws GridServiceException;
}

The postCreate() callback is guaranteed to be called when the framework has finished populating the service instance with all environment and configuration properties, and optionally recovered persistent state for recoverable services. Activate() and deactivate() can be used to checkpoint or squirrel away state that is not needed when the service is idle. The framework provides default activators and deactivators that can be configured or even replaced  by your own policy implementation. All services deployed into a container are in a deactivated state by default, and then get activated on first use. By default the services will never be deactivated, but a TTL policy can be configured to let idle services time out into deactivated state. A lifecycle monitor interceptor interface can be used to monitor state transitions, it is for instance used by the default deactivator:

package org.globus.ogsa;

public interface ServiceLifecycleMonitor {
  public void create(GridContext context) throws GridServiceException; 
  public void preCall(GridContext context) throws GridServiceException; 
  public void postCall(GridContext context) throws GridServiceException; 
  public void destroy(GridContext context) throws GridServiceException; 
}

A life cycle monitor can be configured for all services listed in a deployment descriptor. It is however typically configured for factories to monitor its instances. Here is an example of such a configuration:

<service name="samples/counter/deactivation/CounterFactoryService" provider="Handler" style="wrapped">
  <parameter name="allowedMethods" value="*"/>
  <parameter name="className" value="org.globus.ogsa.impl.samples.counter.basic.CounterFactoryImpl"/>
  <parameter name="persistent" value="true"/>
  <parameter name="schemaPath" value="schema/core/factory/factory_service.wsdl"/>
  <parameter name="instance-schemaPath" value="schema/samples/counter/counter_service.wsdl"/>
  <parameter name="handlerClass" value="org.globus.ogsa.handlers.RPCURIProvider"/>
  <parameter name="lifecycleMonitorClass" value="org.globus.ogsa.repository.DefaultServiceDeactivator"/>
  <parameter name="instance-deactivation" value="10000"/> <!-- idle
TTL before deactivation in milliseconds-->
 </service>

Apart from the DefaultServiceDeactivator we also provide a Performance Logger implementation (see section 9) of the ServiceLifecycleMonitor interface used to instrument services.

Finally, to tell the framework that you want to allow the service instances to be recoverable, and in order to use the persistent property APIs described above you need to add the following parameter to your deployment descriptor:

 <parameter name="instance-lifecycle" value="persistent"/>

8 Writing a Custom Factory

If the default dynamic factory implementation is not flexible enough, you can write your own factory implementation. The custom factory can be used to virtualize a service in another hosting environment, or it can be implemented to create many different implementations depending on creation input and/or configuration and run time settings. You could also provide a factory callback to provide implementations for the GridServiceCallback methods descried above. Implementing the GridServiceCallback is however optional in a factory. The only required interface that you have to implement is the FactoryCallback interface.

Here is an example of how one would implement a factory for the counter example demonstrated in section 1:

package org.globus.ogsa.guide.impl;

import org.globus.ogsa.FactoryCallback;
import org.globus.ogsa.GridServiceBase;
import org.globus.ogsa.GridServiceException;
import org.gridforum.ogsi.ExtensibilityType;

public class CounterFactoryCallback implements FactoryCallback {
    public void initialize(GridServiceBase base) throws GridServiceException {
    }
    public GridServiceBase createServiceObject(ExtensibilityType extension) 
            throws GridServiceException {
        return new CounterImpl();
    }
}

Then you would need to change your deployment descriptor to make the factoryCallback parameter point to this class.

9 Performance Profiling

There is a pluggable performance logger that you can use to instrument your code. the only thing required to use the logger is to set the following parameter in your factory deployment descriptor:

<parameter name="lifecycleMonitorClass" value="org.globus.ogsa.handlers.PerformanceLifecycleHandler"/>

In order to turn on and off various levels of instrumentation (everything is turned off by default) you need to modify your log4j.properties files. Here is an example:

log4j.category.org.globus.ogsa.performance.samples.any.AnyFactoryService=DEBUG

This entry enables the debug filter for the factory configured with service name "samples/any/AnyFactoryService".

To do application specific instrumentation you can use the PerformanceLog API like this:

import org.globus.ogsa.utils.PerformanceLog;
...
PerformanceLog performanceLogger = new PerformanceLog(MyClass.class.getName() + ".performance");
...
performanceLogger.start();
callMyOp();
performanceLogger.stop("callMyOp");

The performance logger is thread safe in that the start() and stop() only concerns the local thread.

The logs are now enabled using the following configuration:

log4j.category.MyClass.performance=DEBUG

10 Service Container

A service container API is provided to start embedded local hosting environments listening on particular ports. The current embedded hosting environment support the httpg and the http protocols. All services that can run inside of a standalone service container or a servlet engine, can also be run in a embedded mode. The NotificationSinkManager API described in section 5 makes use of this API to multiplex all sink URLs exposed over a single port (per transport). Our test framework also makes use of this API to transparently run all unit tests against both a standalone/tomcat server and an embedded server.

Here is an example:

import org.globus.ogsa.server.ServiceContainer;
...
boolean isMainThread = false;
int port = 8080; // if 0 or omitted get available port from TCP stack
ServiceContainer container = ServiceContainer.createContainer(isMainThread, port);
container.waitForInit();
// now we have entered event loop
container.waitForStop();
// now server has shutdown

11 XPath Queries

We now have experimental support for XPath queries on service data. See full documentation.

12 WSIF Clients

The Web Service Invocation Framework (WSIF)  is a client side run rime environment and API for invoking Web services when not all parts of the WSDL definition is known at compile time. This allows for two things 1) the WSDL binding section and thus the transport mechanism of the request can change at run time 2) the provider used to implement the transport can change at run time. From an OGSI point of view we are most interested in 1), because it is a scenario anticipated by the GSH to GSR refresh model. We provide a simple WSIF client example that demonstrate the client API you need to use to get this dynamic behavior. Note that we still only support the SOAP/HTTP transport although a JMS version has been worked on. We also provide an extension to WSIF that takes advantage of the dynamic type mapping of comples types in Axis. So if you use Axis for the transport implementation, you don't need to set up any type mappings for complex types manually. Here is the simple example:

WSIFServiceFactory factory = WSIFServiceFactory.newInstance();
WSIFService service =  factory.getService(args[1], null, null, GUIDE_NS, "ComplexCounterPortType");
WSIFUtils.registerMappings(service, ComplexCounterPortType.class);

ComplexCounterPortType counter =  (ComplexCounterPortType) service.getStub(ComplexCounterPortType.class);
TimestampedValue timestampedValue = new TimestampedValue();
timestampedValue.setTime(Calendar.getInstance());
timestampedValue.setValue(Integer.parseInt(args[0]));

int val = counter.submitAction(ActionType.add, timestampedValue);

See guide/src/org/globus/ogsa/guide/impl/WSIFCounterClient.java for the full example.

To test this example do the following:

  1. java org.globus.ogsa.client.CreateService http://localhost:8080/ogsa/services/guide/counter/ComplexCounterFactoryService
  2. java org.globus.ogsa.guide.impl.WSIFCounterClient 10 <the handle returned by CreateService>?WSDL