Workflow Plugin Guide

Overview

Kuali Enterprise Workflow (KEW) has a plugin framework that allows you to load code into the core system without requiring changes to the KEW source code or configuration. This framework provides:

  • A custom class loading space

  • Hot deploy and reload capabilities

  • Participation in Workflow's JTA transactions

  • An application plugin for installation of routing components

Application Plugin

Use an application plugin to deploy an application area's routing components into Workflow. These routing components might include:

  • Rule attributes

  • Searchable attributes

  • Post processors

  • Route modules

If these components require access to a data source, then the application plugin also configures the data source and allows it to participate in Workflow's JTA transactions.

In addition to routing components, the application plugin can also configure a plugin listener and a Resource Loader. The Resource Loader is responsible for loading resources (both Java classes and service implementations) from the plugin and providing them to the core system.

Application plugins are hot-deployable, so a restart of the server is not required when they are added or modified. The core system searches for plugins in a directory configured in the application configuration (see KEW Module Configuration).

Plugin Layout

You build the plugin as a set of files and directories. You then zip this structure and place it in the appropriate Workflow plugin space. For application plugins, this directory location is defined in the core system configuration.

The name of the zip file (minus the .zip extension) is used as the name of the plugin. The Plugin Loader only looks for files that end in .zip when determining whether to load and hot-deploy a plugin.

In general, application plugins can be named as desired. However, there is one reserved plugin name:

shared - A special plugin that provides a shared classloading space to all plugins (see Plugin Shared Space).

The directory structure of a plugin is similar to that of a web application. It should have this structure:

classes/

lib/
META-INF/
    workflow.xml

  • classes - All java .class files that are used by the plugin should reside in this directory

  • lib - All .jar library files that are used by the plugin should reside in this directory

  • META-INF - The workflow.xml configuration file must reside in this directory

Plugin Configuration

Application plugins usually provide a subset of the functionality that an institutional plugin provides, since the institutional plugin can provide core service overrides.

The plugin framework provides two configuration points:

  1. Plugin XML Configuration (described below)

  2. Transaction and DataSource Configuration

Plugin XML Configuration

The XML configuration is defined in a file called workflow.xml. The format of this file is relatively simple. An example workflow.xml file:

<plug-in>
    <param name="my.param.1">abc</param>
    <param name="my.param.2">123</param>
    <listener>
        <listener-class>org.kuali.rice.core.ApplicationInitializeListener</listener-class>
    </listener>
    <resourceLoader class="my.ResourceLoader"/>
</plug-in>

We'll explain each of these elements in more detail below:

Plugin Parameters

The parameter configuration uses XML as the syntax. These parameters are placed into a configuration context for the plugin. The configuration inherits (and can override) values from the parent configurations. The configuration hierarchy is core -> institutional plugin -> application plugins.

A plugin can access its configuration using this code:

org.kuali.rice.Config config = org.kuali.rice.Core.getCurrentContextConfig();
Plugin Listeners

You can define one or more listeners that implement the interface org.kuali.rice.kew.plugin.PluginListener. These can be used to receive plugin lifecycle notifications from KEW.

The interface defines two methods to implement:

  • Invoked when a plugin starts up

    public void pluginInitialized(Plugin plugin);
  • Invoked when a plugin shuts down

    public void pluginDestroyed(Plugin plugin);

It is legal to define more than one plugin listener. Plugin listeners are started in the order in which they appear in the configuration file (and stopped in reverse order).

Resource Loader

A plugin can define an instance of org.kuali.rice.resourceloader.ResourceLoader to handle the loading of classes and services. When KEW attempts to load classes or locate services, it searches the institutional plugin, then the core, then any application plugins. It does this by invoking the getObject(..) and getService(...) methods on the plugin's ResourceLoader.

If no ResourceLoader is defined in the plugin configuration, then the default implementation org.kuali.rice.resourceloader.BaseResourceLoader is used. The BaseResourceLoader lets you examine the plugin's classloader for objects when requested (such as post processors, attributes, etc.). This is sufficient for most application plugins.

For more information on configuring service overrides in a plugin, see the Overriding Services with a ResourceLoader section below.

Configuring an Extra Classpath

Sometimes it is desirable to be able to point in a plugin to classes or library directories outside of the plugin space. This can be particularly useful in development environments, where the plugin uses many of the same classes as the main application that is integrating with Workflow. In these scenarios, configuring an extra Classpath may mean you don’t need to jar or copy many common class files.

