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:
![]() | 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. |
The only requirements that a data resource implementation must satisfy are as follows:
uk.org.ogsadai.resource.dataresource.DataResourceThis 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.
When OGSA-DAI creates and configures a resource the following process happens:
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.
DataResourceState object and
creates an instance of this class.
initialize method and provides the
data resource implementation class with the
DataResourceState object.
DataResourceState via use of the
DataResourceState.getConfiguration()
method.
![]() | 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.
|
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()));
}
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 |
|---|---|
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).
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.
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.
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.
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.
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 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 |
|---|---|
OGSA-DAI's file based persistence components only support the
persistence of String resource
property values at present.
|
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();
}
}
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:
DataResourceState
wrapper to manage accessing, parsing, validating and updating of
configuration properties.
ResourcePropertyValue implementation
to parse resource property values into XML.
ResourceAccessor
sub-interface will need.
ResourceAccessor sub-interface.
You will need to:
DataResource implementation class.
DataResourceState
wrapper.
ResourceAccessor sub-interface.
OnDemandResourcePropertyCallback
implementations.
ResourcePropertyValue implementation.
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”.