Core: Developer's Guide

Overview
APIs
Programmer's Tutorial
Java Programmer's Guide: Getting Started
>Java Programmer's Guide: Additional APIs
C Programmer's Guide
Performance Guide
Security Support
Test Framework

Java Programming Guide: 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.

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].

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.

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 Writing a Service is run through the stub generator as described in 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 Service Activation, Deactivation, and Recovery Framework 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 calc
2 . java org.globus.ogsa.guide.impl.CounterClient http://localhost:8080/ogsa/services/guide/counter/ServiceDataCounterFactoryService/calc state

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 annotcalc 
2. java org.globus.ogsa.guide.impl.CounterClient http://localhost:8080/ogsa/services/guide/counter/ServiceDataAnnotationCounterFactoryService/annotcalc add 5 
3. java org.globus.ogsa.client.FindServiceDataByName currentValue http://localhost:8080/ogsa/services/guide/counter/ServiceDataAnnotationCounterFactoryService/annotcalc
4. java org.globus.ogsa.client.FindServiceDataByName timestamp http://localhost:8080/ogsa/services/guide/counter/ServiceDataAnnotationCounterFactoryService/annotcalc

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.

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=""/>

Notifications

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)

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 Service Container).

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   calc
2. in a new window start: java org.globus.ogsa.guide.impl.StatusListener http://localhost:8080/ogsa/services/guide/counter/NotificationCounterFactoryService/calc
3. java org.globus.ogsa.guide.impl.CounterClient http://localhost:8080/ogsa/services/guide/counter/NotificationCounterFactoryService/calc   add 1

Start up a new window and run:

java org.globus.ogsa.guide.impl.StatusListener

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.

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 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 Performance Profiling) 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"/> 

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 Writing a Service:

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.

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 

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 Notifications 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

XPath Queries

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

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 are 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   calc
2. java org.globus.ogsa.guide.impl.WSIFCounterClient 10 http://localhost:8080/ogsa/services/guide/counter/ComplexCounterFactoryService/calc?WSDL