To do this, specify these properties in your plugin's workflow.xml file:

  1. extra.classes.dir - Path to an additional directory of .class files or resources to include in the plugin's classloader

  2. extra.lib.dir - Path to an additional directory of .jar files to include in the plugin's classloader

The classloader then includes these classes and/or lib directories into its classloading space, in the same manner that it includes the standard classes and lib directories. The classloader always looks in the default locations first, and then defers to the extra classpath if it cannot locate the class or resource.

Transaction and DataSource Configuration

The easiest method to configure Datasources and Transactions is through the Spring Framework. Here is a snippet of Spring XML that shows how to wire up a Spring Transaction Manager inside of a plugin:

<bean id="userTransaction" class="org.kuali.rice.jta.UserTransactionFactoryBean" />

<bean id="jtaTransactionManager" class="org.kuali.rice.jta.TransactionManagerFactoryBean" />

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction" ref="userTransaction" />
    <property name="transactionManager" ref="jtaTransactionManager" />
    <property name="defaultTimeout" value="${transaction.timeout}"/>
</bean>

The factory beans in the above XML will locate the javax.trasaction.UserTransaction and java.transaction.TransactionManager, which are configured in the core system. These can then be referenced and injected into other beans (such as the Spring JtaTransactionManager).

Once you configure the transaction manager, you also need to configure any DataSources you require. Here's an example of configuring a DataSource that participates in Atomikos JTA transactions (the default Transaction Manager distributed with Rice Standalone).

<bean id="myDataSource" class="com.atomikos.jdbc.nonxa.NonXADataSourceBean">
    <property name="uniqueResourceName" value="myDataSource"/>
    <property name="driverClassName" value="..."/>
    <property name="url" value="..."/>  
    ...
</bean>

So, the application can access it's datasource by either injecting it into Spring services or by fetching it directly from the Spring context.

You can find more information on configuring Rice DataSources and TransactionManagers in Datasource and JTA Configuration.

OJB Configuration within a Plugin

If your plugin needs to use OJB, there are a few other configuration steps that you need to take. First, in your Spring file, add the following line to allow Spring to locate OJB and the JTA Transaction Manager:

<bean id="ojbConfigurer" class="org.kuali.rice.ojb.JtaOjbConfigurer">
    <property name="transactionManager" ref="jtaTransactionManager" />

</bean>

Next, for OJB to plug into Workflow's JTA transactions, you need to modify some settings in the plugin's OJB.properties file (or the equivalent):

PersistenceBrokerFactoryClass=org.apache.ojb.broker.core.PersistenceBrokerFactorySyncImpl
ImplementationClass=org.apache.ojb.odmg.ImplementationJTAImpl
OJBTxManagerClass=org.apache.ojb.odmg.JTATxManager
ConnectionFactoryClass=org.kuali.rice.ojb.RiceDataSourceConnectionFactory
JTATransactionManagerClass=org.kuali.rice.ojb.TransactionManagerFactory

The first three properties listed are part of the standard setup for using JTA with OJB. However, there are custom Rice implementations:

  • org.kuali.rice.ojb.RiceDataSourceConnectionFactory

  • org.kuali.rice.ojb.TransactionManagerFactory

  • org.kuali.rice.ojb.RiceDataSourceConnectionFactory

This OJB ConnectionFactory searches your Spring Context for a bean with the same name as your jcd-alias. Here is what an OJB connection descriptor might look like inside of a Workflow plugin:

<jdbc-connection-descriptor
    jcd-alias="myDataSource"
    default-connection="true"
    platform="Oracle9i"
    jdbc-level="3.0"
    eager-release="false"
    batch-mode="false"
    useAutoCommit="0"
    ignoreAutoCommitExceptions="false">
    
    <sequence-manager className="org.apache.ojb.broker.util.sequence.SequenceManagerNextValImpl" />
    <object-cache class="org.apache.ojb.broker.cache.ObjectCachePerBrokerImpl"/>
</jdbc-connection-descriptor>

Notice that the jcd-alias attribute matches the name of the DataSource Spring bean defined in the example above.

Another important thing to notice in this configuration is that useAutoCommit is set to 0. This tells OJB not to change the auto commit status of the connection because it is being managed by JTA.

Finally, when your plugin needs to use OJB, you need to use this:

org.kuali.rice.ojb.TransactionManagerFactory

This provides OJB with the javax.transaction.TransactionManager that was injected into your JtaOjbConfigurer, as in the example above.

Overriding Services with a ResourceLoader

