级别: 初级 Rania Khalaf (rkhalaf@watson.ibm.com), 软件工程师, IBM TJ Watson Research Center William A. Nagy (nagy@watson.ibm.com), 软件工程师, IBM TJ Watson Research Center
2003 年 4 月 01 日 我们在前一篇文章中研究了 BPEL4WS 中的相关性和故障处理。现在,我们将扩展前几篇文章中一直讨论的简单 BPEL4WS 流程,使它能够和一个已经存在的流程实例进行通信并能够捕获自身执行过程中可能发生的故障。
引言
我们已在本系列的
前一篇专栏文章中解释了 BPEL4WS 中的相关性和故障处理,现在我们将把这些功能添加到以前所讨论的贷款批准示例中以阐明它们的使用。在如下所示的情景中,若一位客户的贷款请求被批准,他就可能发出一个请求以真正获取该贷款。相关性可用于确保将第二个请求发送至正确的流程实例。而添加故障处理则可以捕获一个被显式抛出的错误,该错误表明客户端正试图获取大于被批准数额的贷款。如果捕获到这样一个故障,流程将反馈一个描述该问题的应答。我们还会展示使用了嵌套的作用域的错误处理的效果和它在完成错误处理的作用域传出的链接上的效果,以及无法处自身理错误的作用域不得不将它重新抛给它的上一级作用域的效果。
获取贷款:添加相关性
为了演示相关性(以及下一节中的故障处理),我们将扩展我们的贷款处理示例,使得客户端能够提交一个请求以实际获取所批准的贷款。我们将增加一个
<receive> 活动来接收要获取贷款的请求,并增加一个
<reply> 活动以确认收到该请求。这样得到的流如
图 1所示。
我们现在需要一种方法来确保要求获取贷款的请求到达了最初提供贷款批准的同一个流程实例中 - 这是相关性的用武之地。为了使消息相互关联,我们需要定义一个
<correlationSet> 。而为了定义
<correlationSet> ,我们就要知道要和哪些 WSDL 属性相关联,因而我们首先要做的事就是定义各种
<property> 。在我们的案例中,我们将假设把请求者的姓和名作为一个流程实例的唯一键,因此我们将创建 applicantFirstName 和 applicantLastName 两个基于上述假设的
<property> 。然后,我们需要为这两个
<property> 定义
<propertyAlias> ,这样我们就能从所传输的消息中提取必要的值。我们需要在第二个
<receive> 活动被激活前初始化相关集,而我们可能认为在任何一个
<invoke> 活动、第一个
<receive> 活动或第一个
<reply> 活动中都可以初始化相关集。但由于批准消息中不包含正确的信息,因此我们可以排除在第一个
<reply> 活动中初始化相关集。并且我们一般不会在两个
<invoke> 活动中完成初始化,因而第一个
<receive> 活动将成为初始化相关集的最佳地点。巧合的是,由于两个
<receive> 活动的输入是相同的 WSDL 消息,因此我们只需要定义两个
<propertyAlias> ,其中每个都与一个
<property> 相对应。以上定义如
清单 1所示。
清单 1. propertyAlias 的使用
<bpws:property name="applicantFirstName" type="xsd:string"/>
<bpws:property name="applicantLastName" type="xsd:string"/>
<bpws:propertyAlias propertyName="lns:applicantFirstName"
messageType=
"loandef:creditInformationMessage" part="firstName" query=
"/firstName"/>
<bpws:propertyAlias propertyName="lns:applicantLastName"
messageType=
"loandef:creditInformationMessage" part="name" query="/name"/>
...
<portType name="loanApprovalPT">
<operation name="obtain">
<input message="loandef:creditInformationMessage"/>
<output message="apns:approvalMessage"/>
</operation>
</portType>
<slnk:serviceLinkType name="loanApprovalLinkType">
<slnk:role name="approver">
<portType name="apns:loanApprovalPT"/>
<portType name="lns:loanApprovalPT"/>
</slnk:role>
</slnk:serviceLinkType>
|
一旦我们在 WSDL 文件中定义了
<property> 之后,我们就可以在 BPEL 文档中定义
<correlationSet> 并设置它的初始化(的过程)和用法。我们将增加一个新的名为
loanIdentifier 的
<correlationSet> ,然后将
<correlationSet> 添加到第一个
<receive> 活动中并将
initiation 属性放入
<correlationSet> 中(将它的值设为“yes”)以指出我们想在那里初始化相关集,同时将
<correlationSet> 添加到第二个
<receive> 活动中,这样就可以用它来确认将消息路由到正确的实例中。以上定义如
清单 2所示。
清单 2. correlationSet
<containers>
<container name="request" messageType="loandef:creditInformationMessage"/>
<container name=
"acceptanceRequest" messageType="loandef:creditInformationMessage"/>
<container name="riskAssessment" messageType="asns:riskAssessmentMessage"/>
<container name="approvalInfo" messageType="apns:approvalMessage"/>
<container name="error" messageType="loandef:loanRequestErrorMessage"/>
</containers>
<correlationSets>
<correlationSet name="loanIdentifier" properties=
"lns:applicantFirstName lns:applicantLastName"/>
</correlationSets>
...
<reply name="initial-reply" partner="customer"
portType="apns:loanApprovalPT" operation="approve" container="approvalInfo">
<target linkName="setMessage-to-reply"/>
<target linkName="approval-to-initial-reply"/>
<source linkName="reply-to-receive"
transitionCondition=
"bpws:getContainerData('approvalInfo', 'accept')='yes'"/>
</reply>
<receive name="acceptance-receive"
partner="customer" portType=
"lns:loanApprovalPT" operation=
"obtain" container="acceptanceRequest">
<target linkName="reply-to-receive"/>
<source linkName="receive-to-grant"/>
<correlations>
<correlation set="loanIdentifier"/>
</correlations>
</receive>
<reply name="grant-reply" partner="customer"
portType="lns:loanApprovalPT" operation=
"obtain" container="approvalInfo">
<target linkName="receive-to-grant"/>
</reply>
|
以上是我们为了在示例中启用相关性所需要做的所有事。现在,无论针对第二个
<receive> 活动的消息何时抵达,都可以提取
applicantFirstName 和
applicantLastName 属性的值并将它们的值与存储在流程中的所有实例中的相应值进行比较,然后将消息路由到包含匹配值的实例中。
错误通知:使用故障处理程序
为了演示一个有趣的故障处理示例,我们将引入一个带有一个传出链接的嵌套的作用域。该作用域将包装前面添加的 receive/reply/throw 活动。由于一个作用域可能只包含一个活动,因此我们首先将这三个活动包装在一个
<flow> 活动中。接着再添加一条从该作用域到一个
<empty> 活动的链接,其目的是为了演示故障是否被捕获以及在哪个作用域级别上被捕获对链接的影响。
最后,我们在新的作用域中添加一个故障处理程序。其原理是如果客户端试图获取一笔大于被批准数额的贷款,那就应该通知他们。这可以用一个故障处理程序来实现它,该程序带有一个能将错误消息放入容器中的
<assign> 活动,以及一个能将那个错误消息反馈给客户的
<reply> 活动。这两个活动将被封装在一个
<sequence> 活动中以使它们依次发生。这样得到的流如
图 2所示。
在
清单 3中的 BPEL 文件中添加了这个故障处理程序,且新增的代码是蓝色的,而已存在的 receive/reply/throw 活动的代码是黑色的。您还需要添加如上所述的链接(
scope-to-empty)的定义以及其他定义。请注意在故障处理程序中,我们会捕获与在 throw 活动中声明的故障相同的故障。我们可以指定故障容器来存放错误消息。然而,错误消息(在 WSDL 中)由一个包含错误代码的整数组成。而我们却更愿意将一个有意义的字符串发送给客户。因此,我们可以重用下面
<assign> 活动中所示的
approvalInfo 容器。
清单 3. BPEL 文件
<scope name="new-scope">
<source linkName="scope-to-empty"/>
<faultHandlers>
<catch faultName="lns:loanProcessFault" faultContainer="error">
<sequence name="fault-sequence">
<assign>
<copy>
<from expression="'invalid request: amount too high'"/>
<to container="approvalInfo" part="accept"/>
</copy>
</assign>
<reply partner="customer" portType="lns:loanApprovalPT"
operation="obtain" container="approvalInfo" faultName=
"lns:loanProcessFault"/>
</sequence>
</catch>
</faultHandlers>
<flow name="inner-flow">
<receive name="acceptance-receive" partner="customer"
portType="lns:loanApprovalPT" operation="obtain" container=
"acceptanceRequest"> ... </receive>
<reply name="grant-reply" partner="customer"
portType="lns:loanApprovalPT" operation="obtain" container=
"approvalInfo"> ... </reply>
<throw name="grant-failure" faultName="lns:loanProcessFault"> ... </throw>
</flow>
</scope>
<empty name="the-last-one">
<target linkName="scope-to-empty"/>
</empty>
|
现在让我们考虑一旦运行这个故障处理程序应该发生什么。假设一位客户端请求了 200 美元的贷款并获得批准。“new-scope”在发送了批准消息后启动,使它的故障处理程序做好准备,并启动其内部的流。流程激活 receive 活动并等待要获取贷款的客户端。客户端随后请求获取贷款,但所提交的数额却是 400 美元。从
<receive> 活动到
<reply> 活动的链接变为否定,并且
<reply> 活动因为受到连接失败的影响而遭禁用,从
<receive> 活动到
<throw> 活动的链接变为肯定并抛出故障。
一旦故障被抛出,作用域内所有的活动都不得不停止。在这种情况下任何活动都无法运行。然后内层作用域获取故障并检查自身是否有可以处理该故障的处理程序。如果有,就激活这个处理程序并运行一个简短的序列,该序列把“invalid request: amount too high.”应答发送给客户端。这样处理程序完成后,作用域也就
正常完成:从作用域到 empty 活动的链接被评估为 true ,因而 empty 活动得以运行。流程就此完成。
请花一些时间考虑故障处理程序实际上是在流程级别上而不是在内层作用域中的情况。那又会发生什么呢?内层作用域将获取故障、停止它所有的活动并在发现自身无法处理该故障后将它重新抛至它的父代作用域,后者在本案例中实际上就是流程。现在故障由内层作用域产生,并且它到 empty 活动的链接变为否定。
<empty> 活动因此不会运行。流程将捕获这个故障,在发送和上面一样的应答后正常结束。
运行流程
和我们以前的文章一样,如果您想要运行这个流程,您需要下载并安装 BPWS4J 引擎(您可以从 alphaWorks 上获得它,请参阅
参考资料)。您也可以参阅
参考资料部分以下载包含文中所描述的代码示例的压缩(zip)文件。
由于示例使用了一个改进的客户端以访问附加的功能,因此在您调用流程之前需要对该客户端进行编译。请将客户端源代码(Customer.java)放在一个名为 samples/dwsample 的目录下,确保 BPWS4J 发行版的 reqlib 目录中的 soap.jar 在您的类路径中,然后进行编译。您可以使用 LoanApprovalSample.sh 或 LoanApprovalSample.cmd 来执行客户端,只要确保类装入程序能够找到 samples/dwsample/Customer.class(例如,如果您正在从父目录 samples/dwsample 中执行脚本,则可以在类路径中添加“.”。)
客户端的这个版本需要一个额外的参数,可以用"approve"以获得最初的贷款批准(它的功能和旧版本客户端的功能相同),也可以用“obtain”来获取已批准的贷款。由于我们的关联是基于姓和名的,因此请记住您需要在“approve”请求和“obtain”请求中都使用相同的值。
请注意我们的两个情景都使用相同的服务(即除了属于流程本身的 WSDL 文件以外的所有 WSDL 文件)和客户端来测试功能。我们已将对流程的修改分为两部分以使它更易于理解。然而,由于两个流程使用同一个名称,因此如果您运行了第一个情景,那么必须在取消其部署后才能运行第二个情景。
为了运行这些示例,您将需要遵循 BPWS4J 文档中的运行贷款批准示例的说明(并在需要时替换其中包含的文件),下面是用于每个情景的 BPEL 和 WSDL 文件的清单,以及一些关于如何使用客户端来测试功能的注释。
请记住,如果您想要了解幕后情况,您将需要编辑 webapps/bpws4j/WEB-INF/classes 目录中的 log4j.properties 文件,并将 log4j.rootLogger 设置为 DEBUG。
相关性示例
使用 loanapprovalcorr.bpel 文件和 loanapprovalcorr.wsdl 文件。
部署流程,并按照上面的描述运行客户端,首先请求批准一笔贷款,然后请求获取该贷款。请保持两个请求中参数(姓、名和数额)一致。当您试图获取贷款时,如果日志记录已经启用,您将看见类似于如下所示的内容(当然这个前提是您的贷款请求已经获得批准。):
DEBUG [correlation] Testing correlation set: loanIdentifier
DEBUG [correlation] Testing correlation information:
PropertyValue[name:{http://loans.org/wsdl/loan-approval}applicantFirstName
value:[JROMString: http://loans.org/wsdl/loan-approval:applicantFirstName: John]]
DEBUG [correlation] Testing correlation information:
PropertyValue[name:{http://loans.org/wsdl/loan-approval}applicantLastName
value:[JROMString: http://loans.org/wsdl/loan-approval:applicantLastName: Doe]]
DEBUG [correlation] Testing correlation information: Match found
|
如果您试图再次获取贷款(但不进行重新批准),您应该在日志中看见一些类似于如下所示的内容:
DEBUG [correlation] Testing correlation set: loanIdentifier
DEBUG [correlation] Testing correlation information:
PropertyValue[name:{http://loans.org/wsdl/loan-approval}applicantFirstName
value:[JROMString: http://loans.org/wsdl/loan-approval:applicantFirstName: John]]
DEBUG [correlation] Testing correlation information:
PropertyValue[name:{http://loans.org/wsdl/loan-approval}applicantLastName
value:[JROMString: http://loans.org/wsdl/loan-approval:applicantLastName: Doe]]
DEBUG [correlation] Testing correlation information: Match not found
INFO [process] An existing process instance was not found
WARN [process] Neither a flow instance, nor a valid
instance creation receive could be found
|
您将得到上述消息,这是因为 obtain 消息正试图访问一个已经终止的流程。
故障处理示例
使用 loanapprovalfault.bpel 文件和 loanapprovalfault.wsdl 文件。
部署流程(如果您已经运行过前一个流程,那么您首先要确保取消那个流程的部署),并运行客户端以发送一个贷款批准请求。一旦贷款获得批准,再次运行客户端,但这次尝试获取比已批准贷款数额更大的贷款。日志中应该记录下如下所示的内容,并且客户端应该得到一个由服务器端返回的异常:
DEBUG [flow] Event channel com.ibm.cs.bpws.runtime.events.EventChannel@5dd34bf7
is processing event Fault event named
{http://loans.org/wsdl/loan-approval}loanProcessFault
from source Activity grant-failure with parent named grant-failure and
enclosing scope named grant-failure
DEBUG [flow] Container error getting message null
DEBUG [flow] Found fault handller Activity fault-sequence with parent named
new-scope and enclosing scope named new-scope
DEBUG [base] Scope fault-sequence is running
DEBUG [flow] Starting sequence fault-sequence Activity
DEBUG [base] Sequence fault-sequence is running
DEBUG [flow] Sequence fault-sequence about to run null
DEBUG [base] Scope null is running
DEBUG [base] Assign null is running
DEBUG [flow] evaluating condition for link receive-to-grant
DEBUG [flow] about to
eval condition bpws:getContainerData('acceptanceRequest', 'amount')
<= bpws:getContainerData('request', 'amount')
DEBUG [flow] about to fire link event receive-to-grantfalse
DEBUG [flow] Event channel com.ibm.cs.bpws.runtime.events.EventChannel@5c984bf7
is processing event
com.ibm.cs.bpws.runtime.events.LinkEvent@43e68bea
DEBUG [flow] Process loanApprovalProcess running; waiting for event
DEBUG [bus] Processing request: [BPWSBus.BUSRequestAssign assignSrc=
com.ibm.cs.bpws.model.FromImpl@2c674be8
assignDest =com.ibm.cs.bpws.model.ToImpl@2f3ccbe8 activity =
Activity null with parent named
null and enclosing scope named null
clientData com.ibm.cs.bpws.runtime.AssignRT$AssignInfo@43078bea callback =
Activity loanApprovalProcess
with no parent and no enclosing scope
Ouch, the call failed:
Fault Code = SOAP-ENV:Client
Fault String = An error occurred while processing the message.
Fault =
[Attributes={}] [faultCode=SOAP-ENV:Client] [faultString=
An error occurred while processing the message.]
[faultActorURI=null] [DetailEntries=[(0)=
[name=accept] [type=class java.lang.String]
[value=invalid request: amount too high] [encodingStyleURI=
null]]] [FaultEntries=]
|
上述两个示例都要用到的其他 WSDL 文件包括 loanapprover.wsdl 文件、loanassessor.wsdl 文件和 loandefinitions.wsdl 文件。
下一次
在本系列的下一篇文章中,我们将看看 switch 活动和 pick 活动,并还将看看补偿。
参考资料
作者简介  | |  | Rania Khalaf 是 IBM T.J. Watson Research Center 的 Component Systems 小组的一名软件工程师。2001 年,她从 MIT 获得学士学位和工程硕士学位后加入 IBM。Rania 是 IBM BPEL4WS 引擎 BPWS4J 的创作者之一,您可以从 alphaWorks 获得 BPWS4J。您可以通过
rkhalaf@watson.ibm.com与 Rania 联系。
|
 | |  | William A. Nagy 是 IBM T.J. Watson Research Center 的 Component Systems 小组的一名软件工程师。他是 WS-Inspection 的创作者之一,也是 BPWS4J、Apache SOAP、WSTK 和 WSGW 的开发者之一。他拥有哥伦比亚大学(Columbia University)计算机科学专业的硕士学位。您可以通过
nagy@watson.ibm.com与 William 联系。
|
对本文的评价
|