Developing Java components for the FileNet P8 Component Integrator

This article shows you how to develop Java® components for the FileNet Component Integrator. The Component Integrator is part of the IBM® FileNet® Process Engine. It enables you to call functions of a custom Java class from a component step within a workflow. The article describes how to obtain sessions, debug your Java code, and build and configure a custom JAAS login module for database connectivity.

Ricardo Belfor (ricardo.belfor@ecmdeveloper.com), Information Architect, Inter Access

Ricardo Belfor photoRicardo Belfor lives and works in the Netherlands. Since 1996, he has worked as an architect and programmer for companies specializing in implementing FileNet systems. He has designed and built FileNet systems for several large organizations in the Netherlands using various FileNet products and programming languages.



22 June 2010

Introduction

The Component Integrator is a part of the FileNet Process Engine. It enables you to call functions of a custom Java class from a component step within a workflow. Although there are some limitations as to which data types you can use as arguments for the functions, this is a powerful way for you to add some extra functionality to a process.

In this type scenario, you package the Java class together with its dependent classes into a jar file and configure it to run in the context of the Component Manager. The Component Manager is responsible for providing a "pleasant" environment for the Java class to run in. As the developer of the Java class, you should know what this environment looks like and how to best configure it. This article is intended to provide you with this knowledge.

First, the article covers the basics on how to obtain sessions for the Process Engine and the Content Engine. Next, it describes how to debug the Java code. After that, you'll learn about configuration options. Finally, the article includes a more advanced discussion on how to build and configure a custom JAAS login module for database connectivity.

The source code

The Download section contains a link to source code that demonstrates the concepts presented in this article. The code includes a custom Java class with two public methods and a JUnit test class that contains code to call the methods. The getFolderDocuments() method demonstrates the use of the Content Engine. The second sendMailMessage() method uses the connection to the Process Engine to fetch the mail session and send a custom message using a Content Engine document as the template. The source code also includes the custom code module described in this article.


Obtaining sessions

You can use custom Java components to perform specific business needs that are not in any way related to the system itself. However, in practice, the Java components often have to interact with either the Content Engine or the Process Engine. This section describes how you can obtain sessions to the different systems.

An explanation of sessions in combination with the Component Integrator usually starts with the fact that the JAAS framework is used for authentication. This means that the infrastructure around the Java component is responsible for the connections to the different resources, and the Java component can concentrate on the specific functionality it is intended to provide. One of the goals of this article is to also explain how this infrastructure provides for the Java component.

When you configure a specific Component Queue using the Process Configuration Console, you must provide a Configuration Context in the JAAS Credentials section of the Adaptor tab. This configuration entry is called a JAAS stanza. The name you provide must correspond to a configuration entry in the taskman.login.config file located in the Component Manager folder on the Application Server.

In practice, you have to look at the different connections that the Java component needs, and configure the configuration entry accordingly. If different Java components require the same connections, then they can reuse an existing configuration context.

A minimal configuration looks like the following:

MyLoginContext 
{ 
	filenet.vw.server.VWLoginModule required; 
};

In the code sample above, MyLoginContext is the name of the Configuration Context. The third line references the name of the login module Java class. The Custom login module section of this article elaborates more on the inner workings of login modules, but the main thing to know for now is that VWLoginModuleclass is mandatory for all configuration entries. Without this line, the component manager cannot start the component.

This login module is also the provider for the session to the Process Engine. The session to the Process Engine is managed by the VWSession class. This class has a specific constructor that, according to the documentation, can be used "in an environment where the JAAS context is already established." The signature of this constructor is:

public VWSession(java.lang.String url) throws VWException

This would suggest that a URL has to be provided as an argument, but further reading of the documentation shows that it actually needs a connection point as an argument. Fortunately, the connection point is available as a system property, so you can use the following code to create a session to the Process Engine:

String connectionPoint = System.getProperty("filenet.pe.cm.connectionPoint"); 
VWSession vwsession = new VWSession( connectionPoint );

Handling email is one of the common tasks performed by a Component Manager component. When you configure email notification on your Process Engine server, then you can also use the session to the Process Engine to acquire a session to your mail service provider as follows:

javax.mail.Session mailSession = vwsession.createMailSession();

