Frank Neumann, , WebSphere Application Server Enterprise Process Choreographer
2003 年 12 月 本文描述了在 Process Choreographer 中的查询函数的基础实现,并且提供了如何使用它们的最佳实践方法。
1. 引言
IBM 的 WebSphere 应用程序服务器企业流程策划器(WebSphere Application Server Enterprise Process Choreographer)版本 5 引入了人员活动,它允许在业务流程中进行人员交互。
要管理和描述人员和业务流程实体(比如,人员活动)之间的关系,可以创建工作项目。客户端应用程序(EJB 客户端)或 JSP 可以通过使用流程策划器的 query()EJB API 函数来查询这些工作项目和相关的实体对象的信息。
工作项目也可以用来实现安全性概念,描述允许谁获取关于特定业务流程对象的信息和并且对其进行操作。
本文描述了在 Process Choreographer 中的查询函数的基础实现,并且提供了如何使用它们的最佳实践方法。
2. 工作项目管理
工作项目,与其他业务流程实体对象(比如流程活动实例)一样,是在启动包括人员交互的业务流程之后创建的。根据业务流程模型中人员委派表达式,Process Choreographer 中的导航引擎触发创建业务流程实体适当的工作项目。
处理与人的交互的 Process Choreographer 实体包括:
一个人或一个组的工作项目是在创建一个实体或实体进入就绪状态时创建的。
人员委派表达式定义了谁(一个人或一组人)可以执行一个实体的某种角色。当一个实体被激活时,就给每个有资格的用户创建了与之相对应的工作项目:
- 潜在的启动者(对于一个业务流程)
- 潜在的所有者(对于一个人员活动)
- 查阅者(对于一个业务流程或一个人员活动)
- 管理员(对于一个业务流程或一个人员活动)
- 编辑者(对于一个人员活动)
- 潜在的发送者(对于一个事件)
除了这些模型驱动的工作项目之外,Process Choreographer 导航引擎还创建以下专用的工作项目:
- 启动者(对于一个业务流程)
- 管理员(对于一个人员活动)
- 拥有者(对于一个人员活动)
人员活动的管理员工作项目是为接收上一级业务流程的管理员工作项目的人创建的。
由于工作项目也可用来实现安全性(访问和读取特定实体中的数据的权利),人员活动的附加管理员工作项目也允许业务流程管理员访问下一级的人员活动。请参阅 实体的安全性 一节,以了解详细的情况。
2.2. 工作项目的数据库表
在配置 Process Choreographer 的过程中,关系数据库是与业务流程容器是相关联的。这种数据库存储管理业务流程所需的模板(模型)和实例(运行时)数据。
由于业务流程是事务性地导航的,而处理可以在分布式(集群式)的环境中进行,所以关系性数据库是最好的和最安全的选择。
Process Choreographer 数据库 Schema 包含表和视图(请参见附加的可查询视图)。
与工作项目有关的数据存储在下面的表中(请注意,Process Choreographer Schema 中表的名称通常以 _T 结尾,而视图则没有这种约束):
-
WORK_ITEM_T
这个表包含关于一个实体(例如,人员活动)和一个人或一组人之间关系的一行。如果多个角色产生这个关系(例如,如果一个人既是流程的管理员又是流程的启动者),就为每个角色(或 人)创建一行。
-
RETRIEVED_USER_T
如果关系是一个实体和一组人之间的(通常是业务流程中人员委派表达式的结果),那么这个表就包含从特定人员委派表达式的人员目录中检索的所有具有资格的人员。
-
STAFF_QUERY_TEMPLATE_T
在部署可以有人员委派表达式的的对象的过程中,表达式是以预编译的格式存储在表中的。如果流程多次定义相同的人员委派表达式,那么在表中就只有此表达式的一个条目,也就是说,如果人员表达式在语法上是等同的,那么它们在此表中就是共享的。
-
STAFF_QUERY_INSTANCE_T
如果在流程导航的过程中对人员委派表达式求值,那么该表达式的实例就与它何时到期的时间戮被同时创建。人员委派表达式求值的结果存储到 RETRIEVED_USER_T 表中。
WORK_ITEM 视图被定义为表 WORK_ITEM_T 和 RETRIEVED_USER_T 的联合,目的是每个关系和用户再包含一行,即便将一组人指定为某种角色。这个视图是基于 query()API 函数的所有操作的目标。
2.3. 其他的可查询视图
由于不仅工作项目自己本身是有意义的,而且关于引用的对象(比如业务流程和人员活动)的信息也是有意义的,所以 query() API 函数也提供对这些信息的访问。
为了这样做(并引入一个附加的抽象层,以便基础表可以在 Process Choreographer 的未来版本中进行更改),query()API 函数将其他的一些视图定义为可查询的:
-
PROCESS_TEMPLATE
-
PROCESS_ATTRIBUTE
-
ACTIVITY
-
ACTIVITY_ATTRIBUTE
-
EVENT
要获得这些视图可用的列 / 属性的详细列表,请参阅参考资料 [2]。
要处理 query()API 调用,需要一些关于列类型的信息。这样,除了上面列出的之外,没有其他的表和视图可以通过 query() API 调用进行查询。
3. QueryResultSet
在获取 ProcessChoreographer 的 BusinessProcess 接口的本地和远程的 EJB 接口之后,就可以调用 queryAPI 函数并返回 QueryResultSet 对象。
query() 调用和结果集处理的样本:
import com.ibm.bpe.api.*;
...
InitialContext initialContext = new InitialContext(...);
// lookup the EJB home interface
Object object = initialContext.lookup("com/ibm/bpe/api/BusinessProcessHome");
BusinessProcessHome processHome = (BusinessProcessHome)
javax.rmi.PortableRemoteObject.narrow(object, BusinessProcessHome.class);
// get the remote interface
BusinessProcess process = processHome.create();
// run a query to show all activity names that are ready to be started
// and that I can claim (where I have a potential owner work item)
QueryResultSet resultSet = process.query(
// select clause - what do we want to get?
"PROCESS_INSTANCE.NAME, ACTIVITY.TEMPLATE_NAME",
// where clause - what are the qualifying rows?
"WORK_ITEM.REASON=WORK_ITEM.REASON.REASON_POTENTIAL_OWNER AND " +
"ACTIVITY.STATE = ACTIVITY.STATE.STATE_READY",
// order clause - specify the sort order
"PROCESS_INSTANCE.NAME",
// threshold - return first 10 entries only
new Integer(10),
// no timezone specified
null
);
// loop over results in the result set
while( resultSet.next() )
{
// print out selected columns, keep in mind column indexes start with "1"
System.out.println( "Process instance name = " + resultSet.getString(1) );
System.out.println( "Activity template name = " + resultSet.getString(2) ); }
|
请注意,QueryResultSet 与 JDBCResultSet 是相似的,特别是在指针驱动的导航和列索引是从“1”而不是“0”开始这些方面:
- 在调用
query() 之后,结果集的指针是放置在第一个条目之前的。
- 利用
next() 将指针向下移动一行,并且在到达结果集的末尾时返回“false”。
-
first() 和 last() 可以用于重新访问已经读取的条目。
然而,与 JDBCResultSet 大不相同的是,QueryResultSet 对象是可序列化的,并且可以发送到(远程客户端)。它还包括特定于 Process Choreographer 的增强,比如 ID 和时间戮处理。
3.1. 读取 QueryResultSet 数据
QueryResultSet 理解六种列类型:
-
TYPE_STRING
用于任何文本字符串列,这种类型也可用于存储在 CLOB 中的列。
-
TYPE_NUMBER
用于任何数值数据,比如短整型、整型或长整型。
-
TYPE_TIMESTAMP
对于任何时间戳数据。分辨率通常是毫秒(由数据库系统进行支持)。
-
TYPE_BINARY
用于二进制字符串和 BLOB 数据。
-
TYPE_BOOLEAN
用于存储的布尔值(true 或 false),数据库表示是 smallint 列。
-
TYPE_ID
用于实体标识符。每个实体都是由一个或更多这样的标识符来标识的,例如,流程实例 ID(PIID)和活动实例 ID(AIID)。它们以 16 字节长二进制字段的形式存储在数据库中。
QueryResultSet 提供函数来从当前的指针位置读取数据的:
-
byte [] getBinary()
返回 TYPE_BINARY 的列。例如,VARBINARY 列。
-
Boolean getBoolean()
返回“true”、“false”或空。这个函数可以应用于任何数值列类型,并且为任何非空值返回 true。
-
Integer getInteger(), Long getLong(), Short getShort()
返回相应的数字值或空。 注意:这些函数可以应用于任何数值类型,不过,如果基础数据类型不同,那么它的类型必须进行“强制转换”,这可能会导致数据的丢失。
-
Object getObject()
返回列值的普通对象。如果您不知道类型或只需要将它传送到另一个函数,您就可以使用这个函数。
-
OID getOID()
如果列是标识符,此函数就返回其他的 API 调用需要的 OID 对象。您可能得将 OID(基本接口)转换成更具体的 OID 类,比如 PIID 或 AIID。
-
String getString()
返回指定列的字符串表示。这个函数不仅可用于基于字符的数据列,它也可以用于获取数字值、时间戮和 OID 值的字符串表示。对于 常数数值,这个函数返回常数的描述性名称而不是它的数字值。
-
Calendar getTimestamp()
返回指定列的时间戮值。这个函数考虑到当前客户的时区设置。要了解更详细的内容,请参阅 时间戳和时区。
如果指定的列索引超出了列数的范围,就抛出 IndexOutOfBoundsException(运行时)异常。
如果该函数不适用于相应的列类型,就抛出 ClassCastException 异常。
避免产生这些异常的最安全的方法是首先检查列的类型,然后调用查询结果集中适当的数据存取程序函数。
3.2. QueryResultSet 元数据
在正常情况下,调用 query()API 函数的程序知道它需要查询数据中的哪一列。然而,如果允许用户指定任意的列,(普通的)GUI 就必须在它显示数据之前弄清楚结果集包含什么。
为了这个目的,QueryResultSet 提供了下列函数来获取关于选取的列的元数据:
-
numberColumns()
返回 select 从句中的列数。
-
getColumnDisplayName(int columnIndex)
返回列的名称,这个名称可以用来作为结果表中的显示标题。
- 从 5.0.2 版开始:
getTableDisplayName(int columnIndex)
返回表或视图的名称。这对于在结果数据表中显示标题或对于理解列属于哪一个视图是有用的。
- 从 5.0.2 版开始:
getColumnType(int columnIndex)
返回列的类型。这个函数返回为 QueryColumnInfo 接口(TYPE_STRING、TYPE_NUMBER、TYPE_TIMESTAMP、TYPE_BINARY、TYPE_BOOLEAN、TYPE_ID)中的列类型定义的常数之一。
-
size()
返回结果集中条目的数量。这可以用于读取结果集的数据之前分配内存。
下面的样本演示了如何显示因为某种原因(例如,查询从句是通过用户输入动态地传送过来的)导致不知道结构(列)的结果集内容。
void displayResultSet( QueryResultSet resultSet )
{
// Print table heading
for( int i=0; i<resultSet.numberColumns(); i++ )
{
System.out.print( resultSet.getColumnDisplayName(i) );
System.out.print( "\t" );
}
System.out.println();
// Print row data (String representations)
while( resultSet.next() )
{
for( int i=0; i<resultSet.numberColumns(); i++ )
{
System.out.print( resultSet.getString(i) );
System.out.print( "\t" );
}
System.out.println();
}
}
|
4. 查询处理
Process Choreographer 中的 query()API 调用设计成尽可能与 SQL 语法类似,并协助描述流程策划器的有用特征。
从 query() 的参数中派生一个 SQL 语句的必要操作包括:
- 收集引用的视图,并用定义的 关联名 来替代视图名称。
- 用参数标记来代替字符串和数值文字。
- 解析时间戮和 ID,并在预处理语句中将它们转换成 JDBC 值。
- 将适当的“from”添加到 SQL 语句中。
- 添加结果集限制阈值,此值与数据库系统有关。
其结果是 JDBC 预处理的语句,可由数据系统进行处理。
这些步骤的细节将会在本文的后面讨论。
需要特别注意的是,Process Choreographer 不 解析 传递给它的从句,因为解析是由数据库执行的。这使得可以充分利用可用于数据库系统的 SQL 语法,而不需要所有数据库系统都支持的有限子集。
4.1. 参数标记和预处理语句
用参数标记来替代每个文字表达式,比如字符串或数字的常数值,它们存在于 query()API 调用中的 where 从句中。
例如,下面的 where 从句中的比较表达式:
"... WI.OWNER_ID = 'Frank' ..."
将通过参数标记进行替代:
"... WI.OWNER_ID = ? ..."
这使得可以用 JDBC “预处理”的语句来替代“语句”,即便对于由客户端应用程序传入的字符串文字也是如此。
应用程序(比如 Web 客户端)通常进行一组有限的查询,不同之处仅仅在于字符串文字(例如,认为查询检索所有属于已登录用户的工作项目)。
在不使用预处理语句的情况下,必须在数据库系统中为每个用户编译查询,这将产生重要的性能影响。
然而,在替换成参数标记之后,这些查询在句法上都是相同的,而数据库系统只需要构建访问计划一次,并能够在以后的查询中重复使用它。
4.2. 相关性名称
为了缩短 SQL 语句和允许 复杂的子查询,给所有的可查询视图指定了相关性名称:
PROCESS_TEMPLATE
|
PT
|
PROCESS_ATTRIBUTE
|
PA
|
PROCESS_INSTANCE
|
PI
|
ACTIVITY
|
AI
|
ACTIVITY_ATTRIBUTE
|
AA
|
EVENT
|
EI
|
WORK_ITEM
|
WI
|
一个查询,比如
query("WORK_ITEM.WIID","WORK_ITEM.OWNER_ID='Frank'", null,null, null );
将产生一个 SQL 语句
SELECT WI.WIID FROM WORK_ITEM WIWHERE WI.OWNER_ID='Frank'
5. 查询从句
就像在 SQL 中一样,Select 从句指定需要从数据库中提取哪些信息。可用的视图和列名称在参考资料 [2] 中列出。
Process Choreographer 的 query()API 调用中的 Select 从句必须符合下列语法规则:
- Select 从句可以包含一个或多个列说明,每个说明描述可查询的表或视图中的一列。
- 多个列的说明用逗号“,”隔开。
- 每个列说明都必须恰好包含
view.column 形式的一个标记,其中,view 是前面列出的可查询视图之一,而 column 是该视图中的列。包含句点“.”的 select 从句中的每个表达式都假定是视图 - 列标记。
-
view.column 标记可以被数据库系统支持的任何 SQL“修饰”包围。然而,这种修饰绝不可以更改返回的类型。例如,聚合函数(COUNT、MIN、MAX)更改返回的类型,因此是不被支持的。
如果表或列的名称不被识别,则抛出 QueryUnknownTableException 或 QueryUnknownColumnException 异常。
有效的Select 从句的例子如下:
- 获取所有工作项目的 ID
"WORK_ITEM.WIID"
- 获取不同活动的 ID 和工作项目的原因
"DISTINCTACTIVITY.AIID, WORK_ITEM.REASON"
- 获取流程名称和创建时间
"PROCESS_INSTANCE.NAME,PROCESS_INSTANCE.CREATED"
- 基本算法,只要它不改变最后得到的列类型并且数据库系统支持它
"WORK_ITEM.REASON +7"
- 列的别名
"WORK_ITEM.REASON AS ROLE"
无效
的Select 从句的例子如下:
- 聚合函数
"COUNT(WIID)"
- 产生不兼容的类型的类型转换(强制转换)
"CAST (ACTIVITY.CREATED AS CHAR)"
-
view.column 标记中未知的表或列的名称
"WORK_ITEM.DOES_NOT_EXIST"
"MYVIEW.VALUE"
- 不包含有效
view.column 的表达式,例如,返回常数值,调用已存储的过程或 UDF
"WORK_ITEM.WIID, 'text'"
5.2. Where 从句
where 从句限制查询的结果集,并指定过滤标准。
它是一个可选的参数。如果这个参数为空,就不应用过滤器。
可以使用与 select 从句类似的方式来处理 where 从句。您必须遵守一定的规则,基础数据库中的大部分语法都可以用于这种表达式:
- 每个
view.column 标记都必须引用一个已知的可查询的视图和列 -- 如 select 从句 中所述。如果表或列的名称不被识别,就抛出 QueryUnknownTableException 或 QueryUnknownColumnException。
- 一般不支持在 where 从句中执行另一个完全查询 Subselects。不过,可以参考 复杂查询 一节来找出避开这种限制的方法。
注意:某些列类型(比如对象 ID 和时间戳)需要在本文后面描述的特别语法。
5.3. Order-by 从句
order-by 从句可以指定一个或多个列,用以对结果集进行排序。这是一种可选的参数,如果参数为空,就不执行排序。
处理这种 order 从句的方法与处理 select 从句的方法类似,并且适用于相同的语法限制。
您可以通过加上降序(descending)或 升序(ascending)标记来指定排序的方向。
排序操作完全由基础数据库系统进行处理,这意味着某些地点或代码页的字符次序必须在此指定。在创建查询结果集时不进行事后处理。
5.4. 阈值
可以选择限制在 QueryResultSet 中取得和返回的行数。
如果预期的结果很大并且限制返回的行数是可以接受的,您就应该考虑使用这个值。
其实现有所不同,并且取决于基础数据库支持什么。
下面是一些例子,显示了流程策划器如何使用这个值来限制结果集的:
- 对于 DB2,最后得到的
SELECT 语句是由 "FETCH xxx FIRST ROWS ONLY" 限制的。
- 对于 Oracle,
SELECT 语句中的 where 从句是由 "AND ROWNUM <=xxx" 扩展的。
- 对于所有的其他数据库系统,结果集限制是在 JDBC 的结果集上指定的,例如,为 Cloudscape 推荐的方法。
注意:根据数据库系统的实现方法的不同,order-by 从句和阈值的结合可能导致不同的结果。例如,有些数据库系统先根据 order-by 从句的操作执行排序操作,然后将结果的列表截断,而其他的数据库系统首先应用阈值,然后再将剩余的行排序。
5.5. 时间戮和时区
时间和日期的表示通常取决于当前的地点和时区设置。
为了支持多时区环境,使得客户端应用程序、WebSphere Application Server 和数据库后端能够驻留在不同的时区中,流程策划器必须将不同的时区考虑在内,并且提供进行任何必要的转换的方法。
时间信息一般存储在 Process Choreographer 的数据库的 UTC 中,任何跟踪或审核日志时间戮都是基于 UTC 的,因此能够很容易地改正运行在不同的时区中的 Application Servers 应用程序服务器写入的结果。在用户界面上显示时间信息之前或从用户界面获取时间信息之后,都需要进行转换。
这种转换影响两个函数:查询结果集上的 query() 和 getTimestamp()。
query()API 需要使用 TS('') 伪函数传送的 where 从句中的时间戳,可以选择指定时区。如果没有给初时区(该参数为空),时间戮就被假定在 UTC 中,并且不经任何转换就将其存储到数据库。
使用 TS() 传送 UTC 中的时间戳的例子如下:
process.query( "ACTIVITY.AIID",
"ACTIVITY.STARTED > TS('2003-05-13T06:01:07')",
null, null, null );
|
相同的例子,但时间戮是在太平洋标准时区(PST):
process.query( "ACTIVITY.AIID",
"ACTIVITY.STARTED > TS('2003-05-13T06:01:07')",
null, null, TimeZone.getTimeZone("PST") );
|
这是在将客户端环境中的时间戮信息传递到 Process Choreographer 时需要做的全部时区转换工作;query() 函数将时间戮转换到 UTC 并继续用经转换的值进行工作。
TS('') 函数中的时间戳需要采用下面的格式:
YYYY-MM-DDThh:mm.ss
除了年之外,所有的东西是可选的,也就是可以去掉,而采用缺省值。因此,只指定日期部分的时间戮 TS('2003-05-13') 也是有效的。
要记住,如果您的多层应用程序是在应用程序服务器上运行的,在处理来自远程的 Web 客户端的请求时,您必须从用户的 Web 浏览器中得到时区信息。
现在我们考虑 getTimestamp() 函数给 QueryResultSet 返回了什么。首先,返回的 JavaCalendar 对象同时包含了时间和时区两方面的信息。时间总是 UTC,与时区信息无关。设置在 Calendar 对象中的时区或者是 UTC,或者是在前一个 query() 调用中传送的时区。
如果您有一个多层的环境,其中,Web 客户端可以有与运行您的 JSP 的机器不同的时区设置,那么这个时区信息能够帮助您完成下面的转换。如果您的代码是在客户端的机器上运行的,您也能够忽视在 Calendar 对象中的时区设置,而采用本地设置。
要显示时间戮信息,您必须使用一个格式程序类,如 DateFormat 或 SimpleDateFormat。下面的例子显示了 Calendar 对象中的数值可以如何进行显示(考虑 Calendar 对象附带的时区信息):
resultSet = process.query( "ACTIVITY.STARTED", null, null, null,
TimeZone.getTimeZone("Europe/Berlin") );
while( resultSet.next() )
{
Calendar cal = resultSet.getTimestamp( 1 );
// target timezone as specified in query() (or UTC if not specified)
// (you may also set the browser's timezone here)
TimeZone tz = cal.getTimeZone();
// Use either DateFormat or SimpleDateFormat
DateFormat fmt = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG );
fmt.setTimeZone( tz );
// Format the time and print the result
System.out.println( "Activity started: " + fmt.format( cal.getTime() ) );
}
|
5.6. CURRENT_DATE
在使用 QueryResultSet 对象时 Process Choreographer 填充 Calendar 对象的方法是与 API 对象相同的,如由 getActivityInstance(AIID) 这样的 API 调用返回的 ActivityInstanceData
对象。由于您不给这个调用传送时区参数,所以 Calendar 对象将当前的本地时区设置为缺省值。
使用版本 5.0.1(PTF1)的 Process Choreographer,可以引入 CURRENT_DATE 标记来将当前的日期指定为时间戳。可以用 CURRENT_DATE 标记来表示当前的日期,而不用在 TS() 函数调用中显式地传送它。对于定义需要根据当前时间返回结果的工作列表,这是非常重要的。
大多数数据库系统也支持时间戮列的算法。由于 CURRENT_DATE 标记在以后的处理中被一个参数标记取代,向 CURRENT_DATE 标记加和减时间都是不支持的,然而,您能够用比较列指定附加的时间信息。
下面的例子显示了如何在采用 DB2 Universal Database 语法的 where 从句中使用 CURRENT_DATE,其他数据库的语法可能有所不同:
"... ACTIVITY.STARTED + 3 DAYS > CURRENT_DATE ..."
Example of invalid use:
"... ACTIVITY.STARTED > CURRENT_DATE − 3 DAYS"
5.7. 对象 ID
为了标识应用程序对象(比如活动或业务流程),为这些对象的大多数引入了标识符(ID)。在数据库中,ID 表示为 16 字节的原始(二进制)数据。
要处理应用程序中的 ID,还有一种字符串表示法能够在 where 从句中使用。例如,一个流程实例可能由用一个 ID 来标识,比如:
_PI:800300f3.9aee3366.9e1c67f6.27
要在 query() 调用的具有资格的 where 从句中传送这个 ID,就必须用 ID() 伪函数来指定它:
"... AND PROCESS_INSTANCE.PIID =ID('_PI:800300f3.9aee3366.9e1c67f6.27') ..."
ID() 伪函数将 ID 字符串表示翻译回它的二进制格式,并且把它传送到基础数据库系统中。相应的 SQL 语句又一次使用参数标记并替代相关性名称:
" ... AND PI.PIID = ?"
如果一个 ID 被选入结果集,getOID() 或 getString() 就能够应用于这个结果集。第一个函数返回一个 OID 对象,而第两个函数返回相应的字符串表示。如果您选择使用 OID 对象或字符串,就应该使用 OID 表示,因为传递一个对象比转换一个字符串表示要快很多。
注意:如果您省略了对象 ID 的 ID() 伪函数,而数据库系统执行从对象 ID 字符串到二进制数据库表示的隐式类型转换,您不会得到 SQL 错误。在这种情况下,您将会只注意到结果集是空的,没有返回的行。
要仔细地检查您的 where 从句以确保对象 ID 被正确地处理,这一步是很重要的。
5.8. 常数值的语法
可查询的视图中的一些列被定义为数值型,但应该只包含特定的不同值。这与编程语言中的牧举是相当的。
代替将这些列的值与具体的整数值相比较,一种好的实践方法是将这些值与预定义的常数相比较。
例如,WORK_ITEM.REASON 列可能具有下列值:
-
REASON_POTENTIAL_OWNER=1
-
REASON_EDITOR=2
-
REASON_READER=3
-
REASON_OWNER=4
-
REASON_POTENTIAL_STARTER=5
-
REASON_STARTER=6
-
REASON_ADMINISTRATOR=7
-
REASON_POTENTIAL_SENDER=8
假定您对于所有您有拥有者工作项目(您自己声明的)的对象的工作项目感兴趣。
这种查询的一个有效的 where 从句将是:
" ...WORK_ITEM.REASON = 4 ..."
由于这种做法隐藏了意义,建议您使用预定义的常数值,这样的语法为:
" ...WORK_ITEM.REASON = WORK_ITEM.REASON.REASON_OWNER..."
注意:右边的常数表达式是根据下面的格式构建的:
<view>.<column>.<constant name>
常数名称必须是全限定的,这是因为它必须加入 view.column 的范围以避免与其他列中定义的常数冲突。
另一种方法将固定的数值用更灵活地命名的常数来代替,使用的是在相应的 API 对象中定义的数值:ect:
" ... WORK_ITEM.REASON = " + WorkItemData.REASON_OWNER + "..."
相应的 API 对象不仅包含所有定义的作为整数值的常数表示,还包含所有列的附加的有价值信息。
6. 实体的安全性
Process Choreographer 使用工作项目来提供对实体(比如活动和业务流程)的访问。
如果一个进入 WebSphere Application Server 用户的主名称具有至少一个实体的工作项目,那么这个用户将被允许查询它的内容。
在 Process Choreographer API 和导航引擎中进行进一步的检查可以限制依赖于工作项目的类型(原因)的附加操作,不过,使用 query() 操作检索信息,任何工作项目的存在都是足够的。
下一节将解释 query()API 函数如何实现这些。
6.1. From 从句、联合算法、联合层次
对于那些熟悉 SQL 的人,看起来可能会很奇怪的是,select 从句和 where 从句在 query()API 调用中给出,而 from 从句省去了。
原因是指定将要查询的视图的 from 从句是根据 select、where 和 order-by 从句中引用的列计算的。当扫描这些从句时,就会构建将引用的视图的列表,它将成为 from 从句的基础。
WORK_ITEM 视图在这个过程中扮演了一种特殊的角色,因为它总是被添加到 from 从句中以确保安全性限制。所有被引用的视图都与工作项目视图“联合”,以确保只有用户拥有工作项目的信息才能够被返回。
要完成这项工作,可以把附加的联合条件追加到传送的 where 从句中,否则将返回两个视图的“卡迪尔产物(cartesian product)”。
例如,考虑下面的 select 从句(假定 where 和 order-by 从句都是空的),其中,用户想要查询他拥有工作项目的所有活动的所有状态:
SelectClause="ACTIVITY.STATE"
在内部,from 从句是由所有引用的视图构成的,在这个例子中,只有 ACTIVITY 外加 WORK_ITEM 视图:
FromClause="WORK_ITEM WI, ACTIVITY AI"
where 从句获取追加的适当联合条件:
WhereClause="WI.OBJECT_ID = AI.AIID"
虽然这看起来似乎是简单的操作,但是如果多个实体参与其中,它能够变得比较复杂。
考虑下面的 select 从句(为了简单起见,where 从句再一次被假定为空的),用户要进行与上面相同的查询,但他也对包含的流程的实例的名称感兴趣:
SelectClause="ACTIVITY.STATE, PROCESS_INSTANCE.NAME"
From 从句将变成:
FromClause="WORK_ITEM WI, ACTIVITY AI, PROCESS_INSTANCE PI"
然而,where 从句附加的联合条件会是怎样的呢?
Process Choreographer 使用一个内置的实体的层次结构和关于哪个列将要用来作为联合条件才符合用户的期待的知识:
WhereClause="WI.OBJECT_ID = AI.AIID AND AI.PIID = PI.PIID"?/p>
另一种替代的(排除的)联合条件可能变成是:
"WI.OBJECT_ID=PI.PIID AND PI.PIID=AI.PIID"
然而,这将会返回关于所有的活动的信息,其中,用户拥有上一级流程的工作项目,而不是拥有活动的工作项目,这将打破前面叙述的规则。
虽然没有更详细地解释此处理过程,但是这给出了当 Process Choreographer 组合最终的 SQL 语句时会发生什么的印象,并且还有可能帮助解释了由您的数据库系统处理的某些 SQL 语句返回的一些意想不到的结果。
6.2. 添加用户 --BPESystemAdministrator 角色
要只返回调用的项目拥有工作项目的对象,可以将附加的表达式添加到每个调用的 where 从句:
"WORK_ITEM.OWNER_ID = '...'"
其中,“...”成为已登录用户的主要名称。
采用前面描述的添加 WORK_ITEM 视图和正确的联合条件的机制,这将完成 Process Choreographer 中 query()API 调用的概念。
在添加 "WORK_ITEM.OWNER_ID = ..." 限制到 query()API 调用的 where 从句的规则中,存在一种例外的情况:J2EE 角色 BPESystemAdministrator 中的用户被允许查询所有的工作项目,即便这些工作项目并不属于他们自己。然而,去掉 "WORK_ITEM.OWNER_ID =..." 从句并不取消至少必须存在一个工作项目来收集一些实体的信息的限制。
重要的是不要混淆在建模的过程中赋予的角色和在部署的过程中赋予的 J2EE 角色。为了成为 BPESystemAdministrator 的成员,J2EE 角色改变了业务流程的行为和构建结果 SQL 语句的方式。然而,像“Administrator”或“Potential Owner”这样在建模时委派给业务流程或人员活动的角色,是针对每个业务流程分派的,决定对一个业务流程实体执行一定的任务的授权。
由于 Process Choreographer 使用登录到 WebSphere Application Server 的用户的主名称来进行数据库的比较,您必须小心确定应用程序服务器是否使用大小写敏感的目标来进行授权,例如,用户的注册是在 Windows 系统上运行的 Application Server。尽管用户能够用不同大小写拼写的登录名登录到服务器,query() 调用可能不返回结果,因为基础的数据库系统对主名称执行大小写敏感的比较。在这样的环境中,您必须或者在客户端处理这个问题,或者让用户知道当输入他们的登录用户 ID 时需要严格匹配大小写。
7. 复杂查询
如前所述,子查询在一般情况下是不支持的,因为受 ProcessChoreographer 中扫描查询的方法的限制。然而,如果您知道所用的规则和算法,您就可以使用子查询。
考虑查询最近的业务流程模板的常见需求。
每个流程模板都包含一个 VALID_FROM 字段,用于指定模板何时变得无效。要发现新的名为 processTempl1 流程启动时,Process Choreographer 选择哪一个模板,可以参见下面的例子。这个例子显示了传送到 queryProcessTemplate()API 调用(它非常类似于 query()API 调用,惟一的不同之处在于它返回 ProcessTemplateAPI 对象而不是 QueryResultSet)的 where 从句:
"PROCESS_TEMPLATE.NAME = 'processTempl1' AND PROCESS_TEMPLATE.VALID_FROM = (SELECT MAX(VALID_FROM) FROM PROCESS_TEMPLATE WHERE NAME=PROCESS_TEMPLATE.NAME AND VALID_FROM <= CURRENT_DATE)"
subselect 从句返回不迟于当前日期的 VALID_FROM 的最大值。理解名称比较 NAME=PROCESS_TEMPLATE.NAME 的工作方式需要本文前面几节中的知识:这个从句通过使用 PROCESS_TEMPLATE 视图和参数标记的 PT 别名进行转换:
"PT.NAME = ? AND PT.VALID_FROM = (SELECT MAX(VALID_FROM) FROM PROCESS_TEMPLATE WHERE NAME=PT.NAME AND VALID_FROM <= CURRENT_DATE)"
请注意,MAX() 中的 VALID_FROM 列和末尾的 NAME 列将被替换,因为它们不被识别并且不包含句点“.”。这对于 PROCESS_TEMPLATE 也是样的。
因为 PROCESS_TEMPLATE.NAME 被 PT.NAME 替代,所以查询返回希望的结果。
8. 人员委派到期和缓存
出于性能方面的原因,Process Choreographer 缓存在对人员委派表达式进行求值的过程中从人员插件检索的用于 ID(要了解详细的关于人员管理的情况,请参阅参考资料 [1])。在语法上等同的人员委派表达式的结果在同一个流程模型中是共享的。
在缺省的情况下,存储的用户 ID 的列表假定在一个小时内有效。然而,因为在此之后没有守护流程重新计算所有的人员委派表达式,所以它们只是在另一个请求访问同样的共享的职员查询表达式时才会重新计算。
要演示这种情况并理解这种实现方法的含义,考虑下面的情形:
业务流程模板 PT 定义了一个人员活动 PT,并且带有部门 D3056 中所有人的角色“潜在的拥有者”的人员委派表达式。
如果模板 PT 的流程实例第一次启动,并且 AT 的活动实例准备就绪,则人员表达式被第一次求值,并返回一个用户 ID 的列表。这个列表存储在 RETRIEVED_USER_T 表中,并且在 WORK_ITEM 视图中产生相应的条目;每个返回的用户都有一行,并与活动实例和潜在的拥有者的原因相关联。
如果第二个 PT 的流程实例在第一个流程启动的一个小时之内启动,并且 AT 的实例准备就绪,则人员表达式不再重新计算,因为它已经被缓存并且还是有效的。工作项目指向为第一个流程实例创建的同一个 RETRIEVED_USER_T 条目,WORK_ITEM 视图有每个用户的工作项目。
如果在一个小时之后(缺省的到期时间),PT 模板的第三个流程实例启动,则职员表达式的结果已经到期,因而结果通过调用人员目录重新计算。
在新的结果存储在 RETRIEVED_USER_T 表中之后,这三个实例(如果还在运行的话)的所有工作项目都指向已刷新的结果。
这个例子显示,重新计算人员表达式结果(计入成员目录的变化)取决于共享相同的人员委派表达式的活动。如果没有这样的活动准备就绪,或者如果没有共享的人员委派表达式,任何人员目录的变化不会生效,即便到期的时间已经超过了。
如果未一个业务流程中的多个活动定义了相同的人员委派表达式,就应用相同的缓存,例如,存在两个活动,而相同的人员组被允许处理一定的操作。
9. queryProcessTemplates()
本文中描述的 query()API 调用设计成能查询工作项目和相关的实例对象(比如流程实例和人员活动实例)。
为了获取关于业务流程模板的信息,ProcessChoreographer 提供了 queryProcessTemplates()。
之所以为业务流程模板提供附加的 API 调用,是因为没有未模板数据创建工作项目。因而,对业务模板(主要是 REASON_POTENTIAL_STARTER)的访问权限的求值是用不同的方法计算的,并且没有与 WORK_ITEM 视图的联合操作。
由于这种限制,queryProcessTemplates()API 调用更简单,因为它不需要 select 从句,并且它返回整个的 ProcessTemplateDataAPI 对象。
而 where 和 order-by 从句、阈值和时区用与 query()API 调用相同的方法进行处理。
关于作者  | |  |
本白皮书的作者在 WebSphere Application Server Enterprise Process Choreographer
开发小组工作。 |
对本文的评价
|