Suppress join failure in WS-BPEL 2.0 with WebSphere Integration Developer V7
Z Jacek Laskowski - Wiki Projektanta Java EE
While preparing for the IBM Certified Solution Developer - WebSphere Integration Developer V6.2 certification I came across an area I've lately been getting into - the parallel activity. I described its main points in Running activities concurrently with flow (parallel) activity in WS-BPEL 2.0 on my blog.
There's been one thing I truly missed while delving into the detail of the parallel activity - BPEL standard fault joinFailure's explanation.
While looking around for explanation of its aim and use, I found the article Join behavior in activities in Training resources for the certification. Its explanation was as flat as "When an activity is nested within a parallel activity, and the link conditions that lead to an activity conflict with those of its join condition, then a fault is thrown on that activity." It just stirred my wish even more to really find out what it was for.
As it turned out, "conflict" (from the above quote) means "is false" and I'd claim it's not very helpful for readers (unless they know what it is before they turned their attention to reading the article - a sort of a paradox).
Hoping to get an answer or some pointers to look at I wrote an email to the WebSphere Integration Developer developerWorks forum - How to cause "join failure" in parallel activity. It looks like it's gonna be me who respond to my own question :-)
Web Services Business Process Execution Language Version 2.0
I remember it was Neil Kolban who, in his reply to Using Parallel activity in the BPEL, lead me to the WS-BPEL 2.0 specification with his mentioning "Parallel Activity (BPEL Flow Activity)", especially the use of the term BPEL. It was clear I should've been looked at the spec for more.
That's how I made it to reading the Web Services Business Process Execution Language Version 2.0 specification and I can hardly understand why I had not done it long before while working with IBM WebSphere Process Server and IBM WebSphere Integration Developer.
I begun reading from the very first place I could find suppressJoinFailure and here are the excerpts I found the most informative.
11.6. Parallel and Control Dependencies Processing – Flow says:
"One of the optional standard-attributes on every activity, suppressJoinFailure, is related to links. This attribute indicates whether a join fault (bpel:joinFailure) should be suppressed if it occurs (see section 11.6.3. Dead-Path-Elimination). When the suppressJoinFailure attribute is not specified for an activity, it inherits its value from its closest enclosing construct (i.e. activity or the process itself).
The semantics of <joinCondition>, <transitionCondition>, and suppressJoinFailure are discussed below in section 11.6.2. Link Semantics."
In 11.6.2. Link Semantics:
"Every activity that is the target of a link has an implicit or explicit join condition associated with it. This applies even when an activity has just one incoming link. Explicit join conditions are provided by the <joinCondition> element under the <targets> element. If the explicit join condition is missing, the implicit condition requires the status of at least one incoming link to be true (see below for an explanation of link status). A join condition is a Boolean expression (see section 8.3.1. Boolean Expressions). [SA00073] The expression for a join condition MUST be constructed using only Boolean operators and the activity's incoming links' status values.
If an activity that is ready to start in this sense has incoming links, then it MUST NOT start until the status of all its incoming links has been determined and the, implicit or explicit, join condition has been evaluated. In order to avoid violating control dependencies, evaluation of the join condition is performed only after the status of all incoming links has been determined.
The link status is a tri-state flag associated with each declared link. This flag may be in the following three states: true, false, or unset.
Each time a <flow> activity is activated, the status of all the links declared in that activity is unset.
When activity A completes without propagating any fault, the following steps MUST be performed to determine the effect of the links on other activities:
- Determine the status of all outgoing links for A. The status will be either true or false. To determine the status for each link its <transitionCondition> is evaluated. If some of the variables referenced by the <transitionCondition> are modified in a concurrent path, the result of the transition condition evaluation may depend non-deterministically on the timing of behavior among concurrent activities.
- For each activity B that has a synchronization dependency on A, check whether:
- B is ready to start (except for its dependency on incoming links) in the sense described above.
- The status of all incoming links for B has been determined. Note that if the incoming link is leaving an isolated scope, then the final status of the link cannot be known until the isolated scope has completed (see section 12.8. Isolated Scopes).
If both of the above conditions are true, then evaluate the <joinCondition> for B, if it evaluates to true, activity B is started. Otherwise a standard bpel:joinFailure fault MUST be thrown, unless the value of suppressJoinFailure is yes in which case bpel:joinFailure is not thrown (see section 11.6.3. Dead-Path-Elimination)."
In 11.6.3. Dead-Path-Elimination:
"When the control flow is defined by links and the value of the suppressJoinFailure attribute is yes, the interpretation of a join condition for activity A that evaluates to false is that A MUST NOT be executed. In this case, the fault bpel:joinFailure MUST NOT be generated. The value of this attribute is inherited by all nested activities, except where overridden by another suppressJoinFailure attribute setting.
When a target activity is not performed due to the value of the <joinCondition> (implicit or explicit) being false, its outgoing links MUST be assigned a false status according to the rules of section 11.6.2. Link Semantics. This has the effect of propagating false link status transitively along entire paths formed by successive links until a join condition is reached that evaluates to true. This approach is called Dead-Path Elimination (DPE).
The default value of the suppressJoinFailure attribute of the <process> element is no. This avoids suppressing a well-defined fault by a default setting.
If universal suppression of the bpel:joinFailure fault is desired, it can be achieved by setting the suppressJoinFailure attribute to yes in the <process> element."
I found Dealing with faults in your business process as informative as the WS-BPEL 2.0 specification itself where I found out about the standard fault types of WS-BPEL 2.0 with the joinFailure being one of them. That's where it became clear when it's thrown - "Thrown when the join condition of an activity evaluates to false." and how to avoid (suppress) its passing to a client - "You can suppress the fault by setting the process or activity attribute suppressJoinFailure to yes." That was it!
Business process model with parallel activity
Heaving read the theory I changed the business process model I've been working as follows:
The business process is an implementation of a reply-response (two-way) operation - it begins with the Receive activity and ends with Reply activity. What's interesting, as far as the joinFailure is concerned, is the use of the parallel activity with 3 activities inside. The Outside snippet activity is to show the behavior of turning the Suppress join failure on and off.
The BPEL for the process is as follows.
<?xml version="1.0" encoding="UTF-8"?> <bpws:process xmlns:bpws="http://schemas.xmlsoap.org/ws/2004/03/business-process/" xmlns:ns="http://GeneralizedFlowBP/JoinFailureArtifacts" xmlns:ns0="http://GeneralizedFlowBP/X" xmlns:wpc="http://www.ibm.com/xmlns/prod/websphere/business-process/6.0.0/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.ibm.com/xmlns/prod/websphere/business-process/expression-lang/java/6.0.0/" name="JoinFailure" suppressJoinFailure="yes" targetNamespace="http://GeneralizedFlowBP" wpc:continueOnError="no" wpc:displayName="JoinFailure" wpc:executionMode="longRunning" wpc:id="1" wpc:validFrom="2011-03-20T16:19:01" wpc:version="IBM_7.0.300"> <bpws:import importType="http://schemas.xmlsoap.org/wsdl/" location="X.wsdl" namespace="http://GeneralizedFlowBP/X" /> <bpws:import importType="http://schemas.xmlsoap.org/wsdl/" location="JoinFailureArtifacts.wsdl" namespace="http://GeneralizedFlowBP/JoinFailureArtifacts" /> <bpws:partnerLinks> <bpws:partnerLink myRole="Interface" name="X" partnerLinkType="ns:XPLT" wpc:id="2" /> </bpws:partnerLinks> <bpws:variables> <bpws:variable name="input1" type="xsd:string" wpc:id="6" /> <bpws:variable name="output1" type="xsd:string" wpc:id="7" /> </bpws:variables> <bpws:sequence name="HiddenSequence" wpc:id="1073741827"> <bpws:receive createInstance="yes" name="Receive" operation="operation1" partnerLink="X" portType="ns0:X" wpc:displayName="Receive" wpc:id="4" wpc:transactionalBehavior="commitAfter"> <wpc:output> <wpc:parameter name="input1" variable="input1" /> </wpc:output> </bpws:receive> <bpws:flow name="ParallelActivities" wpc:displayName="ParallelActivities" wpc:id="8"> <bpws:links> <bpws:link name="Link" wpc:displayName="Link" wpc:id="23" /> <bpws:link name="Link1" wpc:displayName="Link1" wpc:id="25" /> </bpws:links> <bpws:empty name="EmptyAction" wpc:displayName="EmptyAction" wpc:id="21"> <bpws:sources> <bpws:source linkName="Link" /> </bpws:sources> </bpws:empty> <bpws:empty name="EmptyAction1" wpc:displayName="EmptyAction1" wpc:id="22"> <bpws:targets> <bpws:joinCondition expressionLanguage="http://www.ibm.com/xmlns/prod/websphere/business-process/expression-lang/built-in/6.0.0/"> <wpc:false /> </bpws:joinCondition> <bpws:target linkName="Link" /> </bpws:targets> <bpws:sources> <bpws:source linkName="Link1" /> </bpws:sources> </bpws:empty> <bpws:invoke name="Snippet1" operation="null" partnerLink="null" portType="wpc:null" wpc:continueOnError="inherit" wpc:displayName="Inside" wpc:id="24"> <wpc:script> <wpc:javaCode><![CDATA[System.out.println(">>> Inside activity executed");]]></wpc:javaCode> </wpc:script> <bpws:targets> <bpws:target linkName="Link1" /> </bpws:targets> </bpws:invoke> </bpws:flow> <bpws:invoke name="Snippet" operation="null" partnerLink="null" portType="wpc:null" wpc:continueOnError="inherit" wpc:displayName="Outside" wpc:id="26"> <wpc:script> <wpc:javaCode><![CDATA[System.out.println(">>> Outside activity executed");]]></wpc:javaCode> </wpc:script> </bpws:invoke> <bpws:reply name="Reply" operation="operation1" partnerLink="X" portType="ns0:X" wpc:displayName="Reply" wpc:id="5"> <wpc:input> <wpc:parameter name="output1" variable="output1" /> </wpc:input> </bpws:reply> </bpws:sequence> </bpws:process>
As you can see the Suppress join failure is set to Yes - see the attribute suppressJoinFailure="yes" in the bpws:process element. It's easier to spot in the WebSphere Integration Developer, in the Properties > Defaults tab of the process.
It's also worth pointing out that the bpws:joinCondition element of the EmptyAction1 activity is wpc:false.
Summary :: Questions and answers
Question 1: Will the joinFailure be thrown and returned to a caller?
Since the setting suppressJoinFailure="yes" for the process one might answer no, but it may not be true and gets a bit complicated with the setting being available on each activity. The EmptyAction1 doesn't override the global (default) setting so it's inherited and no joinFault will get ever thrown.
Question 2: Does it matter whether Inside activity overrides the suppressJoinFailure? Will the joinFailure be thrown when it's no?
Yes, absolutely. As mentioned in 11.6.3. Dead-Path-Elimination in the WS-BPEL 2.0 specification:
"When the control flow is defined by links and the value of the suppressJoinFailure attribute is yes, the interpretation of a join condition for activity A that evaluates to false is that A MUST NOT be executed. In this case, the fault bpel:joinFailure MUST NOT be generated. The value of this attribute is inherited by all nested activities, except where overridden by another suppressJoinFailure attribute setting."
It's easy to test it out with the global suppressJoinFailure set to no and EmptyAction1's suppressJoinFailure to yes. It will let the processing go along the following link and once it arrives to the Inside snippet activity the rules to suppress or not a joinFailure will get calculated for the EmptyAction1.
Question 3: Will the Outside snippet activity ever be executed?
It depends on the suppressJoinFailure setting for the process and each activity in the parallel activity.
It will if the suppressJoinFailure setting is set to yes (for the process with no overrides or for every activity in the parallel activity).
It won't otherwise.
Question 4: How does WPS7 report the joinFailure? What exception's thrown?
During my tests, WID7's Integration Test Client reported
com.ibm.bpe.api.StandardFaultException: CWWBE0071E: A two-way request for port type 'X' and operation 'operation1' was accepted by activity 'Receive'. The process ended before a corresponding reply activity was executed. com.ibm.bpe.api.StandardFaultException: CWWBE0064E: The join condition of activity 'EmptyAction1' evaluated to false. at com.ibm.bpe.api.StandardFaultException.create(StandardFaultException.java:89) at com.ibm.bpe.engine.BpelActivityStateInactive.activateOrSkip(BpelActivityStateInactive.java:251) at com.ibm.bpe.engine.BpelEngineCore.continueControlLink(BpelEngineCore.java:291) at com.ibm.bpe.engine.BpelContinueLinkMessage3.execute(BpelContinueLinkMessage3.java:88) at com.ibm.bpe.engine.BpelEngine.onMessage(BpelEngine.java:1543) at com.ibm.bpe.framework.GenericAPIServicesImpl$7.run(GenericAPIServicesImpl.java:440)
That's how I learnt about CWWBE0064E: The join condition of activity {0} evaluated to false. message and the com.ibm.bpe.api.StandardFaultException.
The CWWBE0064E says:
"The join condition of an activity evaluated to false and the suppressJoinFailure attribute is false. This is a BPEL standard fault."
while the javadoc of com.ibm.bpe.api.StandardFaultException says:
"Super class for all BPEL standard faults such as bpws:forceTermination."
It looks like I found an explanation I was after. I hope I don't confuse others interested in the topic of joinFailure.