The standard CE_Operations component queue also provides several methods you can use to send email. However, you may find situations where you require more control over the message send function.

To obtain a session to the Content Engine, you have to extend the JAAS configuration with one extra Login Module copied from the FileNetP8 configuration entry, which is also present in the taskman.login.config file. The new configuration entry should then look like the following:

MyLoginContext
{
	filenet.vw.server.VWLoginModule required;
	com.filenet.api.util.WSILoginModule required debug=false;
};

The WSILoginModule class establishes a connection to the Content Engine using the web services interface. As previously stated, the infrastructure around the component is responsible for the connection. In this case the WSILoginModule class builds a connection to the Content Engine. So the only thing the component has to do is start using the connection.

To create a new connection, you must have a URL (in this case it truly is a URL) to the Content Engine. Fortunately, the URL is available as a system property, so you can use the following code to create a connection to the Content Engine:

String url = System.getProperty("filenet.pe.bootstrap.ceuri"); 
Connection connection = Factory.Connection.getConnection(url);

From this point, you can fetch an Object Store object and perform some real Content Engine work, as in the following example:

EntireNetwork entireNetwork = Factory.EntireNetwork.fetchInstance(connection, null); 
Domain domain = entireNetwork.get_LocalDomain(); 
ObjectStore objectStore = Factory.ObjectStore.getInstance( domain, "MyOS" );

Debugging components

Two factors can make debugging and testing Java components developed for the Content Integrator challenging. First, the components are completely managed by the Component Manager. Therefore, it is difficult to manage their runtime environment, and this makes testing and debugging difficult. The second factor is that you must call the component from a component step in a workflow, and this requires that you design a test process and start the process each time you run a test case.

One way to help cope with this environment is to bring the environment back to the IDE. The article "Develop FileNet P8 BPM 4.0 custom components using Eclipse" (refer to Resources section for link) describes how you can configure Eclipse to run the Component Manager within the IDE. This addresses the first challenging factor described above, but does not eliminate the challenges presented by the second factor.

One approach to address the second challenge is to open up the Java component for testing and debugging by using the template design pattern. The idea is to wrap every reference to a session in a public or protected function. The code for obtaining a session for the Content Engine in this manner looks similar to the following:

protected Connection getConnection() { 
   String uri = System.getProperty("filenet.pe.bootstrap.ceuri"); 
   return Factory.Connection.getConnection(uri); 
} 
... 

Connection connection = getConnection();

This change has little to no impact on the running Java component, but it does open it up for testing. To test the component, first create a test program that connects to the Content Engine. For example, you could use code similar to the following:

private static Connection connection; 

@BeforeClass 
public static void setUpBeforeClass() throws Exception { 
   // initialize connection parameters 
   ... 
   connection = Factory.Connection.getConnection(url); 
   Subject subject = UserContext.createSubject(connection, username, password, 
								"FileNetP8"); 
   UserContext uc = UserContext.get(); 
   uc.pushSubject(subject); 
}

In the example above, the connection is stored as a field of a JUnit test class and initialized in the setUpBeforeClass() method. The next step is to create a new instance of the Java component using a constructor similar to the following:

MyOperations myOperations = new MyOperations () { 
   protected Connection getConnection() { 
      return connection; 
   } 
};

By creating the new class in this way, you override the existing getConnection() method, which serves as a template method for the connection. Now you have complete control over the Java component, and you no longer need the component manager to run the component. Therefore, you no longer have to call the Java component from a component step in a workflow, which addresses the second challenge mentioned above.

You can use a similar method to wrap the session to the Process Engine in a template. In the Java component, wrap the creation of the VWSession in a function as follows:

protected VWSession getVWSession() throws VWException { 
String connectionPoint = System.getProperty("filenet.pe.cm.connectionPoint"); 
return new VWSession(connectionPoint); 
}

The test program uses the following connection to connect to the Process Engine and uses a constructor to override the getVWSession() function and return the instance of the VWSession class to the function:

private static VWSession vwSession; 
... 
vwSession = new VWSession(); 
vwSession.setBootstrapCEURI(url); 
vwSession.logon( username, password, connectionPointName ); 
... 
MyOperations myOperations = new MyOperations () { 


VWSession getVWSession() throws VWException { 
return vwsession; 
} 
};

