Powered By Blogger

Tuesday, August 24, 2010

Using Email to initiate a BPEL Process

The notification service in Oracle BPEL Process Manager allows you to send a notification by email (as well as voice message, fax, pager, or SMS) from a BPEL process.

However another requirement is to be able to use the receipt of an email to initiate a BPEL process. This is thesubject of this blog, many thanks to Muruga Chinnananchi on whose original example this is based.Essentially we want to create a simple process EMailActivation which recieves an email sent to a particularemail address. To achive this there are two basic steps:
Configure the BPEL Server to be able to connect to the mail account.
Define the BPEL Process to be initiated on receipt of an email.
Configure Email Account:
To configure the email account which the BPEL Server should connect to, we need to place a MailAccount xml configuration file (in our example BpelMailAccount.xml) into the following directory:

    SOA_DOMAIN\bpel\domains\default\metadata\MailService
Note: You will need to create the metadata and MailService directories.
The file itself can have any name (though must end with .xml) as you can define multiple accounts. However make a note of the name as you will need it to link your BPEL Process to the actual mail account.

Here’s one sample file:
<mailaccount xmlns="http://services.oracle.com/bpel/mail/account">
   <userInfo>
     <displayName> any a/c display name </displayName>
     <organization> My Org </organization>
     <replyTo> replyToAddress@localhost </replyTo>
 </userInfo>
<outgoingServer>
  <protocol> smtp </protocol>
  <host > localhost </host >
  <authenticationRequired > false </authenticationRequired >
  <outgoingserver>

<incomingServer>
  <protocol> pop3 </protocol>
  <host > localhost </host >
  <email> bpel </email>
  <password> CRYPT{xdfg+Gs=}</password>
</incomingServer>
</mailAccount>


The outgoing SMTP service doesn’t need to be configured (as we use the notification service to send outgoing emails). However the incoming account is defined by the following tags:
    <incomingServer>
        <protocol>[protocol pop3 or imap]</protocol>
        <host>[imap or pop3 server]</host>
        <email>[imap or pop3 account]</email>
        <password>[imap or pop3 password]</password>
        <folderName>[imap only, inbox folder ]</folderName>
    </incomingServer>
Note: When defining the account name, be careful to use the actual account name not the email address as they are not always the same.


Creating BPEL Process:-

The first step is to use JDeveloper to create an Asynchronous process initiated by a request message with a payload containing an element of type mailMessage (defined in Mail.xsd installed as part of BPEL PM).
To do this use the BPEL Project Creation wizard to create a BPEL Process in the normal way. After entering the process name and specifying the process template to be asynchronous, select "Next".
This will take you to the next step in the wizard where you specify the Input and Output Schema Elements, click on the flash light for the input schema and select Mail.xsd (located in <SOA_HOME>\bpel\system\xmllib)

Specifying input/output schema elements leads to opening the type chooser window to select the element to use from the imported schema. Select the mailMessage element

Once the process has been created you can remove the callBackClient activity as we won’t need this.
Import Common Schema:

If you now try and compile your process, you will find it fails with an error message. Thus is because the Mail.xsd itself imports a schema (common.xsd), so you need to import this schema as well.

To import the Mail Schema into your BPEL Process, ensure the diagram view for your BPEL process is open and selected in JDeveloper. Then within the BPEL Structure window, right click on the Project Schemas node and select "Import Schemas"

Note: Once imported, manually update the WSDL file to ensure the import statements for both the Mail.xsd and common.xsd are contained within the same element or it will still fail to compile. See previous blog - Using Nested Schemas with BPEL for details.

Define Mail Activation Agent

The process itself is now ready for deployment. However we need to complete one final activity, which is to tie the BPEL Process to a mail activation agent for the Email account that we defined earlier.

The Activation Agent will poll the defined mail box for emails and then for each email it receives invoke an instance of the process to handle it.

To do this you need to add the following definition to the bpel.xml file, after the <partnerlinkbindings>element:

<activationAgents>

  <activationAgent className=”com.collaxa.cube.activation.mail.MailActivationAgent”
  heartBeatInterval=”60”>

  <property name=”accountName">BpelMailAccount</property>

  </activationAgent>

</activationAgents>

 
Where heartBeatInterval is how often we want to poll the email account for new emails, and the accountName corresponds to the name of the account configuration file we defined earlier.
Finally deploy the process and send an email to the appropriate account.
Note: If you modify the BPEL process in JDeveloper, the bpel.xml file may lose its changes (i.e. the activationAgent definition), and as a result the process will never get initiated - so always check the bpel.xml file is correctly defined just before deploying the process.

