Chapter 109. Client toolkit activity proxies and activity inputs and outputs

109.1. Setting up inputs and outputs
109.1.1. Multiple inputs and outputs of the same name
109.2. How to allow activities to be connected to other activities
109.2.1. Multiple inputs and outputs that have the same name
109.3. How to allow clients to provide input values
109.3.1. Simple types
109.3.2. OGSA-DAI lists of values
109.3.3. OGSA-DAI lists of character arrays
109.3.4. OGSA-DAI lists of byte arrays
109.3.5. Multiple inputs and outputs that have the same name
109.4. Validation of inputs and outputs

109.1. Setting up inputs and outputs

Your client toolkit proxy needs to set up objects that represent the activity's inputs and outputs. Your activity should define a member variable for each input and each output. These should be named as follows:

mXInput
mXOutput

Where X is the name of the input. If an input is called Input then the name mInput can be used and likewise, mOutput if an output is called Output.

The inputs and outputs should be of type:

uk.org.ogsadai.client.toolkit.activity.ActivityInput
uk.org.ogsadai.client.toolkit.activity.ActivityOutput

We provide two implementations of these interfaces each of which take, in their constructor the name of the activity inputs and outputs as they are recorded in the server-side activity implementation. The classes are:

uk.org.ogsadai.client.toolkit.activity.SimpleActivityOutput
uk.org.ogsadai.client.toolkit.activity.SimpleActivityOutput

So for example, our server-side activity uk.org.ogsadai.activity.sql.SQLQueryActivity has an input called expression and an output called data. So for the client proxy we define:

private final ActivityInput mExpressionInput =
    new SimpleActivityInput("expression");
private final ActivityOutput mDataOutput =
    new SimpleActivityOutput("data");

Note if an input is optional then the following can be used:

private final ActivityInput mMyExampleInput = 
    new SimpleActivityInput("myExample", SimpleActivityOutput.OPTIONAL);

109.1.1. Multiple inputs and outputs of the same name

If an activity supports multiple inputs with the same name (activity input with multiple occurences) or multiple outputs with the same name (activity output with multiple occurrences) then we need a way for the client developer to specify how many of these they require.

For such activity inputs we define a method, setNumberOfXInputs, where X is the name of the input. For example:

public void setNumberOfXInputs(int count)
{
    mXInput.setNumberOfInputs(count);
}

For such activity outputs we define a method, setNumberOfXOutputs, where X is the name of the output. For example:

public void setNumberOfXOutputs(int count)
{
    mXOutput.setNumberOfOutputs(count);
}

What happens, behind the scenes, is that the client toolkit views each ActivityInput as a collection of SingleActivityInput objects. For most activity inputs there will be just one SingleActivityInput object in the collection. For multiple inputs of the same there will be 1 to N. And, similarly for ActivityOutput objects there are associated SingleActivityOutput objects.

109.2. How to allow activities to be connected to other activities

Workflows are formed when activities are connected to other activities. You therefore need to provide methods that allow client developers to connect activity proxies together and so define their workflows.

For each non-multiple input that an activity has the client-side activity class should provide a method called:

connectXInput

where X is the name of the input. If the input is called Input then the name connectInput can be used.

These methods typically have the following definition:

public void connectXInput(ActivityOutput output);
{
    mXInput.connect(output);
}

where mXInput is the ActivityInput object for this input.

And, of course, we need a way to get the outputs of an activity to connect them to the inputs. For each output an activity has we need to provide a method called:

getXOutput

where X is the name of the output. If the output is called Output then the name getOutput can be used.

These methods typically have the following definition:

public SingleActivityOutput getXOutput()
{
    return mXOutput.getSingleActivityOutputs()[0];
}

where mXOutput is the ActivityOutput object for this output.

109.2.1. Multiple inputs and outputs that have the same name

For activities that support multiple inputs of the same name we need to allow the client developer to specify the index of the input they want to connect to. Our connectXInput methods are therefore defined as:

public void connectXInput(int index, SingleActivityOutput output)
{
    mXInput.connect(index, output);
}

Likewise for activities that support multiple outputs of the same name we need to allow the client developer to specify the index of the output they want to connect to. Our getXOutput methods are therefore defined as:

public SingleActivityOutput getXOutput(int count)
{
    return mXOutput.getSingleActivityOutputs()[count];
}

where mXOutput is the ActivityOutput object for this output.

109.3. How to allow clients to provide input values

It is useful to allow clients to provide the input values for activities directly rather than everything coming from the output from another activity. The most common example of this is allowing the client to provide a query expression for an SQL query or XPath activity.