The Java primitive data types, such as string, boolean, and integer, can be supplied directly from the test program to the method being tested. There are two data types that are also supported as arguments for the functions of the Java class: VWAttachment and VWParticipant. For these two data types, you need a utility function to convert the test data to the proper data type. For example, you can use the following code to convert a Content Engine folder object to a VWAttachment class:

private VWAttachment getFolderAsVWAttachment(Folder folder) throws VWException { 
   VWAttachment folderAttachment = getCEAttachment(folder.getObjectStore() ); 
   folder.fetchProperties( new String[] { PropertyNames.ID, PropertyNames.NAME } ); 
   folderAttachment.setId( folder.get_Id().toString() ); 
   folderAttachment.setAttachmentName( folder.get_Name() ); 
   folderAttachment.setType( VWAttachmentType.ATTACHMENT_TYPE_FOLDER ); 
   return folderAttachment; 
} 
private VWAttachment getCEAttachment(ObjectStore objectStore) throws VWException { 
   VWAttachment ceAttachment = new VWAttachment(); 
   ceAttachment.setLibraryType( VWLibraryType.LIBRARY_TYPE_CONTENT_ENGINE ); 
   objectStore.fetchProperties( new String[] { PropertyNames.NAME } ); 
   ceAttachment.setLibraryName( objectStore.get_Name() ); 
   return ceAttachment; 
}

Developing a component using the method described in this section does cause a slight problem that you need to address. When you configure the Java class using the Process Configuration Console, you get the following error in the applet Java console when retrieving the public methods of the class:

java.lang.NoClassDefFoundError: com/filenet/api/core/Connection 
	at java.lang.Class.getDeclaredMethods0(Native Method) 
	at java.lang.Class.privateGetDeclaredMethods(Unknown Source) 
	at java.lang.Class.getDeclaredMethods(Unknown Source) 
	at filenet.vw.integrator.adaptors.java.VWJavaBaseDialog.initMethodList 
(VWJavaBaseDialog.java:224) 	
	(...)

Due to this exception, none of the public methods of the class are shown. This error is caused by the getConnection() method added above. The return class of this method is not a recognized class and therefore the getDeclaredMethods() method fails. This problem occurs each time a method is added to the Java class using "third party" data types as method arguments or return values. The solution is to use the Process Configuration Console to register the jar files that contain these classes. From the console, open the isolated region, select the Component Queues folder icon, and select the Register additional classes command from the action menu. Next, add the jar-file, which in this case is the jar file containing the Content Engine API (Jace.jar). Finally, commit the change to the Process Engine.


Configuration

You may need to make some additional configuration changes to the Java class besides the changes for the connections to the different systems. To make these modifications to the configuration of the Component Manager, use the Process Task Manager of your application server. Use the JRE parameters field on the Advanced tab of the configuration to define additional properties. With this field you can specify either the value of specific properties or a path to a configuration file. You can use the System.getProperty() function to retrieve the value of the configured properties.

You can also specify the location of the log4j configuration file as a JRE parameter. To do this, add the following line to the existing parameters:

-Dlog4j.configuration=file:C:\Program Files\MyComponent\log4j.properties

Now you can add logging to the Java components with a simple log4j.properties configuration file such as the following:

log4j.appender.MyAppender=org.apache.log4j.RollingFileAppender 
log4j.appender.MyAppender.File=C:/Program Files/MyComponent/trace.log 
log4j.appender.MyAppender.layout=org.apache.log4j.PatternLayout 
log4j.appender.MyAppender.layout.ConversionPattern=%d %5p [%t] -%m\r\n 

log4j.category.mypackage.MyOperations=debug, MyAppender

In the example above, RollingFileAppender prevents the file system from filling up. The [%t] part of the ConversionPattern configuration is useful if your Component Manager is running more than one thread for a specific component queue. The [%t] serves to make it clear which thread is logging a specific message.

You can also use the JRE parameters to enable the Component Manager for remote debugging. This type of debugging is similar to what is described in the developerWorks article "Develop FileNet P8 BPM 4.0 custom components using Eclipse" (refer to Resources section for link). Instead of bringing the Component Manager to your IDE, you connect the IDE to the remote running Component Manager. To enable remote debugging add the following parameters to the existing parameters:

-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n

