组件集成器是FileNet Process Engine的一部分。 它使您可以从工作流程中的组件步骤调用自定义Java类的函数。 尽管可以将哪些数据类型用作函数的参数存在一些限制,但这是一种向流程添加一些额外功能的有效方法。
在这种类型的方案中,您将Java类及其从属类打包到jar文件中,并将其配置为在组件管理器的上下文中运行。 组件管理器负责为Java类的运行提供“令人愉悦”的环境。作为Java类的开发人员,您应该知道此环境的外观以及如何对其进行最佳配置。 本文旨在为您提供这些知识。
首先,本文介绍了如何获取流程引擎和内容引擎的会话的基础知识。 接下来,它描述了如何调试Java代码。 之后,您将了解配置选项。 最后,本文包括有关如何构建和配置用于数据库连接的自定义JAAS登录模块的更高级的讨论。
“ 下载”部分包含指向源代码的链接,该链接演示了本文介绍的概念。 该代码包括一个带有两个公共方法的自定义Java类和一个JUnit测试类,其中包含用于调用这些方法的代码。 getFolderDocuments()
方法演示了内容引擎的用法。 第二个sendMailMessage()
方法使用与Process Engine的连接来获取邮件会话,并使用Content Engine文档作为模板发送自定义消息。 源代码还包括本文描述的自定义代码模块。
您可以使用自定义Java组件执行与系统本身无关的特定业务需求。 但是,实际上,Java组件通常必须与内容引擎或流程引擎进行交互。 本节介绍如何获取到不同系统的会话。
通常,结合使用JAAS框架进行身份验证来对与Component Integrator结合使用的会话进行说明。 这意味着Java组件周围的基础结构负责与不同资源的连接,并且Java组件可以集中精力于其打算提供的特定功能。 本文的目标之一是还说明该基础结构如何为Java组件提供服务。
当使用Process Configuration Console配置特定的组件队列时,必须在Adapter选项卡的JAAS Credentials部分中提供Configuration Context 。 此配置条目称为JAAS节。 您提供的名称必须与Application Server上Component Manager文件夹中taskman.login.config文件中的配置条目相对应。
实际上,您必须查看Java组件所需的不同连接,并相应地配置配置项。 如果不同的Java组件需要相同的连接,则它们可以重用现有的配置上下文。
最低配置如下所示:
MyLoginContext
{
filenet.vw.server.VWLoginModule required;
};
在上面的代码示例中, MyLoginContext
是配置上下文的名称。 第三行引用登录模块Java类的名称。 本文的“ 自定义登录模块”部分详细介绍了登录模块的内部工作原理,但是目前要了解的主要是, VWLoginModuleclass
对于所有配置条目都是必需的。 没有此行,组件管理器将无法启动组件。
该登录模块还是与Process Engine的会话的提供者。 与流程引擎的会话由VWSession
类管理。 根据文档,此类具有特定的构造函数,可以在“已经建立JAAS上下文的环境中使用”。 该构造函数的签名是:
public VWSession(java.lang.String url) throws VWException
这表明必须将URL作为参数提供,但是进一步阅读文档显示它实际上需要一个连接点作为参数。 幸运的是,该连接点可用作系统属性,因此您可以使用以下代码创建到Process Engine的会话:
String connectionPoint = System.getProperty("filenet.pe.cm.connectionPoint");
VWSession vwsession = new VWSession( connectionPoint );
处理电子邮件是组件管理器组件执行的常见任务之一。 在Process Engine服务器上配置电子邮件通知时,还可以使用到Process Engine的会话来获取到邮件服务提供商的会话,如下所示:
javax.mail.Session mailSession = vwsession.createMailSession();
标准的CE_Operations组件队列还提供了几种可用于发送电子邮件的方法。 但是,您可能会发现需要对消息发送功能进行更多控制的情况。
要获得到Content Engine的会话,您必须使用从FileNetP8配置条目中复制的一个额外的登录模块扩展JAAS配置,该模块也存在于taskman.login.config文件中。 然后,新的配置条目应如下所示:
MyLoginContext
{
filenet.vw.server.VWLoginModule required;
com.filenet.api.util.WSILoginModule required debug=false;
};
WSILoginModule
类使用Web服务接口建立与内容引擎的连接。 如前所述,组件周围的基础结构负责连接。 在这种情况下, WSILoginModule
类将建立与内容引擎的连接。 因此,组件唯一要做的就是开始使用连接。
要创建新的连接,您必须具有指向内容引擎的URL(在这种情况下,它确实是URL)。 幸运的是,URL可用作系统属性,因此您可以使用以下代码创建与内容引擎的连接:
String url = System.getProperty("filenet.pe.bootstrap.ceuri");
Connection connection = Factory.Connection.getConnection(url);
从这一点出发,您可以获取一个对象存储对象并执行一些真正的Content Engine工作,如以下示例所示:
EntireNetwork entireNetwork = Factory.EntireNetwork.fetchInstance(connection, null);
Domain domain = entireNetwork.get_LocalDomain();
ObjectStore objectStore = Factory.ObjectStore.getInstance( domain, "MyOS" );
有两个因素会使调试和测试为Content Integrator开发的Java组件带来挑战。 首先,组件由组件管理器完全管理。 因此,难以管理其运行时环境,这使测试和调试变得困难。 第二个因素是必须在工作流的组件步骤中调用该组件,这要求您设计一个测试过程并在每次运行测试用例时启动该过程。
应对这种环境的一种方法是将环境重新带回IDE。 文章“使用Eclipse开发FileNet P8 BPM 4.0定制组件”(请参阅参考资料部分的链接)描述了如何配置Eclipse以在IDE中运行组件管理器。 这解决了上述第一个挑战性因素,但并没有消除第二个因素带来的挑战。
解决第二个挑战的一种方法是使用模板设计模式开放Java组件以进行测试和调试。 这个想法是将对会话的所有引用包装在公共或受保护的函数中。 以这种方式获取内容引擎会话的代码类似于以下内容:
protected Connection getConnection() {
String uri = System.getProperty("filenet.pe.bootstrap.ceuri");
return Factory.Connection.getConnection(uri);
}
...
Connection connection = getConnection();
此更改对正在运行的Java组件几乎没有影响,但确实可以进行测试。 要测试组件,请首先创建一个连接到内容引擎的测试程序。 例如,您可以使用类似于以下代码:
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);
}
在上面的示例中,连接存储为JUnit测试类的字段,并在setUpBeforeClass()
方法中初始化。 下一步是使用类似于以下内容的构造函数来创建Java组件的新实例:
MyOperations myOperations = new MyOperations () {
protected Connection getConnection() {
return connection;
}
};
通过以这种方式创建新类,您可以覆盖现有的getConnection()
方法,该方法用作连接的模板方法。 现在,您可以完全控制Java组件,并且不再需要组件管理器来运行该组件。 因此,您不再需要从工作流的组件步骤中调用Java组件,从而解决了上述第二个挑战。
您可以使用类似的方法将会话包装到模板中的Process Engine。 在Java组件中,将VWSession
的创建包装在一个函数中,如下所示:
protected VWSession getVWSession() throws VWException {
String connectionPoint = System.getProperty("filenet.pe.cm.connectionPoint");
return new VWSession(connectionPoint);
}
测试程序使用以下连接来连接到Process Engine,并使用构造函数覆盖getVWSession()
函数并将VWSession
类的实例返回到该函数:
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;
}
};
Java基本数据类型(例如字符串,布尔值和整数)可以直接从测试程序提供给要测试的方法。 Java类的功能还支持两种数据类型作为参数: VWAttachment
和VWParticipant
。 对于这两种数据类型,您需要一个实用程序功能来将测试数据转换为正确的数据类型。 例如,您可以使用以下代码将Content Engine文件夹对象转换为VWAttachment
类:
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;
}
使用本节中描述的方法开发组件确实会导致需要解决的一个小问题。 使用流程配置控制台配置Java类时,在检索该类的公共方法时,您在applet Java控制台中收到以下错误:
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)
(...)
由于这个例外,没有显示该类的任何公共方法。 此错误是由上面添加的getConnection()
方法引起的。 此方法的返回类不是可识别的类,因此getDeclaredMethods()
方法失败。 每次使用“第三方”数据类型作为方法参数或返回值将方法添加到Java类时,都会发生此问题。 解决方案是使用Process Configuration Console来注册包含这些类的jar文件。 从控制台中,打开隔离的区域,选择“组件队列”文件夹图标,然后从操作菜单中选择“ 注册其他类”命令。 接下来,添加jar文件,在这种情况下,该jar文件是包含Content Engine API(Jace.jar)的jar文件。 最后,将更改提交给流程引擎。
除了更改与不同系统的连接之外,您可能还需要对Java类进行一些其他配置更改。 要对组件管理器的配置进行这些修改,请使用应用程序服务器的流程任务管理器。 使用配置的“高级”选项卡上的“ JRE参数”字段来定义其他属性。 在此字段中,您可以指定特定属性的值或配置文件的路径。 您可以使用System.getProperty()
函数来检索已配置属性的值。
您还可以将log4j配置文件的位置指定为JRE参数。 为此,将以下行添加到现有参数中:
-Dlog4j.configuration=file:C:\Program Files\MyComponent\log4j.properties
现在,您可以使用简单的log4j.properties配置文件将日志记录添加到Java组件中,如下所示:
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
在上面的示例中, RollingFileAppender
防止文件系统填满。 如果组件管理器针对特定组件队列运行多个线程,则ConversionPattern
配置的[%t]
部分很有用。 [%t]
用于使哪个线程正在记录特定消息。
您还可以使用JRE参数来启用组件管理器以进行远程调试。 这种调试类型类似于developerWorks文章“使用Eclipse开发FileNet P8 BPM 4.0定制组件”中描述的调试( 有关链接,请参阅参考资料部分)。 您可以将IDE连接到远程运行的Component Manager,而不是将Component Manager引入IDE。 要启用远程调试,请将以下参数添加到现有参数中:
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
如果将Eclipse用作IDE,则必须使用“远程Java应用程序”的启动设置来为项目进行新的调试配置。 将主机设置为运行Java组件的应用程序服务器。 然后,您可以通过启动创建的调试配置来启动调试。 调试完成后,可以使用Disconnect操作分离IDE。
有时,Java类可能需要与数据库进行交互。 启用这种情况的一种方法是从设置文件中检索连接到数据库所需的设置。 通常,设置文件包含访问数据库的用户的凭据。 本节中显示的方法使用JAAS框架连接到数据库。 访问数据库的用户是配置为访问其他系统的同一用户。 这样,您就不需要包含凭据的特定设置文件。 为此,您必须使用自定义登录模块。 本节介绍如何编写和部署这种类型的登录模块。 developerWorks上的“ JAAS LoginModule开发人员指南”( 有关链接,请参阅参考资料部分)提供了有关编写自定义登录模块的良好常规信息。
定制登录模块必须实现javax.security.auth.spi.LoginModule
接口。 该界面如下所示:
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;
}
根据身份验证过程的进度调用接口方法。 始终首先调用initialize()
方法。 通过这种方法,登录模块可以将其余身份验证过程所需的数据保存在其类字段中。 这是用于此示例的initialize()
方法:
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" );
}
上面显示的initialize()
方法保存对subject
和sharedState
参数的类字段引用。 从选项映射中读取driverClass
和connectionUrl
的值。 值的配置方式将在后面讨论。 对于此示例,其余方法参数均不相关。
这是用于此示例的login()
方法:
public boolean login() throws LoginException {
succeeded = false;
getCredentials();
createDatabaseConnection();
succeeded = true;
return succeeded;
}
通常, login()
函数执行三个任务:
对于此示例,以下代码用于获取凭据:
private void getCredentials() throws LoginException {
username = (String) sharedState.get("javax.security.auth.login.name");
password = (String) sharedState.get("javax.security.auth.login.password");
}
此自定义登录模块利用了凭据共享的好处。 身份验证过程中较高的VWLoginModule
类已经使用JAAS回调机制来获取用户名和密码,并将该信息存储在共享状态映射中。 因此,此方法所需要做的就是从状态图中读取这些值。 “ JAAS LoginModule开发人员指南”(请参阅参考资料部分的链接)描述了键名javax.security.auth.login.name
和javax.security.auth.login.password
作为存储此信息的标准约定。
现在,如下面的代码所示,所有必需的信息都可用于建立与数据库的连接:
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() );
}
}
如果身份验证过程中发生错误,则login方法应始终抛出LoginException
。 因此,上述方法将异常包装在LoginException
类中。
如果登录方法成功,则身份验证过程的其余部分如何进行取决于身份验证过程中涉及的其他登录模块的结果。 如果所有登录模块均已完成,则身份验证过程的第二阶段开始。 根据配置,如果所有必需的,足够的和可选的登录模块均给出令人满意的结果,则如以下代码所示,将调用commit()
方法:
public boolean commit() throws LoginException {
if ( succeeded ) {
registerPrincipal();
clearCredentials();
commitSucceeded = true;
return true;
}
return false;
}
接口规范指出,如果在login()
方法成功时调用此方法,则它应返回false
值。 如果login()
成功,则此方法必须将身份验证过程的结果存储在初始化时传递的Subject
对象中。
javax.security.auth.Subject
类表示要认证的对象。 身份验证过程完成后,实例将与对象具有的不同身份相关联。 与流程引擎的连接是身份验证过程中获得的对象的身份之一。 该自定义登录模块添加了另一个身份,即与数据库的连接。 标识由实现java.security.Principal
和java.io.Serializable
接口的类表示。 java.security.Principal
接口如下所示:
public interface Principal {
boolean equals(Object another);
String getName();
int hashCode();
String toString();
}
对于此示例, DatabasePrincipal
类实现所需的接口。 接口的实现部分非常简单,此处未显示。 该类有趣的部分如下:
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
}
该类将对数据库连接的引用作为一个类字段保存,并为该连接提供“ getter”方法。 获得此身份的客户端可以使用此连接与数据库进行交互。
切换回commit()
方法可实现registerPrincipal()
方法的以下实现:
private void registerPrincipal() throws LoginException {
principal = new DatabasePrincipal( username, connection );
if ( !subject.getPrincipals().contains(principal) ) {
subject.getPrincipals().add(principal);
}
}
上面的方法使用用户名和数据库连接创建DatabasePrincipal
类的新实例。 然后,它将此实例添加到主体集合中。 没有显示abort()
和logout()
方法的实现。 要查看这些方法和登录模块的其余部分,可以参考“ 下载”部分中包含的示例源代码。
在客户端程序中使用DatabasePrincipal
类之前,必须首先配置和部署它。 在本节的开头,从选项映射中读取driverClass
和connectionUrl
参数。 选项映射是从JAAS配置文件填充的。 对于此代码模块,JAAS配置行必须类似于以下内容(还添加了额外的调试设置):
mypackage.database.DatabaseLoginModule required debug="true"
connectionUrl="jdbc:sqlserver://localhost:1433;DatabaseName=MYDB;integratedSecurity=tr
ue;" driverClass="com.microsoft.sqlserver.jdbc.SQLServerDriver";
在上面的示例中,自定义登录模块是使用它的Java组件的一部分。 因此,部署Java组件还会将登录模块部署到组件管理器。 您还必须将数据库的JDBC驱动程序添加为组件管理器的必需库。
在组件管理器中配置的用户的凭据用于连接到数据库。 该用户应具有访问数据库的足够权限。 用于连接到数据库的密码必须与配置密码相同,或者您必须将连接配置为使用集成安全方案。
在使用数据库登录模块的Java组件中,使用以下代码检索DatabasePrincipal
类的实例,然后检索与DatabasePrincipal
的连接:
protected java.sql.Connection getDatabaseConnection() {
Subject subject = Subject.getSubject( AccessController.getContext() );
Set principals = subject.getPrincipals( DatabasePrincipal.class );
if ( principals != null && ! principals.isEmpty() ) {
DatabasePrincipal principal = principals.iterator().next();
return principal.getConnection();
}
return null;
}
本文介绍了如何开发和调试与FileNet Component Integrator一起使用的自定义Java组件。 这种调试方法减少了开发组件所需的时间,并使得可以使用JUnit测试用例轻松测试组件。 它还可以更改通常开发此类组件的方式。 使用传统方法,必须通过快速配置组件并将其部署到Process Engine来测试正在开发的组件。 每次更改组件的结构时,都必须重新配置并重新部署以进行测试。 通过使用本文中介绍的技术,您可以首先彻底设计和测试组件,并最大程度地减少重新配置和重新部署所需的工作。
本文的第二部分描述了如何开发自定义登录模块,如果组件必须连接到外部系统,则需要开发该模块。 本节中的信息还可用于编写其他登录模块。
翻译自: https://www.ibm.com/developerworks/data/library/techarticle/dm-1006javafilenet/index.html