Chapter 30. How to write a data resource

30.1. Why write OGSA-DAI data resources?
30.2. Data resource implementation requirements
30.3. What happens when a data resource is created?
30.4. Data resource configuration
30.5. Data resource configuration and using a wrapper
30.6. Data resource functionality
30.6.1. Getting the resource ID
30.6.2. Destroying the resource
30.6.3. Creating resource accessors
30.7. Resource properties
30.7.1. Persisted resource properties
30.7.2. On-demand resource properties
30.8. Writing data resources summary
30.9. Using data resources

30.1. Why write OGSA-DAI data resources?

OGSA-DAI supports the use of application-specific data resources. This requires development of a data resource implementation class to manage access to and use of the data resource. Such an implementation may be required if, for example:

  • You want to use a data resource that is not supported by the default OGSA-DAI data resource implementations
  • The default OGSA-DAI data resource implementations don't behave in a way (either in terms of functionality or efficiency) that is ideal for your requirements and applications.
[Note]Note
When we say data resource we mean an OGSA-DAI data resource - the OGSA-DAI abstraction that interacts with an external database or data resource.

30.2. Data resource implementation requirements

The only requirements that a data resource implementation must satisfy are as follows:

  • The class must not be abstract.
  • The class must provide a zero argument constructor.
  • The class must implement the interface:
    uk.org.ogsadai.resource.dataresource.DataResource
    
    This interface provides just two methods:
    • public void initialize(ResourceState resourceState) - this method allows the OGSA-DAI persistence and configuration components to configure the data resource class with it's configuration. It can be assumed that the argument is downcastable to uk.org.ogsadai.resource.dataresource.DataResourceState.
    • public ResourceState getState() - this method allows the OGSA-DAI persistence and configuration components access the data resource's current state and configuration.

30.3. What happens when a data resource is created?

When OGSA-DAI creates and configures a resource the following process happens:

  1. The persistence and configuration components read the data resource configuration from the persistence media and populate a uk.org.ogsadai.resource.dataresource.DataResourceState object. This object includes the name of the data resource implementation class as well as an indexed collection of configuration properties specific to this implementation class.
  2. The OGSA-DAI server resource manager gets the implementation class from the DataResourceState object and creates an instance of this class.
  3. The OGSA-DAI server resource manager then invokes the initialize method and provides the data resource implementation class with the DataResourceState object.
  4. The data resource can then extract configuration properties from the DataResourceState via use of the DataResourceState.getConfiguration() method.
[Important]Important
On data resource creation the associated DataResourceState object is registered with OGSA-DAI persistence and configuration components. Any changes to this object are persisted but only changes to this object are persisted. So if configuration of a data resource is to be persisted it should be reflected in the associated. DataResourceState.

30.4. Data resource configuration

A data resource implementation class gets configuration properties from its DataResourceState via use of the DataResourceState.getConfiguration() method.

For example OGSA-DAI JDBC data resources expect the following configuration properties:

dai.driver.class
dai.data.resource.uri
dai.login.provider

The data resource implementation class can then access these values as follows:

import uk.org.ogsadai.config.Key;
import uk.org.ogsadai.config.KeyValueProperties;

public static Key DRIVER_CLASS = new Key("dai.driver.class");
public static Key DATABASE_URL = new Key("dai.data.resource.uri");
public static Key DATABASE_LOGIN_PROVIDER = new Key("dai.login.provider");

KeyValueProperties config = dataResourceState.getConfiguration();
String driverClass = config.get(DRIVER_CLASS);
String databaseURL = config.get(DATABASE_URL);
String loginProviderName = config.get(DATABASE_LOGIN_PROVIDER);

Since these values in the configuration are all Strings a data resource implementation can then cast these or parse these into objects usable by the data resource, e.g. into Numbers, Booleans or IDs. For example for the login provider the login provider name could be converted into a ID and then the login provider with that ID accessed from the OGSA-DAI context:

import uk.org.ogsadai.common.ID;
import uk.org.ogsadai.context.OGSADAIContext;
import uk.org.ogsadai.authorization.LoginProvider;

ID loginProviderID = new ID(loginProviderName);
LoginProvider provider = 
    (LoginProvider)(OGSADAIContext.getInstance().get(loginProviderID));

It can be useful for OGSA-DAI administrators if exceptions are thrown for cases where an expected configuration value is missing or does not parse to the required object, e.g.

