Extending the Oracle BPEL Error Hospital with custom Java Actions

With patch set 10.1.3.3 of its SOA Suite Oracle introduced standard fault handling functionality for BPEL. This Error Hospital framework allows the definition of policies for handling runtime exceptions, like Remote Faults and Binding Faults, that may occur on invoke activities in BPEL processes. Remote Faults occur when the service that the BPEL process tries to invoke cannot be reached, e.g. in case of a network or server failure. Remote Faults can be retried. Binding Faults indicate a mismatch between service provider and service consumer. It makes no sense to retry these unless the mismatch is repaired.

Instead of modeling the error handling behavior for runtime exceptions on invoke activities in BPEL it is now possible to define policies for dealing with these. A policy defines actions for responding to specific faults. Actions may be conditional: if the test that is specified for a condition passes the action will be executed. Specifying a test is not mandatory; in that case the condition is treated as the catch all condition by the Error Hospital framework. Note that multiple conditions may be specified without test element. In that case however, the framework will execute only the last condition specified and simply disregards the other conditions. This does not mean that chaining of actions is impossible. We will demonstrate several capabilities for this in this blog post.

Fault policies

Consider the following Fault Policy that is used at one of our customers.

<?xml version="1.0" encoding="UTF-8"?>
<faultPolicy version="2.0.1" id="SabicDefaultPolicy"
             xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
             xmlns:xs="http://www.w3.org/2001/XMLSchema"
             xmlns="http://schemas.oracle.com/bpel/faultpolicy"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!-- This section describes fault conditions.
       Build more conditions with faultName, test and action -->
  <Conditions>
    <!-- Fault if wsdlRuntimeLocation is not reachable;
         remoteFault is retryable -->
    <faultName xmlns:bpelx="http://schemas.oracle.com/bpel/extension" name="bpelx:remoteFault">
      <condition>
        <action ref="ora-retry"/>
      </condition>
    </faultName>
    <!-- Bindingfaults are not retryable -->
    <faultName xmlns:bpelx="http://schemas.oracle.com/bpel/extension" name="bpelx:bindingFault">
       <condition>
         <action ref="send-notification"/>
       </condition>
    </faultName>
  </Conditions>
  <Actions>
     <!-- This action will attempt 8 retries at increasing intervals of 2, 4, 8, 16, 32, 64, 128, and 256 seconds. -->
     <Action id="ora-retry">
       <retry>
         <retryCount>8</retryCount>
         <retryInterval>2</retryInterval>
         <exponentialBackoff/>
         <retryFailureAction ref="send-notification"/>
       </retry>
     </Action>
     <!-- This action will cause a replay scope fault -->
     <Action id="ora-replay-scope">
        <replayScope/>
     </Action>
     <!-- This action will bubble up the fault -->
     <Action id="ora-rethrow-fault">
       <rethrowFault/>
     </Action>
     <!-- This action will mark the work item to be "pending recovery from console" -->
     <Action id="ora-human-intervention">
       <humanIntervention/>
     </Action>
     <!-- This action will cause the instance to terminate -->
     <Action id="ora-terminate">
       <abort/>
     </Action>
     <!-- Custom Java action that sends a notification to the technical support team;
          Then it will invoke the ora-human-intervention action. -->
    <Action id="send-notification">
      <javaAction className="nl.amis.soa.bpel.ext.util.SendMail"
                  defaultAction="ora-human-intervention"
                  propertySet="send-notification-properties"/>
    </Action>
  </Actions>
  <Properties>
    <propertySet name="send-notification-properties">
      <property name="from">emailaddress@somedomain.com</property>
      <property name="to">emailaddress@somedomain.com; otheremailaddress@somedomain.com</property>
      <property name="subject">BPM UAT:</property>
      <property name="text">Environment: UAT</property>
      <property name="host">smtp_host</property>
      <property name="port">smtp_port</property>
    </propertySet>
  </Properties>
</faultPolicy>

 

This fault policy specification is for both Runtime and Binding Faults. It lists all operations that are provided by Oracle out-of-the-box and adds a custom build Java action for sending e-mail notifications which we will discuss later. Using the following fault-bindings specification for a BPEL domain the policy is declared to be active for any partnerlink invocation in the domain.

<?xml version="1.0" encoding="UTF-8"?>
<faultPolicyBindings version="2.0.1" 
                     xmlns="http://schemas.oracle.com/bpel/faultpolicy" 
                     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <!-- Enabling this will cause all processes in this domain to use this fault policy -->
	<process faultPolicy="SabicDefaultPolicy"/>
	<partnerLink faultPolicy="SabicDefaultPolicy">
          <!--
	    <name>creditRatingService</name>
	    <portType xmlns:db="http://xmlns.oracle.com/pcbpel/adapter/db/insert/">db:insert_plt</portType>
	  -->
	</partnerLink>