Email Server:

Install and configure Email server according to product instructions

Passing BPEL Variable contents into XSLT as Parameters

XSLT is executed using the XPath Extension Function ora:processXSLT. The well known two arguments for this extension function are as follows.

1) The XSL File Name
2) The source variable to be transformed [bpws:getVariableData(...)]

But there is one more argument that this XPath function can accept - 'properties'.
Note the signature of this function specified in xpath-functions.xml
Signature: ora:processXSLT('xsl template','input variable','properties'?).
These properties translate to XSL Parameters that can be accessed within the XSL map using the construct

<xsl:param name="paramName">

You can retrieve the value of this parameter within your XSLT in a way similar to the way used to extract data from XSL variables.

For e.g. <xsl:value-of select="$paramName"/>

The "properties" argument of the XPath function is expected to be an XML Element that has the following structure.
Illustrated below is an example of such a properties XML.

<parameters xmlns:ns2="http://schemas.oracle.com/service/bpel/common" xmlns="http://schemas.oracle.com/service/bpel/common">
<ns2:item>
<ns2:name>userName</ns2:name>
<ns2:value>ramkmeno</ns2:value>
</ns2:item>
<ns2:item>
<ns2:name>location</ns2:name>
<ns2:value>CA</ns2:value>
</ns2:item>
</parameters>

XSLTParameters.xsd

<?xml version="1.0" encoding="windows-1252" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.oracle.com/service/bpel/common" targetNamespace="http://schemas.oracle.com/service/bpel/common" elementFormDefault="qualified">
<xsd:element name="parameters">
<xsd:annotation>
<xsd:documentation> A sample element </xsd:documentation> </xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="item" maxOccurs="unbounded"> <xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="value" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Within the XSLT, the parameters are accessible through their names. [in this case, the parameter names are "userName" and "location", and their values are "ramkmeno" and "CA" respectively.

Approach in nutshell

1) Add XSLTParameters.xsd to BPEL project
2) Declare a variable (xslt_Variable) is of the abovementioned data type- parameters [see XML above.]
3) Populate the variable with the contents of the BPEL variable you wish to pass into the XSLT
4) Invoke processXSLT() with the XSL File, source variable, and the parameters variable.
5) Access the parameter contents within the XSLT

Example BPEL Snippet

<!--Step 1: initialize the parameters variable from whatever BPEL variable whose information you need to access from within XSLT -->
<assign name="Assign_PassProcessIterator">
<copy>
<from expression='"userName"'/> <!-- Name of xslt parameter-->
<to variable="xslt_Variable"
query="/ns7:parameters/ns7:item/ns7:name"/>
</copy>
<copy>
<from variable="Praveen"/> <! Value of xslt parameter-->
<to variable="xslt_Variable"
query="/ns7:parameters/ns7:item/ns7:value"/>
</copy> </assign>
<!--Step 2: Invoke the XSLT with the parameters as the third argument -->
<assign name="executeXSLT">
<bpelx:annotation>
<bpelx:pattern>transformation</bpelx:pattern>
</bpelx:annotation>
<copy>
<from expression="ora:processXSLT('TestXSLParams.xsl',
bpws:getVariableData('inputVariable','payload'),
bpws:getVariableData('xslt_Variable'))"/>
<to variable="outputVariable" part="payload"/>
</copy>
</assign>

XSLT Snippet

<xsl:stylesheet version="1.0" ....>
<xsl:param name="userName"/>
<xsl:template match="/">
<ns1:TestXSLParamsProcessResponse>
<ns1:result>
<xsl:value-of select="concat('User : ', $userName, ' Location : ',$location)"/>
</ns1:result>
</ns1:TestXSLParamsProcessResponse>
</xsl:template>
</xsl:stylesheet>

Tuesday, August 3, 2010

Automatic recovery program for pending BPEL call back messages

BPEL engine maintains all async call back messages into database table called dlv_message. You can see such all messages in BPEL console call-back manual recovery area.The query being used by bpel console is joined on dlv_message and work_item tables.This query simply picks up all call back messages which are undelivered and have not been modified with in certain threshold time.

Call-back messages are processed in following steps
  • BPEL engine assigns the call-back message to delivery service
  • Delivery service saves the message into dlv_message table with state 'UNDELIVERED-0'
  • Delivery service schedules a dispatcher thread to process message asynchronously
  • Dispatcher thread enqueues message into JMS queue
  • Message is picked up by MDB
  • MDB delivers the message to actual BPEL process  waiting for call-back and changes state to 'HANDLED=2'
