How to Write a Basic Activity

Activities provide one of the extension points of OGSA-DAI through which users can add new functionality. We described the role of activities in our page on interacting with data service resources

This tutorial describes how to extend OGSA-DAI by writing and deploying a new activity. We use a stringTokenizer activity as a basis for the example. Note that OGSA-DAI already includes such an activity described on the stringTokenizer activity page.

The stringTokenizer activity will sit between two activities at an intermediate stage in an activity chain. It will separate a sequence of string input data into smaller tokens for output. The set of delimiters used to separate tokens may be specified. For example, using comma, colon and space as delimiters, the following string input data:

nichiyoobi,getsuyoobi:kayoobi suiyoobi,mokuyoobi:kinyoobi doyoobi

will be broken into the following seven tokens:

nichiyoobi
getsuyoobi
kayoobi
suiyoobi
mokuyoobi
kinyoobi
doyoobi

There are four stages involved in writing a basic activity:

  1. Define the XML Schema
  2. Write an implementation class in Java
  3. Write a corresponding Client Toolkit class in Java (optional)
  4. Install the activity

1. Define the XML Schema

Every activity has an XML-Schema that describes the structure of the activity XML element that will be written by clients in perform documents. This element is known as an activity element and its XML-Schema will define the inputs and outputs of the activity as well as any additional configuration details. Occurrences of activity elements are used within perform documents to indicate which activities should be performed by a data service resource.

For this example, the XML Schema for the stringTokenizer activity will be constructed by modifying an XML Schema template. Start by copying this Activity Schema Template into a new file named string_tokenizer.xsd. Next make the following changes:

  1. Alter the name of the activity's type to something meaningful:
  2. Alter the names of the activity's input and output to be more informative:
  3. The stringTokenizer will require an element to allow the tokenizing delimiters to be specified. To do this, replace the definition of the myConfiguration element with one for a delimiters element:
    <xsd:element name="delimiters" minOccurs="0" maxOccurs="1">
      <xsd:complexType>
        <xsd:attribute name="value" use="required">
          <xsd:simpleType>
            <xsd:restriction base="xsd:string"/>
          </xsd:simpleType>
        </xsd:attribute>
      </xsd:complexType>
    </xsd:element>
    

    This configuration will allow a user to specify the set of characters that they wish to use as delimiters. For example:

    <delimiters value=", :"/>
    

    will use comma, space and colon characters as separators.

An example of the completed string_tokenizer.xsd XML Schema is available here.

2. Write an implementation class in Java

Every activity has an implementation class that performs the action associated with the activity. Before writing an activity implementation class it is useful to understand the life-cycle of the activity. This is shown using a UML sequence diagram in Figure 1.

UML sequence diagram of an activity's life-cycle
Figure 1: UML sequence diagram of an activity's life-cycle.

Activity Lifecycle - Construction and Configuration

When a perform document is received by a data service resource, the activity elements are first extracted. An activity object corresponding to each activity element is then created. The activity element is passed to the activity object constructor. After construction, the context and session for the activity is set using the configureContext method. From this point onwards, the activity object is able to interact with the context and session in order to access the data resource, inputs and outputs, user credentials and session attributes.

Activity Lifecycle - Initialisation

Following construction and configuration, the initialise method is invoked to perform any initialisation that the activity requires prior to activity processing.

Activity Lifecycle - Processing

Afterwards, the activity processing stage commences. This involves repeated invocations of the process method until the activity status indicates that processing is complete. On the first invocation, the processFirst method will be called then the processBlock method. On subsequent invocations only the processBlock method will be called.

Activity Lifecycle - Cleaning Up

When there is no more processing to do, the activity must indicate so by calling the setCompleted method. Afterwards, the process method shall cease to be invoked. After an activity has completed it will be made eligible for garbage collection and will duly be cleaned up by the JVM.

Writing an Activity Implementation Class

Writing an activity implementation class involves some or all of the following steps:

Returning now to the example, the stringTokenizer activity will need an implementation class. The processing task is to split a stream of input data into a sequence of tokens for output. Start by copying the Activity Java Template into a new file named StringTokenizerActivity.java. Then make the following changes:

  1. Alter the name of the class to correspond with the name of the activity it implements. This is usually done by capitalising the first letter of the activity element and appending Activity to the end. For this example, the stringTokenizer activity element should have an implementation class called StringTokenizerActivity.
  2. Remove the mSetting instance variable and add two new instance variables:
  3. Modify the constructor by updating the names of the input and output elements to reference those defined in the activity's schema:
    String inputName = ((Element) element.getElementsByTagNameNS(
        OGSADAIConstants.TYPES_NAMESPACE,
        "stringBlocksInput").item(0)).getAttribute("from");
    String outputName = ((Element) element.getElementsByTagNameNS(
        OGSADAIConstants.TYPES_NAMESPACE,
        "stringTokensOutput").item(0)).getAttribute("name")
        ;
    
  4. Continue to modify the constructor by changing the code that extracts the configuration information from the activity element. We need to extract the value attribute of the delimiters element and set the mDelimiters instance variable. If this optional element has been ommitted or the attribute value has been left blank, the default value should be set to space.
    NodeList list = element.getElementsByTagNameNS(
        OGSADAIConstants.TYPES_NAMESPACE, "delimiters");
    mDelimiters = (list.getLength()>0)
        ? ((Element) list.item(0)).getAttribute("value")
        : " ";
    
  5. Modify the processBlock method so that on each call a token of input data is output. When there is no more input data to tokenize and no more tokens to output, the activity processing is complete. Some guidelines are given below:

An example of the completed StringTokenizerActivity.java class is available here.

Interacting with the Data Resource

If an activity needs to interact with a data resource then this is typically done through the data resource accessor object. The data resource accessor object will implement the uk.org.ogsadai.dataresource.DataResourceAccessor interface. Activities can get a reference to the data resource accessor object object via the mContext member variable of the base Activity class:

DataResourceAccessor dataResourceAccessor = 
    mContext.getDataResourceAccessor();

The mContext field also provides access to the activity inputs and outputs and security context. Note that the mContext field must not be used from within an activity constructor, because it has not yet been initialised. See the description of the activity life-cycle above for more details. It can however be used from within the initialise, processFirst and processBlock methods.

This DataResourceAccessor interface is not much use to activity developers so you will wish to cast the object returned by the getDataResourceAccessor() method to another interface or class. This would normally be carried out within the initialise method in order to initialise a field. The core data service resources provided with OGSA-DAI can be cast to the following interfaces:

Relational Data Resource Accessor
Accessor class: uk.org.ogsadai.dataresource.JDBCDataResourceAccessor
Interface: uk.org.ogsadai.dataresource.JDBCConnectionProvider
XML Data Resource Accessor
Accessor class: uk.org.ogsadai.dataresource.XMLDBDataResourceAccessor
Interface: uk.org.ogsadai.dataresource.XMLDBCollectionProvider
File System Data Resource Accessor
Accessor class: uk.org.ogsadai.dataresource.FilesDataResourceAccessor
Interface: uk.org.ogsadai.dataresource.FileAccessProvider

It is strongly advised that you cast to the interface rather than the accessor class. This will allow your activity to work with any data service accessors that implement the interface. For example, if you are developing an activity that connects to a relational database using JDBC then the DataResourceAccessor object should be cast to the JDBCConnectionProvider interface as show here:

JDBCConnectionProvider connectionProvider =
    (JDBCConnectionProvider) mContext.getDataResourceAccessor();

The connectionProvider object can then be used to obtain and release JDBC connection to the data resource.

Interacting with the Session

If an activity needs to interact with a session, the uk.org.ogsadai.sessions.Session interface can be accessed using the getSession method of the base Activity class. The session allows the activity to store or access session attributes, session streams and data service resource properties.

Session session = getSession();
SessionAttribute attribute = session.getAttribute(new QName("MyAttribute"));
Properties properties = session.getProperties();

Interacting with the Access Authorizer

If an activity needs to interact with the authorizer which controls access to resources and activities, the AccessAuthorizer interface can be accessed using the getAuthorizer method of the ActivityContext.

AccessAuthorizer authorizer = mContext.getAuthorizer();

For more information on access authorization see the How to Authorize Access to Resources and Activities tutorial.

3. Write a corresponding Client Toolkit class in Java (optional)

If you do not wish to interact with your new activity using the Client Toolkit then this step can be ommitted. Otherwise a Client Toolkit activity implementation class is needed. This is a client-side class that corresponds to the server-side activity. The main responsibilities of such a class are to generate the activity element for embedding in a perform document, and to provide access to the results of the activity in a convenient form.

For this example, the Client Toolkit activity implementation will be written by modifying a Client Toolkit Activity Java Template. Note that the class in the template extends uk.org.ogsadai.client.toolkit.activity.Activity. This is an abstract base class for all client activity implementations. It provides common functionality and defines the abstract generateXML method that must be implemented by concrete sub-classes. An implementation of this method must generate the XML activity element suitable for embedding into a perform document.