import uk.org.ogsadai.common.MalformedIDException;
import uk.org.ogsadai.config.ConfigurationValueIllegalException;
import uk.org.ogsadai.config.ConfigurationValueMissingException;
import uk.org.ogsadai.config.KeyValueUnknownException;
import uk.org.ogsadai.context.ContextValueUnknownException;
import uk.org.ogsadai.exception.DAIClassMissingInterfaceException;

ID loginProviderID = null;
try 
{
    String loginProviderName = config.get(DATABASE_LOGIN_PROVIDER);
    loginProviderID = new ID(loginProviderName);
}
catch (KeyValueUnknownException e)
{
    // The configuration value expected by the data resource is missing.
    throw new ConfigurationValueMissingException(DATABASE_LOGIN_PROVIDER);
}
catch (MalformedIDException e)
{
    // The name of the configuration value is improperly formatted.
    throw new ConfigurationValueIllegalException(DATABASE_LOGIN_PROVIDER, e);
}
LoginProvider provider = null;
try
{
    provider = (LoginProvider)(OGSADAIContext.getInstance().get(loginProviderID));
}
catch (ContextValueUnknownException e)
{
    throw new ConfigurationValueMissingException(loginProviderID);
}
catch (ClassCastException e)
{
    // The configuration value is invalid since the thing it
    // identifies is of the wrong class.
    throw new ConfigurationValueIllegalException(loginProviderID,
        new DAIClassMissingInterfaceException(provider.getClass().getName(), 
                                              LoginProvider.class.getName()));
}

30.5. Data resource configuration and using a wrapper

Instead of a data resource managing the access and parsing of its configuration properties itself it can be useful to define a data resource-specific wrapper for DataResourceState that handles the setting and getting of such properties.

For example, for JDBC data resources we have the uk.org.ogsadai.resource.dataresource.jdbc.JDBCDataResourceState interface and its implementation class uk.org.ogsadai.resource.dataresource.jdbc.SimpleJDBCDataResourceState .

These provide the following methods:

// Return the DataResourceState that this class wraps.
public DataResourceState getDataResourceState();

// Return the JDBC driver class name.
public String getDriverClass();

// Set the JDBC driver class name. 
public void setDriverClass(String driverClass);

// Return the URL to connect to the database.
public String getDatabaseURL();

// Set the URL to connect to the database.
public void setDatabaseURL(String databaseURL);

// Return the login provider for this JDBC data resource.
public LoginProvider getLoginProvider();
[Important]Important
If using such wrapper classes ensure that the class does indeed wrap DataResourceState but does not sub-class DataResourceState. This is because the persistence and core have no knowledge of (nor should they have any knowledge of) specific types of data resource.

The data resource class can then wrap this DataResourceState when the initialize() method is invoked. For example, in uk.org.ogsadai.resource.dataresource.jdbc.JDBCDataResource:

public void initialize(ResourceState resourceState) 
{
    mConfiguration = new SimpleJDBCDataResourceState((DataResourceState)resourceState);
}

As implied above JDBCDataResource does not even maintain a direct reference to DataResourceState but only the wrapper. This means that its other required DataResource method can be implemented as follows:

public ResourceState getState() 
{
    return mConfiguration.getDataResourceState();
}

If using wrappers it can be useful if your data resource implementation class provides access to this wrapper, e.g. JDBCDataResource provides:

// Get JDBC data resource state convenience wrapper.
public JDBCDataResourceState getJDBCDataResourceState() 
{
    return mConfiguration;
}

One can go further and provide another initialize method that takes one of these wrappers. This can be useful for testing the data resource. For example:

public void initialize(JDBCDataResourceState jdbcDataResourceState)
{
    mConfiguration = jdbcDataResourceState;
}

If using wrappers in this way the data resource class specified in the DataResourceState may get out of synch with the actual data resource implementation class if used in bizarre application-specific ways. To avoid this the data resource can provide something like the following:

private void updateDataResourceClass(JDBCDataResourceState stateWrapper)
{
    DataResourceState state = stateWrapper.getDataResourceState()   
    if (state.getDataResourceClass() == null)
    {
        state.setDataResourceClass(JDBCDataResource.class.getName());
    }
}

This method can then be invoked within both initialize(ResourceState) and initialize(DataResourceState).

30.6. Data resource functionality

Data resources need to support three distinct functions - reflected in the methods of the uk.org.ogsadai.resource.Resource interface that is the super-class of DataResource.

30.6.1. Getting the resource ID

A data resource implementation class needs to provide a getResourceID which just returns the resource ID. The resource ID is held in the DataResourceState so the resource class can just get this and return it. For example, JDBCDataResource does the following:

