Chapter 103. Extending the resource factory activity

ExtendedCreateRelationalResource
103.1. Coding the extended relational resource factory activity
103.1.1. Declaring our activity
103.1.2. Telling the activity framework what the activity needs
103.1.3. Creating a resource from a template
103.1.4. Our completed activity
103.2. How to deploy the extended relational resource factory activity
103.3. How to write the extended relational resource factory client proxy
103.4. How to write an extended relational resource factory client

In the previous section, we described how to write a resource factory which allows clients to request creation of relational resources. In this section we discuss one way in which our example relational resource factory activity can be extended to give more control to clients, allowing them to specify the database driver class name, the database URL, a user name and a password.

The specification of the example activity we will develop is given in the next section and then we describe how it is developed.

103.1. Coding the extended relational resource factory activity

Extending our relational resource factory activity of the previous section to give more control for clients in terms of the resource configuration they can provide is just a matter of primariy declaring and handling more inputs and then using these.

103.1.1. Declaring our activity

Firstly we need to declare our activity. Again we can declare it to be a sub-class of MatchedIterativeActivity.

We can then set up attributes to hold the names of our activity inputs - for which we now also have the driver class name, database URL, username and password. Our list of inputs and outputs therefore is:

/** Activity input name - ID of the new resource to create. */
public static final String INPUT_ID = "resourceId";

/** Activity input name - ID of the resource template. */
public static final String INPUT_TEMPLATE_ID = "templateId";

/** Activity input name - database driver class name. */
public static final String INPUT_DRIVER = "dbDriver";

/** Activity input name - database URL. */
public static final String INPUT_URL = "dbURL";

/** Activity input name - database username. */
public static final String INPUT_USER = "dbUser";

/** Activity input name - database password. */
public static final String INPUT_PASSWORD = "dbPass";

/** Activity output name - ID of the newly created resource. */
public static final String RESULT = "result";

We then implement the getIterationInputs method required by matched iterative activities. We have more inputs so the declaration becomes:

protected ActivityInput[] getIterationInputs()
{
    return new ActivityInput[] 
    {
        // Input 0
        new TypedOptionalActivityInput(INPUT_ID, String.class),
        // Input 1
        new TypedOptionalActivityInput(INPUT_TEMPLATE_ID, String.class),
        // Input 2
        new TypedOptionalActivityInput(INPUT_DRIVER, String.class),
        // Input 3
        new TypedOptionalActivityInput(INPUT_URL, String.class),
        // Input 4
        new TypedOptionalActivityInput(INPUT_USER, String.class),
        // Input 5
        new TypedOptionalActivityInput(INPUT_PASSWORD, String.class)
    };
}

We then need to implement the preprocess method which is executed before the activity starts processing in earnest and the postprocess method. These have the same implementation as that for CreateRelationalResourceActivity

103.1.2. Telling the activity framework what the activity needs

We now need to determine the extension interfaces our activity needs to implement. Our new activity requires exactly the same extension interfaces as CreateRelationalResourceActivity i.e.:

  • uk.org.ogsadai.activity.extension.ResourceFactoryActivity.
  • uk.org.ogsadai.activity.extension.ResourceManagerActivity.
  • uk.org.ogsadai.activity.extension.ConfigurableActivity.
  • uk.org.ogsadai.activity.extension.SecureActivity.

With the addition of these interfaces our activity declaration becomes:

public class ExtendedCreateRelationalResourceActivity 
    extends MatchedIterativeActivity
    implements ResourceManagerActivity, 
               ResourceFactoryActivity,
               ConfigurableActivity,
               SecureActivity

103.1.3. Creating a resource from a template

Now we are again ready to implement the core functionality of the activity, and fill in the body of the processIteration method:

Most of the functionality is as for CreateRelationalResourceActivity. However we need to handle and use our existing inputs.

