Chapter 25. Hello world example activity

25.1. Developing the server-side activity
25.2. Developing the client-side activity
25.3. Developing the client application

Not wishing to break with tradition our first example will be a Hello World activity. This activity will have no inputs. The activity will have a single output called output to which the activity will write the string "Hello world!".

25.1. Developing the server-side activity

All server-side activities must implement the following interface: uk.org.ogsadai.activity.Activity. The easiest way for an activity to implement this interface is to extend from the following base class: uk.org.ogsadai.activity.ActivityBase.

We are going to call our server-side activity implementation HelloWorldActivity. It is the convention that all server-side activity implementation class names end with Activity. So we declare our class with the following code:

package uk.org.ogsadai.tutorials.activity;

import uk.org.ogsadai.activity.ActivityBase;

public class HelloWorldActivity extends ActivityBase
{
}

By extending from ActivityBase most of the methods required by the Activity interface have been taken care of. The only method we are required to implement is called process and has the following signature:

/**
 * Processes the activity to completion. This method blocks until
 * the processing is complete.
 * 
 * @throws ActivityUserException
 *     If the settings specified by the user prevent processing from
 *     completing.
 * @throws ActivityProcessingException
 *     If an internal error prevents processing from completing.
 * @throws ActivityTerminatedException
 *      If activity processing is terminated at an intermediate
 *      stage. This occurs when the thread processing the activity is
 *      interrupted.
 */
 public void process() throws ActivityUserException,
                              ActivityProcessingException, 
                              ActivityTerminatedException;   

This method is called when the activity is started. The method is called from a thread that is unique to that activity instance.

We therefore need to place all the activity logic within the process method. All we have to do is get the output and write the data to it. To get the output we should first validate that the output exists by calling the validate method of the base class:

validateOutput("output");

This will cause the appropriate exception to be thrown if the client has not specified an output called output. The exception that is thrown if this is the case is called InvalidActivityOutputsException. This exception extends ActivityUserException which is one of the exception classes that the process method can thrown. There is therefore no need to catch this exception within the process method. Note that the exception extends ActivityUserException because the failure to provide an expected output is a user error and should be reported back to the user.

Now that we have validated that the input exists we need a means of writing data to it. To write data to an output we use the following interface: uk.org.ogsadai.activity.io.BlockWriter.

To obtain the BlockWriter for an output we use the getOutput method provided by the base class:

BlockWriter outputBlockWriter = getOutput("output");

The BlockWriter interface is used to write blocks (any Java object) to the activity's output using the write method. This method has the following signature:

/**
 * Writes a block of data into the pipe. This method blocks until the pipe
 * can consume the data or the pipe is closed.
 * 
 * @param block
 *            the block of data
 * @throws PipeClosedException
 *             if the pipe has been closed for reading indicating that the
 *             consumer does not require any more data
 * @throws PipeIOException
 *             if a problem occurs preventing the data from being written
 *             into the pipe
 * @throws PipeTerminatedException
 *             if the pipe read operation is interrupted indicating that the
 *             request containing the pipe is being terminated
 */
 public void write(Object block) 
     throws PipeClosedException, PipeIOException, PipeTerminatedException;

As described in the comment for the method the write method will block if the pipe to which we are writing is full. If this is the case another activity will have to read some data from the pipe before the method can return. To write "Hello world!" to the output we need the following line:

outputBlockWriter.write("Hello world!");

The full server-side activity implementation is:

// Copyright (c) The University of Edinburgh, 2007.
//See OGSA-DAI-Licence.txt for licencing information.

package uk.org.ogsadai.tutorials.activity;

import uk.org.ogsadai.activity.ActivityBase;
import uk.org.ogsadai.activity.ActivityProcessingException;
import uk.org.ogsadai.activity.ActivityTerminatedException;
import uk.org.ogsadai.activity.ActivityUserException;
import uk.org.ogsadai.activity.io.ActivityPipeProcessingException;
import uk.org.ogsadai.activity.io.BlockWriter;
import uk.org.ogsadai.activity.io.PipeClosedException;
import uk.org.ogsadai.activity.io.PipeIOException;
import uk.org.ogsadai.activity.io.PipeTerminatedException;

/**
 * An activity that outputs the string "Hello World!".
 * <p>
 * Inputs: None.
 * <p>
 * Outputs:
 * <ul>
 *   <li>output - output that the string is written to.</li>
 * </ul>
 * 
 * @author The OGSA-DAI Project Team
 */