import uk.org.ogsadai.resource.ResourceID;

public ResourceID getResourceID()
{
    return mConfiguration.getDataResourceState().getResourceID();
}

where mConfiguration is its JDBCDataResourceState wrapper for DataResourceState.

30.6.2. Destroying the resource

A data resource implementation class needs to provide a destroy that terminates the resource. This is done by just setting the resource termination time to be the current time. Again this can be done using the DataResourceState. For example, for JDBCDataResource:

import java.util.Calendar;
import uk.org.ogsadai.resource.ResourceLifetime;

public void destroy()
{
    ResourceLifetime resourceLifetime =  
        mConfiguration.getDataResourceState().getResourceLifetime();
    resourceLifetime.setTerminationTime(Calendar.getInstance());
}

Of course, if you want your resource never to be terminated this way then this could be implemented via a no-op.

30.6.3. Creating resource accessors

A data resource implementation class needs to provide a createResourceAccessor method. This is the most complex method the class needs to provide. This must provide a class that implements the uk.org.ogsadai.resource.ResourceAccessor interface.

When an activity is targeted at a resource the OGSA-DAI activity framework goes to the resource and invokes this method and passes the ResourceAccessor to the activity. The activity then downcasts the ResourceAccessor to see if the resource is of the type expected by the activity. For example, ResourceAccessor sub-interfaces provided by OGSA-DAI include:

uk.org.ogsadai.resource.dataresource.file.FileAccessProvider
uk.org.ogsadai.resource.dataresource.group.ResourceGroupProvider
uk.org.ogsadai.resource.dataresource.jdbc.JDBCConnectionProvider
uk.org.ogsadai.resource.dataresource.xmldb.XMLDBCollectionProvider

The activity then interacts with the ResourceAccessor (see Section 24.2.5, “Interacting with resources” for an activity-centric description of how activities are configured with these).

Why this approach is chosen rather than interacting with the resource directly is hinted by the uk.org.ogsadai.authorization.SecurityContext argument to the method. A ResourceAccessor enables the activity to interact with the resource in the presence of a specific security context and ensure that the resource is accessed correctly given the nature of the client (for more on security contexts see Section 31.1, “Security contexts”).

So this means you need to provide a sub-interface of ResourceAccessor plus an implementation class and return this from the createResourceAccessor method.

For example for JDBCDataResource we have the interface and class:

uk.org.ogsadai.resource.dataresource.jdbc.JDBCConnectionProvider
uk.org.ogsadai.resource.dataresource.jdbc.SimpleJDBCConnectionProvider

So for our JDBCDataResource we implement this as follows

public ResourceAccessor createResourceAccessor(
       final SecurityContext securityContext) 
{
    return new SimpleJDBCConnectionProvider(this, securityContext);
}

JDBCConnectionProvider provides the methods which activities can use:

import java.sql.Connection;
    
// Problem when accessing or using connection.
import uk.org.ogsadai.resource.dataresource.jdbc.JDBCConnectionUseException;
    
// Get a JDBC connection.
public Connection getConnection() throws JDBCConnectionUseException;
    
// Release a previously obtained JDBC connection.
public void releaseConnection(Connection connection) throws JDBCConnectionUseException;

These provide the JDBC connection and release it. The implementation class determines how a security context is used to determine the connection provided.

For example SimpleJDBCConnectionProvider implements this via the wrapping of and callbacks to the parent JDBDataResource. For example:

private JDBCDataResource mResource;
private SecurityContext mSecurityContext;

// JDBCConnectionProvider method.
public Connection getConnection() throws JDBCConnectionUseException
{
    return mResource.getConnection(mSecurityContext);
}  
// JDBCConnectionProvider method.
public void releaseConnection(final Connection connection) throws JDBCConnectionUseException
{
     mResource.releaseConnection(connection);
}

The JDBDataResource methods called by SimpleJDBCConnectionProvider are as follows:


JDBCDataResourceState mConfiguration;