If the server-side activity expects values that are primitive types e.g.:

java.lang.String   
char[]
byte[]
java.lang.Boolean
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Double 
java.lang.Date 
java.lang.Calendar

Then these can be provided by the client directly. These are provided via methods called addXInput where X is the name of the input. If the input is called Input then the name addInput can be used.

How these methods are defined depends upon the primitive types to be supported.

109.3.1. Simple types

To allow the client to provide input values of the following types

java.lang.String   
java.lang.Boolean
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Double 
java.lang.Date 
java.lang.Calendar

we wrap the values in associated classes from the package:

uk.org.ogsadai.data

Here are examples of add methods for each type:

public void addX(boolean x)
{
    mXInput.add(new BooleanData(x));
}

public void addX(Date x)
{
    mXInput.add(new DateData(x));
}

public void addX(double x)
{
    mXInput.add(new DoubleData(x));
}

public void addX(float x)
{
    mXInput.add(new FloatData(x));
}

public void addX(int x)
{
    mXInput.add(new IntegerData(x));
}

public void addX(long x)
{
    mXInput.add(new LongData(x));
}

public void addX(String x)
{
    mXInput.add(new StringData(x));
}

109.3.2. OGSA-DAI lists of values

If the input is an OGSA-DAI list of objects of objects then we provide two methods that take the list of objects and add these, as well as objects representing OGSA-DAI list begin and end markers to the ActivityInput objects. The list markers are denoted using the following:

uk.org.ogsadai.data.ListBegin
uk.org.ogsadai.data.ListEnd

The first method takes a java.util.Iterator containing objects of the required type. It adds the list begin marker, then traverses the iterator wrapping the values in the appropriate wrapper as already described, then adds the list end marker. So, for example, for an OGSA-DAI list of java.lang.String we'd have:

public void addX(Iterator data)
{
    mXInput.add(ListBegin.VALUE);
    while (data.hasNext())
    {
        Object value = data.next();
        if (! (value instanceof String))
        {
            throw new IllegalArgumentException("A non-String was found!");
        }
        mXInput.add(new StringData((String)data.next));
    }
    mDataInput.add(ListEnd.VALUE);
}

The second method takes an array of objects of the required type and operates in much the same way. For example:

public void addX(String[] data)
{
    mXInput.add(ListBegin.VALUE);
    for (int i = 0; i < data.length; i++)
    {
        mDataInput.add(new StringData(data[i]));
    }
    mDataInput.add(ListEnd.VALUE);
}

109.3.3. OGSA-DAI lists of character arrays

If the input is a list of char[] then we need to provide an addX method that takes a java.io.Reader as input and reads chunks of char[] and adds them to the input. We provide a class that does the bulk of this work:

uk.org.ogsadai.client.toolkit.Utilities;

So the method simply becomes:

public void addX(Reader reader) throws IOException
{
    mXInput.add(ListBegin.VALUE);
    Utilities.readCharData(mXInput, reader, 2048);
    mXInput.add(ListEnd.VALUE);
}

The final argument to readCharData is just a suitable size for each character array in the list.

109.3.4. OGSA-DAI lists of byte arrays

If the input is a list of byte[] then we need to provide an addX method that takes an InputStream as input and reads chunks of byte[] and adds them to the input. We provide a class that does the bulk of this work:

uk.org.ogsadai.client.toolkit.Utilities;

So the method simply becomes:

public void addX(InputStream stream) throws IOException
{
    mXInput.add(ListBegin.VALUE);
    Utilities.readBinaryData(mXInput, stream, 2048);
    mXInput.add(ListEnd.VALUE);
}

The final argument to readBinary is just a suitable size for each byte array in the list.

109.3.5. Multiple inputs and outputs that have the same name

For activities that support multiple inputs of the same name we need to allow the client developer to specify the index of the input they want to connect to. This just requires our addXInput methods to take the index of the input and this to be passed with the input value itself to the ActivityInput. For example:

public void addXInput(int index, String x)
{
    mXInput.add(index, new StringData(x));
}

109.4. Validation of inputs and outputs

The base client toolkit activity classes will do most of the client toolkit activity input and output validation before sending the request to the server. If however you need to do validation across the inputs then there is a method that can be implemented within which this can be done. For example, an activity might be defined to have one of inputs A or B but never both. Validating such conditions client-side means that the round-trip time of submitting the request to the server only to have it fail is avoided.

To validate activity inputs and outputs requires overriding the method:

protected void validateIOState() throws ActivityIOIllegalStateException;

Very few activities need this type of validation so typically the method is empty.