So given above steps, there is always possibility that message is available in dlv_message table but MDB is failed in delivering it to BPEL process which keeps message always in state= 0.

Following program can be tailored to suite one's own requirements to recover from such state-0 messages.

Note:- This program contains logic to recover from invocation and call-back messages. Please comment out appropriately.

package bpelrecovery;
import com.oracle.bpel.client.*;
import com.oracle.bpel.client.util.SQLDefs;
import com.oracle.bpel.client.util.WhereCondition;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import javax.naming.Context;

public class bpelrecovery {

public bpelrecovery() {

}

public static void main(String[] args) {

bpelrecovery recover = new bpelrecovery();
String rtc = "";
try{
rtc = recover.doRecover();
}
catch (Exception e){
e.printStackTrace();
}
}
rivate void recoverCallbackMessages(List messages)
throws Exception
{

String messageGuids[] = new String[messages.size()];
for(int i = 0; i < messages.size(); i++)
{
ICallbackMetaData callbackMetadata = (ICallbackMetaData)messages.get(i);
String messageGuid = callbackMetadata.getMessageGUID();

messageGuids[i] = messageGuid;
System.err.println((new StringBuilder()).append("recovering callback message =
").append(messageGuids[i]).append(" process
[").append(callbackMetadata.getProcessId()).append("(").append(callbackMetadata.getRevisionTag()).ap
pend(")] domain [").append(callbackMetadata.getDomainId()).append("]").toString());

}

Locator locator = getLocator();
IBPELDomainHandle domainHandle = locator.lookupDomain();
domainHandle.recoverCallbackMessages(messageGuids);

}

public String doRecover() throws Exception{
// Connect to domain "default"

try{

System.out.println("doRecover() instantiating locator...");

Locator locator = getLocator();

System.out.println("doRecover() instantiated locator for domain " +

locator.lookupDomain().getDomainId());

// look for Invoke messages in need of recovery

StringBuffer buf1 = new StringBuffer();

WhereCondition where = new WhereCondition(buf1.append(SQLDefs.IM_state).append( " = "

).append(IDeliveryConstants.STATE_UNRESOLVED ).toString() );

System.out.println("doRecover() instantiating IInvokeMetaData... with where = "+ where.getClause());

IInvokeMetaData imd1[] = locator.listInvokeMessages(where);

System.out.println("doRecover() instantiated IInvokeMetaData");

// iterate thru the list
List l1 = new ArrayList();

for (Object o:imd1){

l1.add(o);

}

// See how many INVOKES are in the recovery zone

System.out.println("doRecover() instantiated IInvokeMetaData size = " +l1.size());

// look for Callback messages in need of recovery

StringBuffer buf = new StringBuffer();

where = new WhereCondition(buf.append(SQLDefs.DM_state).append( " = "

).append(IDeliveryConstants.TYPE_callback_soap ).toString() );

System.out.println("doRecover() instantiating ICallbackMetaData... with where = "+

where.getClause());

ICallbackMetaData imd[] = locator.listCallbackMessages(where);

System.out.println("doRecover() instantiated ICallbackMetaData");
//

// recover

//

List l = new ArrayList();

for (Object o:imd){

l.add(o);

}

 recoverCallbackMessages(l);

}

catch (Exception e){

e.printStackTrace();

}
return "done";

}

public Locator getLocator(){

System.out.println("getLocator() start");

Locator locator = null;
// set JNDI properties for BPEL lookup

String jndiProviderUrl = "opmn:ormi://localhost:6003:oc4j_soa/orabpel";

String jndiFactory = "com.evermind.server.rmi.RMIInitialContextFactory";

String jndiUsername = "oc4jadmin";

String jndiPassword = "welcome1";
Hashtable jndi = new Hashtable();

jndi.put(Context.PROVIDER_URL, jndiProviderUrl);

jndi.put(Context.INITIAL_CONTEXT_FACTORY, jndiFactory);

jndi.put(Context.SECURITY_PRINCIPAL, jndiUsername);

jndi.put(Context.SECURITY_CREDENTIALS, jndiPassword);

jndi.put("dedicated.connection", "true");

try{

System.out.println("getLocator() instantiating locator...");

locator = new Locator("default", "welcome1", jndi);

System.out.println("getLocator() instantiated locator");

}

catch (Exception e){

System.out.println("getLocator() error");

e.printStackTrace();

}

return locator;

}
}