</faultPolicyBindings>

 

Note: binding fault policies can be more fine-grained, e.g. on a per partnerlink basis or even for a specific porttype that is specified for a partnerlink service. Given the fault-bindings configuration presented here, the BPEL Error Hospital framework will now handle any Runtime and Binding faults for any BPEL process in the domain, taking precedence over any specification in the BPEL processes for handling runtimeFaults and bindingFaults.

Chaining actions in case of a runtime exception

According to our policy specification, in case of a Runtime Fault the system will first retry the faulted operation. It will retry up to a maximum of 8 times, gradually increasing the time between attempts. When the retry action does not resolve the problem the system executes the send-notification action. This sends an e-mail notification to the technical support team informing them about the issue. After sending the notification, the fault handling framework executes the “ora-human-intervention” action. The state of the BPEL process instance changes to “pending recovery” and a technical support representative can now access the BPEL Console and initiate further action from there. The chain of actions configured in the policy is shown in the following flowchart schema:

Extending the Oracle BPEL Error Hospital with custom Java Actions bpel runtime fault handling flowchart

 

Custom Java Actions

As shown in the policy specification, the BPEL Error Hospital allows for Java extensions that can be used to define additional actions. In this case, the customer requested the technical support team to be notified in case of a runtime exception. Since the fault-handling framework does not provide this functionality we created a Java action to achieve this. Implementing the IFaultRecoveryJavaClass interface ensures that the Java custom action is usable by the BPEL Error Hospital. The following code listing shows part of the custom Java action class (accessor-methods and some other methods not shown):

package nl.amis.bpel.ext.util;

import com.oracle.bpel.client.config.faultpolicy.IFaultRecoveryContext;
import com.oracle.bpel.client.config.faultpolicy.IFaultRecoveryJavaClass;

import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;


public class SendMail implements IFaultRecoveryJavaClass {
    private String from;
    private String to;
    private String subject;
    private String text;
    private String host;
    private String port;
    private Properties props;

    public SendMail() {
    }

    private void send() {
        Session mailSession = Session.getDefaultInstance(props);
        Message simpleMessage = new MimeMessage(mailSession);

        try {
            InternetAddress fromAddress = new InternetAddress(from);
            simpleMessage.setFrom(fromAddress);
            String[] toAddresses = to.split(";");
            if (toAddresses != null && toAddresses.length > 0) {
                InternetAddress[] toInternetAddresses = 
                    new InternetAddress[toAddresses.length];
                for (int i = 0; i < toAddresses.length; i++) {
                    toInternetAddresses[i] = 
                            new InternetAddress(toAddresses[i]);
                }
                simpleMessage.setRecipients(RecipientType.TO, 
                                            toInternetAddresses);
            }
            simpleMessage.setSubject(subject);
            simpleMessage.setText(text);
            Transport.send(simpleMessage);
        } catch (AddressException e) {
            System.out.println("Error formatting Internet Email Address: " + 
                               e.getMessage());
            e.printStackTrace();
        } catch (MessagingException e) {
            System.out.println("Error sending email: " + e.getMessage());
            e.printStackTrace();
        }

    }

    private String getParameterValue(ArrayList parameterList) {
        String value = null;
        if (parameterList != null && parameterList.size() > 0) {
            value = (String)parameterList.get(0);
        }
        return value;
    }

    /**
     * This method is called by the BPEL Error Hospital framework when this
     * action is selected as retrySuccessAction (with the retry option) or
     * when this action is selected as successor in the human intervention
     * screen in the BPEL Console.
     * 
     * @param iFaultRecoveryContext
     */
    public void handleRetrySuccess(IFaultRecoveryContext iFaultRecoveryContext) {
        Map properties = iFaultRecoveryContext.getProperties();
        if (properties != null && properties.size() == 6) {
            setFrom(getParameterValue((ArrayList)properties.get("from")));
            setTo(getParameterValue((ArrayList)properties.get("to")));
            setSubject(getParameterValue((ArrayList)properties.get("subject")) + 
                       " " + "Retry Success");
            setText("The exception that occurred when processing " + 
                    iFaultRecoveryContext.getTitle() + 
                    " was successfully retried.\n" + 
                    "This message was automatically generated, please do not reply to it.");
            setHost(getParameterValue((ArrayList)properties.get("host")));
            setPort(getParameterValue((ArrayList)properties.get("port")));
            send();
        }
    }