If you are using Eclipse as the IDE, then you must use the launch settings of a "Remote Java Application" to make a new Debug Configuration for the project. Set the host to the application server that is running the Java component. You can then start the debugging by starting the Debug Configuration you created. When debugging is finished, you can detach the IDE using the Disconnect action.


Custom login module

Sometimes it may be necessary for a Java class to interact with a database. One way to enable this type scenario is to retrieve the settings required to connect to the database from a settings file. Normally, the settings file contains the credentials of the user accessing the database. The method shown in this section uses the JAAS framework to connect to a database. The user accessing the database is the same user configured to access the other systems. This way you do not need a specific settings file that contains the credentials. To achieve this, you must use a custom login module. This section describes how to write and deploy this type of login module. The "JAAS LoginModule Developer's Guide" on developerWorks (refer to Resources section for link) provides good general information about writing custom login modules.

A custom login module must implement the javax.security.auth.spi.LoginModule interface. This interface looks like the following:

public interface LoginModule { 
  void initialize(Subject subject, CallbackHandler callbackhandler, Map sharedState, 
			Map options); 
  boolean login() throws LoginException; 
  boolean commit() throws LoginException; 
  boolean abort() throws LoginException; 
  boolean logout() throws LoginException; 
}

The interface methods are called depending on the progress of the authentication process. The initialize() method is always called first. This is the method where the login module can save data necessary for the rest of the authentication process in its class fields. Here is the initialize() method used for this example:

public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, 
				Map options) { 
  this.subject = subject; 
  this.sharedState = sharedState; 

  driverClass = (String) options.get( "driverClass"); 
  connectionUrl = (String) options.get( "connectionUrl" ); 
}

The initialize() method shown above saves a class field reference to the subject and sharedState parameters. The values of the driverClass and connectionUrl are read from the options map. The way the values are configured is discussed later. For this example, the rest of the method parameters are not relevant.

Here is the login() method used for this example:

public boolean login() throws LoginException {
   succeeded = false;
   getCredentials();
   createDatabaseConnection();
   succeeded = true;
   return succeeded;
}

Typically, the login() function performs three tasks:

  • Obtains the credentials of the user
  • Uses the credentials to perform the desired authentication
  • Manages a boolean parameter that indicates the result of the login function

For this example, the following code is used to obtain the credentials:

private void getCredentials() throws LoginException {
   username = (String) sharedState.get("javax.security.auth.login.name");
   password = (String) sharedState.get("javax.security.auth.login.password");
}

This custom login module exploits the benefits of credentials sharing. The VWLoginModule class, which is higher in the authentication process, already used the JAAS callback mechanism to acquire the username and password and stored that information in the shared state map. So all this method has to do is read these values from the state map. The "JAAS LoginModule Developer's Guide" (refer to Resources section for link) describes the key names javax.security.auth.login.name and javax.security.auth.login.password as the standard convention for storing this information.

Now, as shown in the following code, all the necessary information is available to make a connection to the database:

private Connection createDatabaseConnection() throws LoginException {
   try {
      Class.forName( driverClass );
      connection = DriverManager.getConnection(connectionUrl, username, password);
      return connection;
   } catch (Exception exception ) {
      throw new LoginException( exception.getLocalizedMessage() );
    }
}

The login method should always throw a LoginException if there is an error during the authentication process. Therefore, the above method wraps the exception in a LoginException class.

If the login method succeeds, how the rest of the authentication process proceeds depends on the result of the other login modules involved in the authentication process. If all the login modules are completed, then the second phase of the authentication process starts. Based on the configuration, if all the required, sufficient, and optional login modules give satisfactory results, then, as shown in the following code, the commit() method is called:

public boolean commit() throws LoginException {
   if ( succeeded ) {
      registerPrincipal();
      clearCredentials();
      commitSucceeded = true;
      return true;
   }
   return false;
}

The interface specification states that if this method is called when the login() method is successful, then it should return a value of false. If the login() is successful, then this method must store the result of the authentication process in the Subject object that is passed on initialization.