Firstly, we now need to get the database driver class name, URL, username and password, in addition to the resource ID and template ID we already get. We can do this as follows.

// Get database driver.
String dbDriver = null;
if (iterationData[2] != null)
{
    // Use one given as input.
    dbDriver =  (String)iterationData[2];
}
// Get database URL.
String dbURL = null;
if (iterationData[3] != null)
{
    // Use one given as input.
    dbURL =  (String)iterationData[3];
}
// Get database user.
String dbUser = null;
if (iterationData[4] != null)
{
    // Use one given as input.
    dbUser =  (String)iterationData[4];
}
// Get database password
String dbPassword = null;
if (iterationData[5] != null)
{
    // Use one given as input.
    dbPassword =  (String)iterationData[5];
}

Again, using the template ID we can now use the resource factory to get this template and create a new resource object from it using a call to the same createResource method as we used for CreateRelationalResourceActivity, again checking for ResourceUnknownException for cases where the resource template cannot be found.

Again we need to set the resource ID on the resource state:

JDBCDataResourceState state = 
    resource.getJDBCDataResourceState();
state.getDataResourceState().setResourceID(resourceID);

But now we also need to set our database driver class name and URL, which we can do as follows:

// Set database driver.
if (dbDriver != null)
{
    state.setDriverClass(dbDriver);
}
// Set database URL.
if (dbURL != null)
{
    state.setDatabaseURL(dbURL);
}

If the values were null then we don't set them so the defaults in the resource template will be used.

Again, we then need to get the login provider for the template resource. Again we can use a getLoginProvider method.

If the user didn't provide a user name then we need to get a login using the current security context as we did in CreateRelationalResourceActivity, we can use the same getLogin method.

And we'll need the updateLogin method again since that allows us to record the login for the new resource.

Putting this all together yields a slightly different version of processIteration since we have to use the username and password from the client if they supplied one. So we now have:

// Get login provider.
LoginProvider loginProvider = getLoginProvider(state);

SecurityContext context = mSecurityContext;
// Get login for new resource.
Login login = null;
if (dbUser == null) 
{
    // Use login for template resource.
    login = getLogin(templateID, loginProvider, context);
    if (LOG.isDebugEnabled())
    {
        LOG.debug("Got login: " + login);
    }
} 
else
{
    if (dbPassword == null)
    {
        dbPassword = "";
    }
    // Create new login.
    login = new Login(dbUser, dbPassword);
}
// Set login for new resource using current security
// context.
updateLogin(resourceID, loginProvider, login, context);

Note how if there is no username we proceed as for CreateRelationalResourceActivity and use the login recorded for the template resource. However if there was a username from the client then we create a new login from that and the password.

Our resource is now ready for use so we can register it with the OGSA-DAI resource manager via the resource factory and then output the resource ID, as we did for CreateRelationalResourceActivity:

mResourceFactory.addResource(resourceID, resource);
mResult.write(resource.getResourceID().toString());

Again we need to wrap everything in a try-catch block to handle any exceptions that arise, from the point at which we try and create the new resource ie.

