
Business processes specified using BPEL will interact with their partners through operation invocations of web services. Web services are based on loosely coupled Service-Oriented Architecture (SOA). The communication between web services is done over Internet connections that may or may not be highly reliable. Web services could also raise faults due to logical errors and execution errors arising from defects in the infrastructure. Therefore, BPEL business processes will need to handle faults appropriately. BPEL processes may also need to signal faults themselves. Fault handling and signaling is an important aspect of business processes designed using BPEL.
Faults in BPEL can arise in various situations such as the following:
- When a BPEL process invokes a synchronous web service operation, the operation might return a WSDL fault message, which results in a BPEL fault.
- A BPEL process can explicitly signal (throw) a fault.
- A fault can be thrown automatically, for example, when a join failure has occurred. We will discuss join failures later in this chapter.
- The BPEL server might encounter error conditions in the runtime environment, network communications, or any other such reason. BPEL defines several standard faults; these are listed in Appendix A.
WSDL faults occur due to synchronous operation invocations on partner web services. In WSDL, such faults are denoted with the<fault>
element within the<operation>
declaration. In BPEL, WSDL faults are identified by the qualified name of the fault and the target namespace of the corresponding port type used in the operation declaration.
In the Synchronous Business Travel Process example in the previous chapter, we have used the TravelApproval
operation on the TravelApprovalPT
port type with input and output messages. This is shown in the WSDL excerpt as follows:
... <portType name="TravelApprovalPT"> <operation name="TravelApproval"> <input message="tns:TravelRequestMessage" /> <output message="aln:TravelResponseMessage" /> </operation> </portType> ...
To add fault information to the operation, we first need to define a corresponding message. For simplicity, this message will be of the xs:string
type:
... <message name="TravelFaultMessage"> <part name="error" type="xs:string" /> </message> ...
Now we will add the fault declaration to the operation signature shown previously:
...
<portType name="TravelApprovalPT">
<operation name="TravelApproval">
<input message="tns:TravelRequestMessage" />
<output message="aln:TravelResponseMessage" /> <fault name="fault" message="tns:TravelFaultMessage" />
</operation>
</portType>
...
WSDL does not require that we use unique fault names within the namespace used to define the operation. This implies that faults that have the same name and are defined within the same namespace will be considered as the same fault in BPEL. Keep this in mind when designing services that can potentially become partners of BPEL business processes, because this can lead to conflicts in fault handling during execution.
A business process may sometimes need to explicitly signal a fault. For such a situation, BPEL provides the<throw>
activity. It has the following syntax:
<throw faultName="name" />
BPEL does not require that we define fault names in advance, prior to their use in the<throw>
activity. This flexible approach can also be error-prone because there is no compile-time checking of fault names. Therefore, a typo could result in a situation where a misspelled fault might not be handled by the designated fault handler.
Faults can also have an associated variable that usually contains data related to the fault. If such a variable is associated with the fault, we need to specify it when throwing the fault. This is done by using the optional faultVariable
attribute as shown here:
<throw faultName="name" faultVariable="variable-name" />
The following example shows the most straightforward use of the<throw>
activity, where a WrongEmployeeName
fault is thrown—no variable is needed. Remember that fault names are not declared in advance:
<throw faultName="WrongEmployeeName" />
The faults raised with the<throw>
activity have to be handled in the BPEL process. Fault handling is covered later in this chapter. Faults that are not handled will not be automatically propagated to the client as is the case in modern programming languages (Java, for example). Rather, the BPEL process will terminate abnormally. Sometimes, however, we may want to signal faults to clients.
A BPEL process offers operations to its clients through the<receive>
activity. If the process wants to provide a synchronous request/response operation, it sends a<reply>
activity in response to the initial<receive>
. Remember that the type of the operation is defined in the WSDL document of the BPEL process. A synchronous request/response operation is defined as an operation that has an input and an output message and an optional fault message.
If such an operation has the fault part specified, we can use the<reply>
activity to return a fault instead of the output message. The syntax of the<reply>
activity in this case is as follows:
<reply partnerLink="partner-link-name"
portType="port-type-name"
operation="operation-name"
variable="variable-name" <!-- optional --> faultName="fault-name" >
</reply>
When we specify a fault name to be returned through the<reply>
activity, the variable name is optional. If we specify a variable name, then the variable has to be of the fault message type as defined in WSDL.
Let's modify the BPEL process definition in the synchronous travel example and signal the fault (TravelFaultMessage)
to the client by using the<reply>
activity.
First, we need to declare an additional variable that will hold the fault description to return to the client. The variable is of the TravelFaultMessage
type:
... <variables> ... <!-- fault to the BPEL client --> <variable name="TravelFault" messageType="trv:TravelFaultMessage"/> </variables> ...
Then we return the fault to the BPEL process client. We will need to check if something went wrong in the travel process. For the purpose of this example, we will check whether the selected flight ticket has been approved. This information is stored in the confirmationData
part of the TravelResponse
variable in the Approved
element (see the previous chapter for the complete schema definition). Note that this is an oversimplification but it demonstrates how to return faults. We can use an<if>
activity to determine whether the ticket is approved; then we construct the fault variable and use the<reply>
activity to return it to the client. This is shown in the following code:
... <!-- Check if the ticket is approved --> <if> <condition> $TravelResponse.confirmationData/aln:Approved='true' </condition> <!-- Send a response to the client --> <reply partnerLink="client" portType="trv:TravelApprovalPT" operation="TravelApproval" variable="TravelResponse"/> <else> <sequence> <!-- Create the TravelFault variable with fault description --> <assign> <copy> <from>string('Ticket not approved')</from> <to variable="TravelFault" part="error" /> </copy> </assign> <!-- Send a fault to the client --> <reply partnerLink="client" portType="trv:TravelApprovalPT" operation="TravelApproval" variable="TravelFault" faultName="fault" /> </sequence> </else> </if>
If the ticket is not approved, the following fault is signaled to the client:
<TravelFault> <part name="error"> <error xmlns="http://packtpub.com/bpel/travel/"> Ticket not approved </error> </part> </TravelFault>
We have seen that signaling faults in synchronous replies is easy. Let us now discuss signaling faults in asynchronous scenarios.
If an asynchronous BPEL process needs to notify the client about a fault, it cannot use the<reply>
activity. Remember that in asynchronous scenarios the client does not wait for the reply—rather the process uses a callback. To return a fault in callback scenarios, we usually define additional callback operations on the same port type. Through these callback operations, we can signal that an exceptional situation has prevented normal completion of the process.
To demonstrate how faults can be propagated to the client using a callback operation, we will use the asynchronous travel process example. First, we need to modify the travel BPEL process WSDL and introduce another operation called ClientCallbackFault
. This operation consists of an input message called tns:TravelFaultMessage
. The message is of the string
type (similar to the synchronous example). The declaration of the operation and the message is shown in the following code excerpt:
... <message name="TravelFaultMessage"> <part name="error" type="xs:string" /> </message> <portType name="ClientCallbackPT"> <operation name="ClientCallback"> <input message="aln:TravelResponseMessage" /> </operation> <operation name="ClientCallbackFault"> <input message="tns:TravelFaultMessage" /> </operation> </portType> ...
We can use the<if>
activity to determine whether the ticket has been approved, as in the synchronous example. If the ticket is not approved, however, we<invoke>
the ClientCallbackFault
operation instead of using the<reply>
activity to signal the fault to the client. This is shown in the following code excerpt:
... <!-- Check if the ticket is approved --> <if> <condition> $TravelResponse.confirmationData/aln:Approved='true' </condition> <!-- Make a callback to the client --> <invoke partnerLink="client" portType="trv:ClientCallbackPT" operation="ClientCallback" inputVariable="TravelResponse" /> <else> <sequence> <!-- Create the TravelFault variable with fault description --> <assign> <copy> <from>string('Ticket not approved')</from> <to variable="TravelFault" part="error" /> </copy> </assign> <!-- Send a fault to the client --> <invoke partnerLink="client" portType="trv:ClientCallbackPT" operation="ClientCallbackFault" inputVariable="TravelFault" /> </sequence> </else> </if>
In the next section, we will look at how to handle faults thrown in BPEL processes.
Now that we are familiar with how faults are signaled, let us consider how the business process handles faults. When a fault occurs within a business process (this can be a WSDL fault, a fault thrown by the BPEL process, or any other type of fault), it means that the process may not complete successfully. The process can complete successfully only if the fault is handled within a scope. Scopes are discussed in the next section.
A business process can handle a fault through one or more fault handlers. Within a fault handler, the business process defines custom activities that are used to recover from the fault and recover the partial (unsuccessful) work of the activity in which the fault has occurred.
The fault handlers are specified before the first activity of the BPEL process, after the partner links and variables. The overall structure is shown in the following code excerpt:
<process ...>
<partnerLinks>
...
</partnerLinks>
<variables>
...
</variables> <faultHandlers> <catch ... > <!-- Perform an activity --> </catch> <catch ... > <!-- Perform an activity --> </catch> ... <catchAll> <!-- catchAll is optional --> <!-- Perform an activity --> </catchAll> </faultHandlers>
<sequence>
...
</sequence>
</process>
We can see that within the fault handlers we specify several<catch>
activities where we indicate the fault that we would like to catch and handle. Within a fault handler, we have to specify at least one<catch>
or a<catchAll>
activity. Of course, the<catchAll>
activity can be specified only once within a fault handler.
Usually, we will specify several<catch>
activities to handle specific faults and use the<catchAll>
to handle all other faults. The<catch>
activity has two attributes, of which we have to specify at least one:
faultName:
Specifies the name of the fault to be handled.faultVariable:
Specifies the variable type used for fault data. Additionally, we can specify one of the following attributes (both are optional, but we may specify one, not both).faultMessageType:
Specifies the WSDL message type of the fault to be handled.faultElement:
Specifies the XML element type of the fault to be handled.
The flexibility of<catch>
activities is high and several variations are permissible. The most common are listed as follows:
<faultHandlers> <catch faultName="trv:TicketNotApproved" > <!-- First fault handler --> <!-- Perform an activity --> </catch> <catch faultName="trv:TicketNotApproved" faultVariable="TravelFault" > <!-- Second fault handler --> <!-- Perform an activity --> </catch> <catch faultVariable="TravelFault" > <!-- Third fault handler --> <!-- Perform an activity --> </catch> <catchAll> <!-- Perform an activity --> </catchAll> </faultHandlers>
We can see that fault handlers in BPEL are very similar to try/catch clauses found in modern programming languages.
Let us consider the fault handlers listed previously and discuss the scenarios for which the<catch>
activities will be selected:
- The first
<catch>
will be selected if thetrv:TicketNotApproved
fault has been thrown and the fault carries no fault data. - The second
<catch>
will be selected if thetrv:TicketNotApproved
fault has been thrown and carries data of type matching that of theTravelFault
variable. - The third
<catch>
will be selected if a fault has been thrown whose fault variable type matches theTravelFault
variable type and whose name is nottrv:TicketNotApproved.
- In all other cases, the
<catchAll>
will be selected.
We can see that the selection of the<catch>
activity within fault handlers is quite complicated. It may even happen that a fault matches several<catch>
activities. Therefore, BPEL specifies exact rules to select the fault handler that will process a fault:
- For faults without associated fault data, the fault name will be matched. The
<catch>
activity with a matchingfaultName
will be selected, if present; otherwise, the default<catchAll>
handler will be used, if present. - For faults with associated fault data, a
<catch>
activity specifying a matchingfaultName
value andfaultVariable
value will be selected, if present. Otherwise, a<catch>
activity with no specifiedfaultName
and a matchingfaultVariable
will be selected, if present. Otherwise, the default<catchAll>
handler will be used, if present.
If no<catch>
is selected and<catchAll>
is not present, the fault will be re-thrown to the immediately enclosing scope, if present. Otherwise, the process will terminate abnormally. This situation is similar to explicitly terminating the process using the<exit>
activity.
Let's go back to the synchronous BPEL travel process example to add a fault handlers section. We need to define a fault handler that will simply signal the fault to the client. In real-world scenarios, a fault handler can perform additional work to try to recover the work done by an activity or retry the activity itself.
To signal the fault to the client, we use the same TravelFaultMessage
message that we defined in the previous section. Here is an excerpt from the WSDL:
... <message name="TravelFaultMessage"> <part name="error" type="xs:string" /> </message> <portType name="TravelApprovalPT"> <operation name="TravelApproval"> <input message="tns:TravelRequestMessage" /> <output message="aln:TravelResponseMessage" /> <fault name="fault" message="tns:TravelFaultMessage" /> </operation> </portType> ...
We define a fault handler and add a<faultHandlers>
section immediately after the<variables>
definition and before the<sequence>
activity, as shown next. The fault handler for the trv:TicketNotApproved
fault is defined with the associated TravelFault
variable. This handler will use the<reply>
activity to signal the fault to the BPEL client. We will also provide a default<catchAll>
handler, which will first create a variable and then use the<reply>
activity to signal the fault to the client:
... <faultHandlers> <catch faultName="trv:TicketNotApproved" faultVariable="TravelFault"> <reply partnerLink="client" portType="trv:TravelApprovalPT" operation="TravelApproval" variable="TravelFault" faultName="fault" /> </catch> <catchAll> <sequence> <!-- Create the TravelFault variable --> <assign> <copy> <from>string('Other fault')</from> <to variable="TravelFault" part="error" /> </copy> </assign> <reply partnerLink="client" portType="trv:TravelApprovalPT" operation="TravelApproval" variable="TravelFault" faultName="fault" /> </sequence> </catchAll> </faultHandlers> ...
We also have to modify the process itself. Instead of replying to the client (<reply>)
in the<if>
activity if the ticket has not been approved, we will simply throw a fault, which will be caught by the corresponding fault handler. The fault handler will also catch other possible faults:
...
<!-- Check if the ticket is approved -->
<if>
<condition>
$TravelResponse.confirmationData/aln:Approved='true'
</condition>
<!-- Send a response to the client -->
<reply partnerLink="client"
portType="trv:TravelApprovalPT"
operation="TravelApproval"
variable="TravelResponse"/>
<else>
<sequence>
<!-- Create the TravelFault variable with fault description -->
<assign>
<copy>
<from>string('Ticket not approved')</from>
<to variable="TravelFault" part="error" />
</copy>
</assign> <!-- Throw fault --> <throw faultName="trv:TicketNotApproved" faultVariable="TravelFault" />
</sequence>
</else>
</if>
...
Faults that are not handled by the BPEL process result in abnormal termination of the process and are not propagated to the client. In other words, unhandled faults do not cross service boundaries unless explicitly specified using a<reply>
activity as we did in our example. This differentiates BPEL from Java and other languages where unhandled exceptions are propagated to the client.
In asynchronous BPEL processes, faults are handled in the same way as in synchronous processes by using<faultHandlers>
. We need to define a fault handler that, in our example, will simply forward the fault to the client. We cannot, however, use the<reply>
activity to signal the fault to the client. Instead, we need to define an additional callback operation and use the<invoke>
activity, as we did in our previous example. In this example, we will use the same fault callback operation as in the previous asynchronous example:
... <message name="TravelFaultMessage"> <part name="error" type="xs:string" /> </message> <portType name="ClientCallbackPT"> <operation name="ClientCallback"> <input message="aln:TravelResponseMessage" /> </operation> <operation name="ClientCallbackFault"> <input message="tns:TravelFaultMessage" /> </operation> </portType> ...
Now, we will define the<faultHandlers>
section. The difference to the synchronous example will be that we will use the<invoke>
activity to invoke the newly defined operation instead of the<reply>
activity to propagate the fault to the client:
... <faultHandlers> <catch faultName="trv:TicketNotApproved" faultVariable="TravelFault"> <!-- Make a callback to the client --> <invoke partnerLink="client" portType="trv:ClientCallbackPT" operation="ClientCallbackFault" inputVariable="TravelFault" /> </catch> <catchAll> <sequence> <!-- Create the TravelFault variable --> <assign> <copy> <from>string('Other fault')</from> <to variable="TravelFault" part="error" /> </copy> </assign> <invoke partnerLink="client" portType="trv:ClientCallbackPT" operation="ClientCallbackFault" inputVariable="TravelFault" /> </sequence> </catchAll> </faultHandlers> ...
Another important question related to fault handling is how the BPEL process can be notified of faults that occurred in asynchronously invoked partner web service operations. A typical example is the invocation of the American and Delta Airlines web services in our example. To invoke the operation, we used the<invoke>
activity and then a<receive>
activity to wait for the callback.
BPEL provides a way to wait for more than just one message (operation call) using the<pick>
activity, which is described later in this chapter in the Managing events section. By using<pick>
instead of<receive>
, our BPEL process can wait for several incoming messages. One of these can be a message for regular callback; others can be messages that signal fault conditions. With<pick>
, we can even specify a timeout for receiving a callback. For further information on these issues, please see the Managing events section.
In fault handlers, we might want to propagate a fault that we have caught to a higher-level fault handler. For example, a fault handler of a nested scope catches a fault. However, it is a type of fault that it will not handle. Rather, it will propagate it to the fault handler of a higher-level scope.
To achieve this, we can use the<rethrow>
activity. This is used to rethrow the fault caught by the fault handler.<rethrow>
can be used only within a fault handler (<catch>
and<catchAll>)
.
The syntax is simple:
<rethrow />
If a<catchAll>
fault handler for any fault is not defined for any given<scope>
, the BPEL engine implicitly creates a default fault handler. The default fault handler will compensate all inner scopes (compensation is covered later in this chapter) and rethrow the fault to the upper scope.
The default implicit fault handler looks like this:
<catchAll> <sequence> <compensate /> <rethrow /> </sequence> </catchAll>
The loosely coupled model of web services and the use of Internet connections for accessing them make the invocation of operations on web services particularly error prone. Numerous situations can prevent a BPEL process from successfully invoking a partner web service operation, such as broken connections, unavailability of web services, changes in the WSDL, and so on.
Such faults can be handled in the general<faultHandlers>
sections. However, a more efficient way is to handle faults related to the<invoke>
activity directly and not rely on the general fault handlers. The<invoke>
activity provides a shortcut to achieve this — inline fault handlers.
The syntax for inline fault handlers in the<invoke>
activity is similar to the syntax of the<faultHandlers>
section. As shown in the following code excerpt, we can specify zero or more<catch>
activities and we can also specify a<catchAll>
handler. The only difference is that in inline<catch>
activities, we have to specify a fault name. Optionally, we may specify the fault variable:
<invoke ... > <catch faultName="fault-name" > <!-- Perform an activity --> </catch> ... <catch faultName="fault-name" faultVariable="fault-variable" <!-- Perform an activity --> </catch> ... <catch faultName="fault-name" faultVariable="fault-variable" faultMessageType="WSDL-message" <!-- Optional one or the other --> faultElement="XML-element" > <!-- Perform an activity --> </catch> ... <catchAll> <!-- Perform an activity --> </catchAll> </invoke>
The following code excerpt shows an inline fault handler for invoking the Employee Travel Status web service from our BPEL travel process example. Please notice that this also requires modifying the Employee Travel Status WSDL and declaring an additional fault message for the operation. As this code is similar to what we did in previous examples, it is not repeated here again. The following code excerpt demonstrates inline fault handling. The rules for which the catch activity will be selected are the same as for stand-alone fault handlers and have been discussed in the previous section:
<invoke partnerLink="employeeTravelStatus" portType="emp:EmployeeTravelStatusPT" operation="EmployeeTravelStatus" input Variable="EmployeeTravelStatusRequest" outputVariable="EmployeeTravelStatusResponse" > <catch faultName="emp:WrongEmployeeName" > <!-- Perform an activity --> </catch> <catch faultName="emp:TravelNotAllowed" faultVariable="FaultDesc" > <!-- Perform an activity --> </catch> <catchAll> <!-- Perform an activity --> </catchAll> </invoke>
This brings us to the thought that it would be useful if we could specify more than one<faultHandlers>
section in a BPEL process. It would be great if we could specify different fault handlers sections for different parts of the process, particularly for complex processes. This is possible if we use scopes, described in the next section. We will see that inline fault handling of the<invoke>
activity is equal to enclosing the<invoke>
activity in a local scope.