Start by copying the template into a new file named StringTokenizerActivity.java. Make sure that the implementation file from step 2 is not overwritten by saving this file in a different directory. To complete the client activity implementation, follow the steps below:

  1. Define instance variables for storing configuration information for the activity. These variables will normally be initialised via constructors or set methods of the class. For the stringTokenizer activity, an instance variable is needed to store the tokenizing delimiters.
  2. Rename the class and constructor to StringTokenizerActivity. Next modify the constructor code to register any inputs and outputs with the superclass using the addInput and addOutput methods. The constructor should be parameterised to enable the configuration instance variables to be initialised. For the stringTokenizer activity, the constructor should register one input, one output and initialise the mDelimiters instance variable. Note that the strings passed into the setInput and setOutput methods are merely descriptive.
    /**
     * Constructs an activity to tokenize the input using the specified
     * delimiters.
     *
     * @param  delimiters  the delimiters used to separate tokens
     */
    public StringTokenizerActivity(final String delimiters)
    {
        addInput("String blocks input");
        addOutput("String tokens output");
        mDelimiters = delimiters;
    }
    
  3. The next step is to define set methods for any additional configuration. These are normally used for optional settings that are not required to construct a valid activity. The stringTokenizer activity does not require any additional configuration, so simply remove the setSettingC method from the template code.
  4. Define setter methods for any inputs to the activity. These are used to allow activities to be chained together. For example, the output of an sqlQuery activity can be connected to the input of an xslTransform activity by invoking the XSLTransform.setInput method. The stringTokenizer activity has one input and the setInput method defined in the template allows the output of another activity to be connected to it, so no changes are required for this stage.
  5. Define getter methods for accessing any outputs of the activity. These outputs can be connected to the inputs of other activities to form activity chains. The stringTokenizer activity has one output for the tokenized data. The getOutput method defined in the template provides access to this output, so no changes are required for this stage.
  6. The abstract uk.org.ogsadai.client.toolkit.Activity class defines some methods for accessing the output data written by an activity directly. For some activities it may be desirable to define get methods for accessing this data, or part of this data, in a more usable form. For example, the SQLQuery client activity class provides a getResultSet method that gets the output of the activity as a java.sql.ResultSet rather than an XML string. The stringTokenizer activity is designed to sit at an intermediate stage in a chain of activities, so there is no need for any special get methods for accessing the output data. Remove the getSomething method from the template code.
  7. Finally, and most importantly, modify the generateXML method to construct the XML that will be embedded into a perform document for this activity. This usually consists of appending element and attribute XML to a StringBuffer and depends on the configuration information passed to the constructor and set methods.
    protected String generateXML()
    {
        final StringBuffer sb = new StringBuffer();
    
        sb.append("<stringTokenizer name=\"");
        sb.append(getName());
        sb.append("\">\n");
    
        // stringBlocksInput sub-element
    
        sb.append("  <stringBlocksInput from=\"");
        sb.append(getInputParameters()[0].getOutputName());
        sb.append("\"/>\n");
    
        // delimiters sub-element
    
        sb.append("  <delimiters value=\"");
        sb.append(mDelimiters);
        sb.append("\"/>\n");
    
        // stringTokensOutput sub-element
    
        sb.append("  <stringTokensOutput name=\"");
        sb.append(getOutputs()[0].getName());
        sb.append("\"/>");
    
        sb.append("</stringTokenizer>");
    
        return sb.toString();
    }
    

An example of a completed Client Toolkit String Tokenizer Activity is provided.

4. Install the Activity

Once you have defined an XML schema, provided a Java implementation, and optionally written a Client Toolkit activity, your activity is ready to be installed into OGSA-DAI. To install a new activity:

  1. Compile the activity class. For example, for StringTokenizerActivity.java:
    $ javac my/package/StringTokenizerActivity.java
    
    Ensure that the CLASSPATH environment variable contains ogsadai-core.jar and log4j-x.x.x.jar (were x.x.x matches the version you are using).
  2. Make the classes that implement the activity available to OGSA-DAI by building a JAR and copying it into:
  3. Add an entry for the activity to the activity configuration documents used by any data service resources you wish to support the new activity. See the Activity Configuration Document and Where Do Files Reside After Deployment pages for more details. The entry should be in the following format:
    <activity name="ACTIVITY-NAME"
      implementation="ACTIVITY-IMPLEMENTATION-CLASS"
      schema="ACTIVITY-XML-SCHEMA"/>
    
    For example:
    <activity name="stringTokenizer"
      implementation="my.package.StringTokenizerActivity"
      schema="string_tokenizer.xsd"/>
    

    It should be noted that the name attribute here is case sensitive, and specifies the activity element name that will be used within perform documents.

  4. Place the XML Schema file for the activity in the location pointed to by the base attribute of the activityMap element in the activity configuration document. Usually this will be the in the directory:

Now the new activity will be available when the service container is restarted. The activity element can be embedded into perform documents sent to any OGSA-DAI services that are configured using the activity configuration documents you modified above.

To test your activity try sending the following perform document using the End-to-end Client. The dataStore activity sets up the data which is then passed to your new activity. You should see from the output that the delimiter characters have been removed.

<perform 
  xmlns="https://ogsadai.org.uk/namespaces/2005/10/types">

  <dataStore name="parameters">
    <item>nichiyoobi,getsuyoobi:kayoobi suiyoobi</item>
    <item>mokuyoobi:kinyoobi doyoobi</item>
    <itemCursor name="dataStoreOutput"/>
  </dataStore>

  <stringTokenizer name="myTokenizer">
    <stringBlocksInput from="dataStoreOutput"/>
    <delimiters value=" ,:" />
    <stringTokensOutput name="tokenOutput"/>
  </stringTokenizer>
</perform>

If you wrote a Client Toolkit activity class for the new activity, then clients written using the Client Toolkit APIs can also make use of the new activity.

Further Information

This example has shown how to write a simple activity and provided some general guidelines. For more information it is recommended that you download the OGSA-DAI source distribution and examine the source code. The activities provided with OGSA-DAI are stored in sub-packages of uk.org.ogsadai.activity and the client activities within sub-packages of uk.org.ogsadai.client.toolkit.activity. The activities that are included with OGSA-DAI are described on the Activities Page of the documentation.