try
{
    // Create resource from template.
    JDBCDataResource resource = null;
    try
    {
        resource = createResource(templateID);
    } 
    catch (ResourceUnknownException e)
    {
        if (iterationData[1] == null)
        {
            // Cannot be found.
            throw e;
        }
        else
        {
            // Cannot be found but was requested by client.
            throw new ActivityUserException(e);
        }
    }
    if (LOG.isDebugEnabled())
    {
        LOG.debug("Created resource: " + resource);
    }
    JDBCDataResourceState state = 
        resource.getJDBCDataResourceState();
    state.getDataResourceState().setResourceID(resourceID);
    // Set database driver.
    if (dbDriver != null)
    {
        state.setDriverClass(dbDriver);
    }
    // Set database URL.
    if (dbURL != null)
    {
        state.setDatabaseURL(dbURL);
    }
    // Get login provider.
    LoginProvider loginProvider = getLoginProvider(state);
    if (LOG.isDebugEnabled())
    {
        LOG.debug("Got login provider: " + loginProvider);
    }
    SecurityContext context = mSecurityContext;
    // Get login for new resource.
    Login login = null;
    if (dbUser == null) 
    {
        // Use login for template resource.
        login = getLogin(templateID, loginProvider, context);
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Got login: " + login);
        }
    } 
    else
    { 
        if (dbPassword == null)
        {
            dbPassword = "";
        }
        // Create new login.
        login = new Login(dbUser, dbPassword);
    }
    // Set login for new resource using current security
    // context.
    updateLogin(resourceID, loginProvider, login, context);
    // Add the resource to the OGSA-DAI resource manager
    // via the resource factory utility.
    mResourceFactory.addResource(resourceID, resource);
    // Output the resource ID.
    mResult.write(resource.getResourceID().toString());
}
catch (PipeClosedException e)
{
    // No more input wanted.
    iterativeStageComplete();
}
catch (PipeTerminatedException e)
{
    throw new ActivityTerminatedException();
}
catch (PipeIOException e)
{
    throw new ActivityPipeProcessingException(e);
}
catch (ResourceIDAlreadyAssignedException e)
{
    throw new ActivityUserException(e);
}
catch (ActivityUserException e)
{
    throw e;
}
catch (Exception e)
{
    throw new ActivityProcessingException(e);
}

And that's the activity completed.

103.1.4. Our completed activity

The full source code for an activity just like this example can be seen in an OGSA-DAI source distribution in the directory:

src/activities/uk/org/ogsadai/activity/management/CreateExtendedRelationalResourceActivity.java

Note that this has a different package name to the one in this tutorial so if cutting and pasting please use the tutorial package name of:

uk.org.ogsadai.tutorials.activity

103.2. How to deploy the extended relational resource factory activity

Now we deploy the activity onto the server and then expose the activity via the data request execution resource. This is done as follows.

Since this activity needs a resource template for the relational resource you also need to ensure that this has been created. The easiest way to do this is to take a copy of our one:

Write an OGSA-DAI configuration file, config.txt:

# Deploy activity.
Activity add uk.org.ogsadai.tutorials.ExtendedCreateRelationalResource uk.org.ogsadai.tutorials.activity.ExtendedCreateRelationalResourceActivity "A relational resource creation activity"
# Expose activity via DRER.
Resource addActivity DataRequestExecutionResource uk.org.ogsadai.tutorials.ExtendedCreateRelationalResource uk.org.ogsadai.tutorials.ExtendedCreateRelationalResource
# Deploy template.
Resource deployTemplate MY_EXT_CREATE_TEMPLATE myTemplate.txt
# Add a default database login for the template.
Login permit MyTemplate ANY USER PASSWORD
# Configure activity to use template.
Activity addConfig uk.org.ogsadai.tutorials.ExtendedCreateRelationalResource dai.template.id MY_EXT_CREATE_TEMPLATE

Remember to replace MY_EXT_CREATE_TEMPLATE with the ID of your resource template, and USER and PASSWORD with the database usernames and passwords.

Run:

$ ant -Dtomcat.dir=$CATALINA_HOME -Dconfig.file=config.txt -Djar.dir=tmp

It must also be borne in mind that any driver class name that clients provide must be available to the server (i.e. have been deployed by the OGSA-DAI deployer) else attempts to use their newly-created resources will fail.

You will now need to restart your container.

103.3. How to write the extended relational resource factory client proxy

Our client toolkit activity for ExtendedCreateRelationalResource is very simple since we have just two optional inputs and one output.

The full source code for a proxy almost identical to what we need can be seen in an OGSA-DAI source distribution in the directory:

source/client/uk/org/ogsadai/client/toolkit/activities/management/ExtendedCreateRelationalResource.java