public class HelloWorldActivity extends ActivityBase
{
    /** Copyright notice */
    private static final String COPYRIGHT_NOTICE = 
        "Copyright (c) The University of Edinburgh, 2007.";

    /**
     * Runs the activity.
     */
    public void process() 
        throws ActivityUserException,
               ActivityProcessingException, 
               ActivityTerminatedException
    {
        try
        {
            // Validate we have an output called 'output'
            validateOutput("output");
    
            // Get the block writer to write data to this output
            BlockWriter outputBlockWriter = getOutput("output");
            
            // Write a block to the output
            outputBlockWriter.write("Hello world!");
        }
        catch (PipeClosedException e)
        {
            // Consumer does not want any more data, just stop.
        }
        catch (PipeIOException e)
        {
            throw new ActivityPipeProcessingException(e);
        }
        catch (PipeTerminatedException e)
        {
            throw new ActivityTerminatedException();
        }            
    }
}

This implementation contains code to handle exceptions. Details of exception handling within server-side activities can be found Section 24.2.4, “Exception handling and activity termination”.

25.2. Developing the client-side activity

Having written a server-side activity we should now write a client-side activity class that allows Java applications to add the activity to workflows. Note that the client-side activity implementation does not do any of the work of the activity - this is all done in the server-side activity implementation. The client-side activity implementation simply allows client developers to specify inputs and access outputs.

The client-side activity class should have the same name as the server-side activity class except that it should not end with Activity. So our client-side activity class is called HelloWorld.

Client-side activity classes must implement the following interface: uk.org.ogsadai.client.toolkit.Activity. Note that this is a different interface from the Activity interface used for server-side activity implementations because the package name is different. The easiest way to write a client-side activity class is to inherit from: uk.org.ogsadai.client.toolkit.activity.BaseActivity

Here is our basic declaration for the HelloWorld activity:

package uk.org.ogsadai.tutorial.activity.client;

import uk.org.ogsadai.client.toolkit.activity.BaseActivity;

/**
 * Client toolkit activity used to call the HelloWorld activity.
 *
 * @author The OGSA-DAI Project Team
 */
public class HelloWorld extends BaseActivity
{
}

The activity implementation should contain a member variable for each input and output. The HelloWorld activity has only one output so we have one declaration:

private ActivityOutput mOutput;

We now need to implement a constructor that passes the default activity name to the base class. This default activity name is the name used to identify the activity we wish to run on the server. This should correspond to the name used to expose the activity on the server. In this case it is "uk.org.ogsadai.tutorial.HelloWorld". The constructor most also initialise the mOutput member variable. The constructor code is:

public HelloWorld()
{
    super(new ActivityName("uk.org.ogsadai.tutorial.HelloWorld"));
    mOutput = new SimpleActivityOutput("output");
}

We are now required to implement three abstract protected methods that are required by the base class. The first of these gives the base class details of the activity's inputs. The HelloWorld activity has no inputs so this is easy:

protected ActivityInput[] getInputs()
{
    return new ActivityInput[]{};
}

The second abstract method gives the base class details of the activity's outputs. The HelloWorld activity has one output so this must be returned:

protected ActivityOutput[] getOutputs()
{
    return new ActivityOutput[]{mOutput};
}

The third abstract method gives the activity implementation an opportunity to validate the inputs and outputs. Most of the basic validation is carried out in the base class so no additional validation is required here. Our implementation therefore does nothing:

protected void validateIOState() throws ActivityIOIllegalStateException
{
    // No further validation to do
}

We now have to implement methods that will be used by client application developers to access the output data from the HelloWorld activity and also to connect the output to other activities. To allow outputs to be connected to other activities every client-side activity must provide a method to access the output. This method should be called getOuputX where X is the name of the output. If the output is called output then the method should be called getOutput rather than getOutputOutput. Our output is called output so the method is called getOutput and is implemented as:

/**
 * Gets the output so that it can be connected to the input of other
 * activities.
 * 
 * @return the activity output.
 */
public SingleActivityOutput getOutput()
{
    return mOutput.getSingleActivityOutputs()[0];
}

Finally we have to provide methods to access the data from the output. Data should always be accessed from outputs in an iterative manner in order to support streaming as much as possible. Thus we use a hasNext and next pattern familiar from Java collections. To check if an output called X has more data we write a method called hasNextX. To get the next output value we write a method called nextX. Thus our method to check if there is any more data is called hasNextOutput and is implemented as:

/**
 * Gets if the activity has a next output value.
 * 
 * @return true if there is another output value, false otherwise.
 *         
 * @throws DataStreamErrorException 
 *             if there is an error on the data stream.
 * @throws UnexpectedDataValueException
 *             if there is an unexpected data value on the data stream.
 * @throws DataSourceUsageException
 *             if there is an error reading from a data source.
 */
public boolean hasNextOutput()
    throws DataStreamErrorException, 
           UnexpectedDataValueException, 
           DataSourceUsageException
{
    return mOutput.getDataValueIterator().hasNext();
}

and our method to get the next output value is called getNextOutput and is implemented as:

/**
 * Gets the next output value.
 * 
 * @return the next output value.
 *         
 * @throws DataStreamErrorException 
 *             if there is an error on the data stream.
 * @throws UnexpectedDataValueException
 *             if there is an unexpected data value on the data stream.
 * @throws DataSourceUsageException
 *             if there is an error reading from a data source.
 */
public String nextOutput()
    throws DataStreamErrorException, 
           UnexpectedDataValueException, 
           DataSourceUsageException
{
    return mOutput.getDataValueIterator().nextAsString();
}

The full client-side activity implementation is:

// Copyright (c) The University of Edinburgh, 2007.
//See OGSA-DAI-Licence.txt for licencing information.

package uk.org.ogsadai.tutorial.activity.client;

import uk.org.ogsadai.activity.ActivityName;
import uk.org.ogsadai.client.toolkit.ActivityOutput;
import uk.org.ogsadai.client.toolkit.SingleActivityOutput;
import uk.org.ogsadai.client.toolkit.activity.ActivityInput;
import uk.org.ogsadai.client.toolkit.activity.BaseActivity;
import uk.org.ogsadai.client.toolkit.activity.SimpleActivityOutput;
import uk.org.ogsadai.client.toolkit.exception.ActivityIOIllegalStateException;
import uk.org.ogsadai.client.toolkit.exception.DataSourceUsageException;
import uk.org.ogsadai.client.toolkit.exception.DataStreamErrorException;
import uk.org.ogsadai.client.toolkit.exception.UnexpectedDataValueException;

/**
 * Client toolkit activity used to call the HelloWorld activity.
 *
 * @author The OGSA-DAI Project Team
 */
public class HelloWorld extends BaseActivity
{
    /** Copyright notice */
    private static final String COPYRIGHT_NOTICE = 
        "Copyright (c) The University of Edinburgh,  2007.";

    /** Default activity name */
    public final static ActivityName DEFAULT_ACTIVITY_NAME = 
        new ActivityName("uk.org.ogsadai.tutorial.HelloWorld");

    /** The activity output */
    private ActivityOutput mOutput;
    
    /**
     * Constructor.
     */
    public HelloWorld()
    {
        super(DEFAULT_ACTIVITY_NAME);
        mOutput = new SimpleActivityOutput("output");
    }

    /**
     * Gets the output so that it can be connected to the input of other
     * activities.
     * 
     * @return the activity output.
     */
    public SingleActivityOutput getOutput()
    {
        return mOutput.getSingleActivityOutputs()[0];
    }
    
    /**
     * Gets if the activity has a next output value.
     * 
     * @return trueif there is another output value, false otherwise.
     *         
     * @throws DataStreamErrorException 
     *             if there is an error on the data stream.
     * @throws UnexpectedDataValueException
     *             if there is an unexpected data value on the data stream.
     * @throws DataSourceUsageException
     *             if there is an error reading from a data source.
     */
    public boolean hasNextOutput()
        throws DataStreamErrorException, 
               UnexpectedDataValueException, 
               DataSourceUsageException
    {
        return mOutput.getDataValueIterator().hasNext();
    }
    
    /**
     * Gets the next output value.
     * 
     * @return the next output value.
     *         
     * @throws DataStreamErrorException 
     *             if there is an error on the data stream.
     * @throws UnexpectedDataValueException
     *             if there is an unexpected data value on the data stream.
     * @throws DataSourceUsageException
     *             if there is an error reading from a data source.
     */
    public String nextOutput()
        throws DataStreamErrorException, 
               UnexpectedDataValueException, 
               DataSourceUsageException
    {
        return mOutput.getDataValueIterator().nextAsString();
    }
    
