This tutorial follows on from the previous How to Write a Basic Activity tutorial. It describes how to develop and deploy a more advanced type of activity known as a "Configurable Activity". In the previous tutorial, the example of a stringTokenizer activity was used. This activity received all the settings that determined its behaviour from the user via a perform document. For example, a perform document containing the following XML fragment would tokenize its input data using the "," character as a delimiter:
... <stringTokenizer name="tokenizer"> <stringBlocksInput from="myOutput"/> <delimiters value=","/> <stringTokensOutput name="myTokens"/> </stringTokenizer> ...
While the delimiters element is sufficient to provide user control of the simple stringTokenizer activity, there are many cases where an activity requires more complex configuration by the service provider. Consider the case of a distributedJoin activity that uses several remote data service resources to perform queries against different data resources and then joins the results together in some way. Figure 1 shows an example of how this might be realised.
The distributedJoin activity would need to be configured with the information necessary to access the distributed data service resources. This would consist of the URL of the data service exposing the data service resource and the name of the data service resource.
OGSA-DAI provides two mechanisms through which an activity can be configured:
The remainer of the tutorial describes how to use one or both of these mechanisms to create a configurable activity.
Property configuration allows a sequence of one or more property elements to be positioned within any activity element of the activity configuration document. This mechanism is best suited to simple, non-structured configuration information. For example, an sqlQuery activity might use property elements to specify the timeout and maximum number of rows that can be returned from a query, as shown below:
... <activity name="sqlQuery" implementation="my.package.SQLQueryActivity" schemaFileName="path/to/my-sql-query-activity.xsd"> <property key="maxResults" value="100"/> <property key="timeout" value="5000"/> </activity> ...
The activity implementation can then access these properties using the following methods inherited from the base Activity class:
Method | Returns | Description |
---|---|---|
hasProperties() | boolean | Indicates whether or not the activity has any configuration properties. |
getProperties() | java.util.Properties | Accesses the activity configuration properties, if the activity has any. |
The activity implementation is responsible for performing validation to ensure that all the required properties have been specified and their values are suitable. The code fragment below shows how an SQLQueryActivity might perform property validation within the initialise method. An ActivityExecutionException is raised whenever the properties are deemed invalid - the detail of such exceptions are logged server-side but are not communicated to the client (beyond a general message informing them that something went wrong and they should contact the service deployer i.e. you):
import uk.org.ogsadai.exception.DAINumberFormatException; import uk.org.ogsadai.activity.ActivityConfigurationPropertyException; protected void initialise() throws ActivitySpecificationException, ActivityExecutionException { if (!hasProperties() || !getProperties().containsKey("maxResults")) { throw new ActivityExecutionException( getName(), new ActivityConfigurationPropertyException("maxResults")); } if (!getProperties().containsKey("timeout")) { throw new ActivityExecutionException( getName(), new ActivityConfigurationPropertyException("timeout")); } Properties properties = getProperties(); String maxResultsStr = properties.getProperty("maxResults"); try { mMaxResults = Integer.parseInt(maxResultsStr); } catch (NumberFormatException e) { throw new ActivityExecutionException(getName(), new ActivityConfigurationPropertyException("maxResults", new DAINumberFormatException(maxResultsStr))); } String timeoutStr = properties.getProperty("timeout"); try { mTimeout = Integer.parseInt(timeoutStr); } catch (NumberFormatException e) { throw new ActivityExecutionException(getName(), new ActivityConfigurationPropertyException("timeout", new DAINumberFormatException(timeoutStr))); } ...etc... }
Note that mMaxResults and mTimeout are both instance variables used by the activity for storing the property values. These can then be used elsewhere in the activity, for instance during processing. LOG is a static final variable referencing the OGSA-DAI uk.org.ogsadai.exception.DAILogger object for the activity class.
External file configuration facilitates more complex activity configuration using arbitrary XML specified in an external file. This mechanism is suitable for activities that require structured configuration information, rather than a flat list of property key-value pairs.
To use an external configuration file, a config attribute should be added to the desired activity element within the activity configuration document. This attribute should contain either the absolute URL of the external configuration file, or alternatively, the path relative to a base URL specified using the configBase attribute of the surrounding activityMap element. For example:
... <activityMap schemaBase="file:C:/ogsadai/config/"> configBase="file:C:/ogsadai/schema/ogsadai/xsd/activities/"> ... <activity name="distributedJoin" implementation="package.MyDistributedJoin" schema="my-distributed-join-activity.xsd" config="my-distributed-join-config.xml"/> ...
Note that the file URLs used in the above example are specified in a format that Windows accepts. A similar file URL in Unix could be expressed as file:/home/ogsadai/config/.
Returning to the example of a distributedJoin activity, the external configuration file may contain the following XML data:
<configuration> <joinNodes> <node service="http://www.turnip.org/wsrf/services/DataService" resource="MySQLResource" /> <node service="http://www.parsnip.org/wsrf/services/DataService" resource="DB2Resource" /> <node service="http://www.pumpkin.org/wsrf/services/DataService" resource="SQL2000Resource" /> </joinNodes> </configuration>
In order for this information to be accessed within the activity implementation, the developer must implement two interfaces:
Interface | Description |
---|---|
uk.org.ogsadai.activity.ActivityConfiguration | Defines an initialise method that receives a DOM Document containing the XML from the external configuration file. An implementation should validate and encapsulate this document, providing convenient methods for accessing the details that will be used by the Activity implementation. |
uk.org.ogsadai.activity.ActivityConfigurationCreator | Defines a createActivityConfiguration method that creates an instance of an ActivityConfiguration implementation. Any activity making use of an external configuration file must implement this interface. This is an example of the Factory Method design pattern. |
The UML diagram below demonstrates how these interfaces would be used by the developer of the distributedJoin activity:
In summary, the DistributedJoinConfiguration class implements the ActivityConfiguration interface, providing an initialise method to receive the configuration document and a number of convenient methods for exposing the details. The DistributedJoinActivity extends the abstract base Activity class and implements the ActivityConfigurationCreator interface, providing a method that creates an instance of DistributedJoinConfiguration. OGSA-DAI will invoke this method internally and make the ActivityConfiguration object accessible to the activity implementation via the inherited getActivityConfiguration method. A hasActivityConfiguration method is also provided to indicate whether or not a particular activity has been configured using an external configuration file.
The remainder of this tutorial contains some example code for the DistributedJoinActivity and DistributedJoinConfiguration classes. For a more comprehensive example, download the OGSA-DAI source distribution and refer to src/java/activities/uk/org/ogsadai/activity/indexedfiles/AddAndIndexFileActivity.java.
package my.package; import uk.org.ogsadai.activity.Activity; import uk.org.ogsadai.activity.ActivityConfiguration; import uk.org.ogsadai.activity.ActivityConfigurationCreator; ..etc... public class DistributedJoinActivity extends Activity implements ActivityConfigurationCreator { /** Object encapsulating activity configuration. */ private DistributedJoinConfiguration mConfig; ...etc... public ActivityConfiguration createActivityConfiguration() { return new DistributedJoinConfiguration(); } public void initialise() { if (hasActivityConfiguration()) { mConfig = (DistributedJoinConfiguration)getActivityConfiguration(); } } ...etc... }
package my.package; import java.util.ArrayList; import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import uk.org.ogsadai.activity.ActivityConfiguration; import uk.org.ogsadai.activity.ActivityConfigurationException; import uk.org.ogsadai.common.xml.XMLMissingElementException; ...etc... public class DistributedJoinConfiguration implements ActivityConfiguration { private List mServices = new ArrayList(); private List mResources = new ArrayList(); public void initialise(Document document) throws ActivityConfigurationException { Element configuration = document.getDocumentElement(); NodeList nodes = configuration.getElementsByTagName("joinNodes"); if (nodes.getLength()>0) { Element joinNodes = (Element) nodes.item(0); nodes = joinNodes.getElementsByTagName("node"); for (int i = 0; i < nodes.getLength(); i++) { Element node = (Element) nodes.item(i); mServices.add(node.getAttribute("service")); mResources.add(node.getAttribute("resource")); } } else { throw new ActivityConfigurationException( "StringTokenizerActivity", new XMLMissingElementException("distributedJoin.joinNodes")); } } public int numberOfNodes() { return mServices.size(); } ...etc... }
Up: OGSA-DAI User Guide | ||
© International Business Machines Corporation, 2002-2006 | © The University of Edinburgh, 2002-2006 |