The javax.security.auth.Subject class represents the object being authenticated. Once the authentication process is completed, the instance is associated with the different identities that the object has. The connection to the Process Engine is one of the identities the object obtained during the authentication process. This custom login module adds another identity, the connection to the database. Identities are represented by classes implementing the java.security.Principal and java.io.Serializable interfaces. The java.security.Principal interface looks like the following:

   public interface Principal {
   boolean equals(Object another);
   String getName();
   int hashCode();
   String toString();
}

For this example, a DatabasePrincipal class implements the required interfaces. The implementation part of the interfaces is pretty straightforward and is not shown here. The interesting part of the class is the following:

public class DatabasePrincipal implements Principal, Serializable {
   private String name;
   private Connection connection;

   public DatabasePrincipal(String username, Connection connection) {
      this.name = username;
      this.connection = connection;
   }
   public Connection getConnection() {
      return connection;
   }
   // Some more code, see attached source code
}

The class holds a reference to the database connection as a class field and also provides a "getter" method for this connection. Clients obtaining this identity can use this connection to interact with the database.

Switching back to the commit() method gives the following implementation of the registerPrincipal() method:

private void registerPrincipal() throws LoginException {
   principal = new DatabasePrincipal( username, connection );
   if ( !subject.getPrincipals().contains(principal) ) {
      subject.getPrincipals().add(principal);
   }
}

The method above creates a new instance of the DatabasePrincipal class using the username and the database connection. Then it adds this instance to the collection of principals. The implementations of the abort() and the logout() methods are not shown. To see these methods and the rest of the login module, you can refer to the sample source code included in the Download section.

Before you can use the DatabasePrincipal class in a client program, you must first configure and deploy it. In the beginning of this section, the driverClass and connectionUrl parameters were read from the options map. The options map is populated from the JAAS configuration file. For this code module, the JAAS configuration line must look like the following (an extra debug setting is also added):

mypackage.database.DatabaseLoginModule required debug="true"
connectionUrl="jdbc:sqlserver://localhost:1433;DatabaseName=MYDB;integratedSecurity=tr
ue;" driverClass="com.microsoft.sqlserver.jdbc.SQLServerDriver";

Note: This custom code module was tested with Microsoft® SQL Server 2005 configured to use SQL Server and Windows Authentication Mode. The JDBC driver used was Microsoft SQL Server 2005 JDBC Driver. The "Building the Connection URL" article on the MSDN Library website (refer to the Resources section for a link) provides documentation on the connection URL and a description of how to configure integrated authentication. The two key steps are adding integratedSecurity=true to the connection URL as shown in this article, and copying the DLL file sqljdbc_auth.dll, which is included in the driver download, to the Windows system path.

In the example above, the custom login module is part of the Java component using it. So deploying the Java component also deploys the login module to the component manager. You must also add the JDBC driver of the database as a required library of the component manager.

The credentials of the user configured in the Component Manager are used to connect to the database. This user should have sufficient rights for accessing the database. The password used to connect to the database must be the same as the configure password, or you must configure the connection to use an integrated security scheme.

In the Java component using the database login module, use the following code to retrieve the instance of the DatabasePrincipal class and subsequently the connection to the database:

protected java.sql.Connection getDatabaseConnection() {
   Subject subject = Subject.getSubject( AccessController.getContext() );
   Set<DatabasePrincipal> principals = subject.getPrincipals( DatabasePrincipal.class );
   if ( principals != null && ! principals.isEmpty() ) {
      DatabasePrincipal principal = principals.iterator().next();
      return principal.getConnection();
   }
   return null;
}

Conclusion

This article described how to develop and debug a custom Java component for use with the FileNet Component Integrator. This debugging method reduces the time it takes to develop the component and makes it possible to easily test the component using a JUnit test case. It can also change the way that you typically develop this type of component. Using the traditional method, you must test the component you are developing by quickly configuring it and deploying it to the Process Engine. Each time you change the structure of the component you must reconfigure and redeploy to test. By using the techniques described in this article, you can first thoroughly design and test the component and minimize the effort required for reconfiguration and redeployment.

The second part of the article described how to develop a custom login module, which is required if the component has to connect to external systems. The information in this section can also be used to write additional login modules.


Download

DescriptionNameSize
Sample code for this articleMyOperations.zip31.8KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Information management on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Information Management, Java technology, Security, Industries
ArticleID=497125
ArticleTitle=Developing Java components for the FileNet P8 Component Integrator
publish-date=06222010