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!".
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”.
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
}
}
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 GT Default:
http://localhost:8080/wsrf/services/dai/
// 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.HelloWorlddai.activity.name: uk.org.ogsadai.tutorial.HelloWorlddai.activity.class: uk.org.ogsadai.tutorials.activity.HelloWorldActivitydai.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.