public Connection getConnection(final SecurityContext securityContext)
       throws JDBCConnectionUseException
{
    Connection connection = null;
    final String driver = mConfiguration.getDriverClass();
    final String url = mConfiguration.getDatabaseURL();
    // Using the SecurityContext and ResourceID and a LoginProvider
    // get a database login.
    Login login;
    try
    {
        LoginProvider loginProvider = mConfiguration.getLoginProvider();
        login = (Login)loginProvider.getLogin(getResourceID(),
                                              securityContext);
    }
    catch (LoginDeniedException e)
    {
        throw new JDBCConnectionUseException(url, e);
    }
    catch (LoginProviderException e)
    {
        throw new JDBCConnectionUseException(url, e);
    }
    catch (ResourceUnknownException e)
    {
        throw new JDBCConnectionUseException(url, e);
    }
    // Try and connect via JDBC to the database.
    final String username = login.getUserID();
    final String password = login.getPassword();
    try
    {
        Class.forName(driver);
        connection = DriverManager.getConnection(url, username, password);
    }
    catch (SQLException e)  
    {
        final JDBCConnectionAccessException cause = 
            new JDBCConnectionAccessException(url, username, e);
        throw new JDBCConnectionUseException(url, cause);
    } 
    catch (ClassNotFoundException e)
    {
        final DAIClassNotFoundException cause =
            new DAIClassNotFoundException(driver);
        throw new JDBCConnectionUseException(url, cause);
    }
    return connection;
}

public void releaseConnection(Connection connection)
       throws JDBCConnectionUseException
{
    try 
    {
        connection.close();
    } 
    catch (SQLException e) 
    {
        Exception cause = new JDBCCloseConnectionException(e);
        throw new JDBCConnectionUseException(
            mConfiguration.getDatabaseURL(), cause);
    }
}

Note how when creating the connection the security context is used in conjunction with a login provider (specified as part of the data resource configuration) to provide a database username and password. Login providers are described in Section 32.1, “What are login providers”. You do not have to use a login provider to provide database usernames and passwords - this is implementation specific.

In this example it is JDBCDataResource that manages the connection to the database. An alternative would have been to have SimpleJDBCConnectionProvider wrap a JDBCDataResourceState instead and manage the connections itself. This is an implementation detail however.

30.7. Resource properties

Resource properties are units of information of a resource that clients can access. Using this clients can query the state and capabilities of a resource. There are two types of resource property:

  • Persisted resource properties.
  • On-demand resource properties.

30.7.1. Persisted resource properties

Persisted resource properties are properties that are persisted if the resource is persisted. Any resource property specified in the persisted information and configuration about a resource (e.g. in a resource file - see Section 17.2.4, “Resource Files”) is added to the resource property set of the resource when the resource is created.

Any persisted resource property added to a resource's resource property set at runtime is automatically persisted (depending upon whether the resource state of the resource as a whole is to be persisted).

Persisted resource properties implement the interface:

uk.org.ogsadai.resource.PersistedResourceProperty

An example of creating a persisted resource property and adding it to the resource property set of a resource is as follows:

import uk.org.ogsadai.resource.PersistedResourceProperty;
import uk.org.ogsadai.resource.ResourcePropertyName;
import uk.org.ogsadai.resource.ResourcePropertyValue;
import uk.org.ogsadai.resource.SimplePersistedResourceProperty;
import uk.org.ogsadai.resource.SimpleResourcePropertyValue;

DataResourceState state;
String someValue = "This is some value!";

// Create resource property name.
ResourcePropertyName propertyName = new ResourcePropertyName("MyPropertyName");
    
// Create persisted resource property.
PersistedResourceProperty property = new SimplePersistedResourceProperty();
property.setPropertyName(propertyName);
    
// Create resource property value wrapper.
ResourcePropertyValue valueWrapper = new SimpleResourcePropertyValue(someValue);
    
// Assign wrapped value to property.
property.setPropertyValue(valueWrapper);
// Add property to resource property set.
state.getResourcePropertySet().addProperty(property);

We use a ResourcePropertyValue wrapper since for WSRF-based presentation layers we have to convert properties to XML - ResourcePropertyValue is an interface that supports a method provides getAsDOM which does this conversion.

We provide an implementation of ResourcePropertyValue - SimpleResourcePropertyValue - which will handle the conversion to XML of the following types:

java.lang.Number
java.lang.Boolean
java.lang.String
java.util.Date
java.util.Calendar
org.w3c.dom.Node
uk.org.ogsadai.common.ID (and all sub-classes)

as well as java.util.Lists and java.util.Collectionss of these.

You can also provide your own implementation of ResourcePropertyValue, if required.

[Caution]Caution
OGSA-DAI's file based persistence components only support the persistence of String resource property values at present.

30.7.2. On-demand resource properties

On-demand resource properties are those that are not persisted. Rather their value is generated when it is requested. Examples of on-demand resource properties may include the status of some connection at any point in time or a database schema, i.e. values that are not held in the OGSA-DAI data resource but elsewhere.

On-demand resource properties implement the interface:

uk.org.ogsadai.resource.OnDemandResourceProperty

An example of creating an on-demand resource property and adding it to the resource property set of a resource is as follows:

import uk.org.ogsadai.resource.OnDemandResourceProperty;
import uk.org.ogsadai.resource.OnDemandResourcePropertyCallback;
import uk.org.ogsadai.resource.SimpleOnDemandResourceProperty;

DataResourceState state;
// Callback for the resource property value.
OnDemandResourcePropertyCallback myCallback;

// Create resource property name.
ResourcePropertyName propertyName = new ResourcePropertyName(MyPropertyName");
    
// Create on-demand resource property.
OnDemandResourceProperty property = 
    new SimpleOnDemandResourceProperty(propertyName, myCallback);
    
// Add property to resource property set.
state.getResourcePropertySet().addProperty(property);

The on-demand resource property is given a reference to a callback class. This is the class that will actually provide the value of the resource property. When the on-demand resource property is asked for its value it forwards the request to the callback class. On-demand resource property callback classes implement the interface:

uk.org.ogsadai.resource.OnDemandResourcePropertyCallback

A simple example of a callback class is as follows. This callbacks to a data resource implementation class:

public class MyCallback implements OnDemandResourcePropertyCallback
{
    private MyDataResource mMyDataResource;

    public MyCallback(MyDataResourceState myDataResource)
    {
	mMyDataResource = myDataResource;
    }
    // Method of OnDemandResourcePropertyCallback interface.
    public ResourcePropertyValue 
        getResourcePropertyValue(ResourcePropertyName name)
    {
        return new SimpleResourcePropertyValue(mMyDataResource.getSomeValue());
    }
    // Method of OnDemandResourcePropertyCallback interface.
    public void setResourcePropertyValue(ResourcePropertyName name, 
                                         Object value)
    {
        mMyDataResource.setSomeValue(value);
    }
}

Note how the callback returns the value wrapped in a ResourcePropertyValue. Again you can use the OGSA-DAI SimpleResourcePropertyValue implementation or an application-specific implementation if desired.

A more complex example is below where the callback class also provides a custom implementation of ResourcePropertyValue:

public class MyCallback implements OnDemandResourcePropertyCallback,
                                   ResourcePropertyValue
{
    private MyDataResource mMyDataResource;

    public MyCallback(MyDataResourceState myDataResource)
    {
	mMyDataResource = myDataResource;
    }
    // Method of OnDemandResourcePropertyCallback interface.
    public ResourcePropertyValue 
        getResourcePropertyValue(ResourcePropertyName name)
    {
        return this;
    }
    // Method of OnDemandResourcePropertyCallback interface.
    public void setResourcePropertyValue(ResourcePropertyName name, 
                                         Object value)
    {
        mMyDataResource.setSomeValue(value);
    }
    // Method of ResourcePropertyValue.
    public Node[] getAsDOM()
    {
        Node[] nodes = null;
        // nodes = Convert myDataResource.getSomeValue().
        return nodes;
    }
    // Method of ResourcePropertyValue.
    public Object getValue()
    {
        return myResource.getSomeValue();
    }
}

30.8. Writing data resources summary

To write an OGSA-DAI data resource you need to provide an implementation of the interface:

uk.org.ogsadai.resource.dataresource.DataResource

You will need to make the following decisions:

  • What the configuration properties required by the data resource are.
  • Whether to use a DataResourceState wrapper to manage accessing, parsing, validating and updating of configuration properties.
  • What persisted resource properties your resource will support. If these are required as part of the resource configuration you need to document these so that OGSA-DAI administrators deploying your resource provide these in the configuration for your resource.
  • What on-demand resource properties your resource will support and what callback classes you will need.
  • Whether you need an application-specific ResourcePropertyValue implementation to parse resource property values into XML.
  • What operations your ResourceAccessor sub-interface will need.
  • How mapping of security contexts. database usernames and passwords and the like are handled by the class that implements your ResourceAccessor sub-interface.

You will need to:

  • Write a DataResource implementation class.
  • Possibly write a DataResourceState wrapper.
  • Write a ResourceAccessor sub-interface.
  • Write an implementation of this sub-interface.
  • Possibly write OnDemandResourcePropertyCallback implementations.
  • Possibly write a ResourcePropertyValue implementation.

30.9. Using data resources

Configuring OGSA-DAI to use your data resource implementation is the same as for OGSA-DAI's own data resource implementations. Data resource deployment is described in Section 16.1.2, “Deploying a resource”.