For a service override, you need to define a custom ResourceLoader implementation and configure it in your workflow.xml plugin configuration file. The org.kuali.rice.resourceloader.ResourceLoader interface defines this relative method:

public Object getService(javax.xml.namespace.QName qname);

When KEW is searching for services, it invokes this method on its plugins' ResourceLoader implementations. The service name is a qualified name (as indicated by the use of javax.xml.namespace.QName), but for services being located from the core, service names typically contain only a local part and no namespace.

The easiest way to implement a custom ResourceLoader is to create a class that extends from org.kuali.rice.resourceloader.BaseResourceLoader and just override the getService(QName) method. The BaseResourceLoader provides standard functionality for loading objects from ClassLoaders, among other things.

For example, if you want to override the User Service, you might implement this ResourceLoader:

public class MyResourceLoader extends BaseResourceLoader {
    public MyResourceLoader() {
        super(new QName("MyResourceLoader"));
	
    }

    @Override
    public Object getService(QName serviceName) {
        if ("enUserOptionsService".equals(serviceName.getLocalPart()) {
            // return your custom implementation of org.kuali.rice.kew.useroptions.UserOptionsService
        } else if (...) {
            ...
        } else if (...) {
            ...
        }
        return super.getService(serviceName);
    }
}

In the next section, we'll look at some of the services commonly overridden in an institutional plugin

Commonly Overridden Services

In theory, you can override any service defined in the org/kuali/workflow/resources/KewSpringBeans.xml file in the Institutional Plugin. What follows is a list of the most commonly overridden services:

Table 3.18. Commonly Overridden Services

Service Name Interface Description
enUserOptionsService org.kuali.rice.kew.useroptions.UserOptionsService Provides User lookup and searching services
IdentityHelperServiceorg.kuali.rice.kew.identity.service.IdentityHelperServiceInterfaces with KIM identity management services
enEmailService org.kuali.rice.kew.mail.service.impl.DefaultEmailServiceProvides email sending capabilities
enNotificationService org.kuali.rice.ken.service.NotificationService Provides callbacks for notifications within the system
enEncryptionService org.kuali.rice.core.service.EncryptionService Allows for pluggable encryption implementations


User Service

The Workflow core uses the UserService to resolve and search for users. The UserService could be as simple as a static set of users or as complex and integrated as a university-wide user system. Your institution may choose how to implement this, as long as you provide capabilities for the ID types that you intend to use. At the very least, implementations are required for the WorkflowUserId and AuthenticationUserId types (and their corresponding VO beans). All of the UserId types must be unique across the entire set of users.

The WorkflowUserId is typically associated with a unique numerical sequence value and the AuthenticationUserId is typically the username or network ID of the user.

The default UserService implementation provides a persistent user store that allows you to create and edit users through the GUI. It also caches users for improved performance and implements an XML import for mass user import. Institutions usually override the default user service with an implementation that integrates with their own user repository.

IdentityHelper Service

The IdentityHelper service helps to interact with the KIM identity management services in the system. IdentityHelpers are identified in one of two ways:

  1. PrincipalId - A numerical identifier for a KIM principal

  2. Group – An object associated with a group of principal users numerical identifier assigned to a Workgroup

Both of these object variables are implemented in KEW in the IdentityHelperServiceImpl file.

Email Service

The Email service is used to send emails from KEW. You can configure the default implementation when you configure KEW (see KEW Configuration). However, if more custom configuration is needed, then you can override the service in the plugin.

For example, you could override this service if you need to make a secure and authorized SSL connection with an SMTP server because of security policies.

Notification Service

The Notification service is responsible for notifying users when they receive Action Items in their Action List.

The default implementation simply sends an email (using the EmailService) to the user according to the individual user’s preferences. A custom implementation might also notify other (external) systems in addition to sending the email.

Encryption Service

The Encryption service is responsible for encrypting document content.

The default implementation uses DES to encrypt the document content. If the encryption.key configuration parameter is set as a Base64 encoded value, then the document content is encrypted using that key. If it is not set, then document content will not be encrypted and will be stored in the database in plain text.

Plugin Shared Space

All plugins also load certain classes from a shared space. The shared space contains certain classes that link with certain libraries that might exist in each application or institutional plugin's classloader (such as OJB and Spring). Current classes that Workflow publishes in the shared space are those in the shared module of the Rice project (rice-shared-version.jar). This is important because some of these classes link with libraries like Spring or OJB and since the plugin needs its own copy of these libraries, it needs to ensure that it doesn't retrieve these classes from any classloader but it's own.