    /**
     * Gets the activity inputs.
     */
    protected ActivityInput[] getInputs()
    {
        return new ActivityInput[]{};
    }

    /**
     * Gets the activity outputs.
     */
    protected ActivityOutput[] getOutputs()
    {
        return new ActivityOutput[]{mOutput};
    }

    /**
     * Validates the data of the inputs and outputs.
     */
    protected void validateIOState() throws ActivityIOIllegalStateException
    {
        // No further validation to do
    }
}

25.3. Developing the client application

An example client application that uses this activity is shown here. You may have to change the base services URL in the main method depending on your server. By default this is:

  • OGSA-DAI Axis Default: http://localhost:8080/dai/services/

// Copyright (c) The University of Edinburgh, 2007.
// See OGSA-DAI-Licence.txt for licencing information.

package uk.org.ogsadai.tutorial.activity.client.apps;

import java.net.URL;

import uk.org.ogsadai.client.toolkit.DataRequestExecutionResource;
import uk.org.ogsadai.client.toolkit.PipelineWorkflow;
import uk.org.ogsadai.client.toolkit.RequestExecutionType;
import uk.org.ogsadai.client.toolkit.ServerProxy;
import uk.org.ogsadai.client.toolkit.activities.delivery.DeliverToRequestStatus;
import uk.org.ogsadai.client.toolkit.exception.RequestExecutionException;
import uk.org.ogsadai.resource.ResourceID;
import uk.org.ogsadai.tutorial.activity.client.HelloWorld;

/**
 * Application for the HelloWorld activity tutorial.
 *
 * @author The OGSA-DAI Project Team
 */
public class HelloWorldApp
{
    /** Copyright notice */
    private static final String COPYRIGHT_NOTICE = 
        "Copyright (c) The University of Edinburgh, 2007.";
    
    /**
     * Main method.
     * 
     * @param args command line arguments.
     * 
     * @throws Exception if an unexpected error occurs
     */
    public static void main(String[] args) throws Exception
    {
        URL serverBaseUrl = new URL("http://localhost:8080/wsrf/services/dai/");
        ResourceID drerId = new ResourceID("DataRequestExecutionResource");
        
        ServerProxy serverProxy = new ServerProxy();
        serverProxy.setDefaultBaseServicesURL(serverBaseUrl);
        
        DataRequestExecutionResource drer = 
            serverProxy.getDataRequestExecutionResource(drerId);
        
        // Create the activities
        HelloWorld helloWorld = new HelloWorld();
        DeliverToRequestStatus deliverToRequestStatus = 
            new DeliverToRequestStatus();
        
        // Connect the output of HelloWorld to DeliverToRequestStatus
        deliverToRequestStatus.connectInput(helloWorld.getOutput());
        
        // Create the workflow
        PipelineWorkflow pipeline = new PipelineWorkflow();
        pipeline.add(helloWorld);
        pipeline.add(deliverToRequestStatus);
        
        // Excecute the workflow
        try
        {
            drer.execute(pipeline, RequestExecutionType.SYNCHRONOUS);
        }
        catch( RequestExecutionException e)
        {
            System.out.println("There was an error executing the workflow");
            System.out.println(e.getRequestResource().getRequestStatus());
            throw e;
        }
        
        // Get the result and display it
        while( helloWorld.hasNextOutput())
        {
            System.out.println(helloWorld.nextOutput());
        }
    }
}

Note that you may need to alter the value used for the server base URL to correspond to your server installation. The output from this client is:

Hello world!

To get this client to successfully run you must install the server-side activity on your server. To do this compile your server side activity to a jar, for example, HelloWorld.jar then follow the instructions at Section 16.1.8, “Deploying an activity” and Section 16.1.9, “Extending the supported activities of a resource”. When following these instructions use the following values:

  • dai.activity.id: uk.org.ogsadai.tutorial.HelloWorld
  • dai.activity.name: uk.org.ogsadai.tutorial.HelloWorld
  • dai.activity.class: uk.org.ogsadai.tutorials.activity.HelloWorldActivity
  • dai.activity.description: "Hello World activity"

The dai.activity.id and dai.activity.name values must match the default activity name used in the constructor of the client-side activity implementation class HelloWorld. The dai.activity.class value must be the full class name of the server-side activity class. The dai.activity.description can be any suitable textual description.