    /**
     * This method is called by the BPEL Error Hospital framework when this
     * class is configured as action in the fault handling policy
     * 
     * @param iFaultRecoveryContext
     * @return String that can be used to influence choice for next action (not used in this case)
     */
    public String handleBPELFault(IFaultRecoveryContext iFaultRecoveryContext) {
        Map properties = iFaultRecoveryContext.getProperties();
        if (properties != null && properties.size() == 6) {
            setFrom(getParameterValue((ArrayList)properties.get("from")));
            setTo(getParameterValue((ArrayList)properties.get("to")));
            setSubject(getParameterValue((ArrayList)properties.get("subject")) + 
                       iFaultRecoveryContext.getTitle());
            setText(getParameterValue((ArrayList)properties.get("text")) + 
                    "\n" + "BPEL Process Instance: " + 
                    iFaultRecoveryContext.getInstanceId() + 
                    " needs intervention to recover from a technical exception: " + 
                    iFaultRecoveryContext.getFault().getMessage() + ".\n" + 
                    "Check the Activities tab in the BPEL Management Console in order to resolve the error as soon as possible.\n" + 
                    "This message was automatically generated, please do not reply to it.");
            setHost(getParameterValue((ArrayList)properties.get("host")));
            setPort(getParameterValue((ArrayList)properties.get("port")));
            send();
        }
        return null;
    }
}

The framework executes method “handleBPELFault” to perform the action that is defined in the policy. Method “handleRetrySuccess” is executed when the technical team member resolving the problem selects to perform the send-notification action when the retry action was successful. This option is shown in the following screenshot:

Extending the Oracle BPEL Error Hospital with custom Java Actions on retry success

The IFaultRecoveryContext parameter that is passed by the framework in both methods provides a wealth of information with respect to the faulted BPEL instance and regarding the cause of the error. For example:

  • getFault(): returns the BPELFault object for obtaining fault details, like getFault().getFaultName() or  getFault().getMessage()
  • getInstanceId(): gets the process id of the faulted instance. Alternatively, use getProcessId() for more detailed data for process identification
  • getLocator(): returns the Locator object via which the BPEL Client API can be accessed in order to do further processing on the domain
  • getPolicyId(): to identify the fault policy
  • getProperties(): returns a Java Map
    ob
    ject with properties that are passed on to the Java class as defined in the policy
  • getTitle(): alternative identification of the faulted process in the format “Instance #<instandeId> of <ProcessName>”
  • getVariableData(): the equivalent of bpws:getVariableData() that is used when programming BPEL processes; gives access to any data in the BPEL process

As may have been clear from the fault policy specification that was shown earlier, it is possible to pass parameters to the custom Java class using the <Properties> element which is bound to the Java action using the propertySet attribute on the <javaAction> element. The properties are passed on as a Java Map with values encapsulated as ArrayList objects.

Note that it is also possible to pass back a return value from the handleBPELFault method. The <javaAction> element in the policy specification may be used for specifying further action-chaining based on value returned by the handleBPELFault method. For example:

    <Action id="send-notification">
      <javaAction className="nl.amis.soa.bpel.ext.util.SendMail"
                  defaultAction="ora-human-intervention"
                  propertySet="send-notification-properties">
        <returnValue value="X" ref="ora-terminate"/>
        <returnValue value="Y" ref="some_other_java_action"/>
      </javaAction>
    </Action>

 

Programmatically accessing activities with “pending recovery” status

Finally, Oracle extended the BPEL client API in order to retrieve activities that are in “pending recovery” state. The following snippet of code will provide you with a list of activities that are in pending recovery state:

  Locator locator = getLocator();
  WhereCondition where = 
    WhereConditionHelper.whereActivitiesPendingRecovery();
  where.prependOrderBy("wi_modify_date desc");
  IActivityHandle[] activityHandles = locator.listActivities(where);

 

The resulting list matches the activities that are shown in the BPEL Console Activities tag for which the Fault Name column lists either remoteFault or bindingFault.

Future wishes

The BPEL Error Hospital that Oracle introduced with SOA Suite 10.1.3.3 works fine. When applied in the way described here it will prevent you from having to model the handling of Binding Faults or Runtime Faults in BPEL processes. It still leaves a lot to be desired for future releases though. To start with, the following would be great:

  • A default notification option in the BPEL Error Hospital that no longer requires Java coding
  • That is capable of using the Identity service for obtaining e-mail addresses
  • Updated documentation for using the Error Hospital and matching JavaDoc for extending it

Perhaps in the future we will also be able to specify policies for handling custom faults that may occur anywhere in the BPEL process, i.e. not only on invoke activities.

3 Comments

  1. Dan March 5, 2010
  2. Thyagu November 10, 2009
  3. Ashish April 30, 2008