Note that this has a different package name to the one in this tutorial so if cutting and pasting please use the tutorial package name of:

uk.org.ogsadai.tutorials.activity.client

And change the line:

    private final static ActivityName DEFAULT_ACTIVITY_NAME = 
        new ActivityName("uk.org.ogsadai.ExtendedCreateRelationalResource");

To reflect the name by which we deployed the activity earlier i.e.:

    private final static ActivityName DEFAULT_ACTIVITY_NAME = 
        new ActivityName("uk.org.ogsadai.tutorials.ExtendedCreateRelationalResource");

103.4. How to write an extended relational resource factory client

An example client can be seen in:

$OGSADAI_HOME/examples/uk/org/ogsadai/client/toolkit/example/ExtendedCreateRelationalResourceClient.java

This is suitable for use with our implementations of the CreateRelationalResource activity. If you wish to use it for your own then you need to just change the line:

import uk.org.ogsadai.client.toolkit.activities.management.CreateRelationalResource;

to be:

import uk.org.ogsadai.tutorials.activity.client.CreateRelationalResource;

The client takes the following arguments:

  • -u SERVICES_BASE_URL - services base URL - this specifies the location of the OGSA-DAI server. This will typically be of form: where HOST is the host on which the OGSA-DAI is running and PORT is the port on which the container accepts connections.
  • -t TEMPLATE-ID - ID of resource template to use e.g. -t JDBC_RESOURCE.
  • -d RESOURCE-ID - ID of the new resource e.g. -d myResource.
  • -driver DRIVER-CLASS - database driver class name eg. -driver org.gjt.mm.mysql.Driver.
  • -db DATABASE-URL - database URL e.g. -db jdbc:mysql://myhost.epcc.ed.ac.uk:3306/daitest.
  • -user DATABASE-USER - database user name e.g. -user ogsadai.
  • -pass DATABASE-PASSWORD - database password e.g. -pass ogsadai.

To create a new relational resource using the default template ID run:

$ java uk.org.ogsadai.client.toolkit.example.ExtendedCreateRelationalResourceClient \
  -u SERVICES-BASE-URL

For example:

The ID of the new resource will then be printed.

To create a new relational resource using a specific template ID run:

$ java uk.org.ogsadai.client.toolkit.example.ExtendedCreateRelationalResourceClient \
  -u SERVICES-BASE-URL \
  -t TEMPLATE-ID

For example:

The ID of the new resource will then be printed.

To create a new relational resource using the default template ID and with a new ID selected by yourself run:

$ java uk.org.ogsadai.client.toolkit.example.ExtendedCreateRelationalResourceClient \
  -u SERVICES-BASE-URL \
  -d RESOURCE-ID

For example:

The ID of the new resource (equal to the ID you provide) will then be printed.

To create a new relational resource using a specific template ID and with a new ID selected by yourself run:

$ java uk.org.ogsadai.client.toolkit.example.ExtendedCreateRelationalResourceClient \
  -u SERVICES-BASE-URL \
  -t TEMPLATE-ID       \
  -d RESOURCE-ID

For example:

The ID of the new resource (equal to the ID you provide) will then be printed.

To create a new relational resource using the default template ID and with a new ID, and which uses a specific database driver to connect to a specific URL using a specific username and passowrd, all selected by you, run:

$ java uk.org.ogsadai.client.toolkit.example.ExtendedCreateRelationalResourceClient \
  -u SERVICES-BASE-URL    \
  -d RESOURCE-ID          \
  -driver DRIVER-CLASS    \
  -db DATABASE-URL        \
  -user DATABASE-USERNAME \
  -pass DATABASE-PASSWORD

For example:

The ID of the new resource (equal to the ID you provide) will then be printed.

[Warning]Warning
The connection URL, username and password will be sent from client to server unencrypted unless you extend the client to support secure communications with the OGSA-DAI server and the server deployer has likewise setup a secure server.