Wednesday, July 30, 2008

Find the OPMN Port for Java BPEL API Calls

Frequently, you'll need the OPMN request port for doing Java BPEL API calls.

You can find this value by looking at the opmn.xml file on the mid tier application server. It is in the $ORACLE_SOA_HOME/opmn/conf directory. Around the 5th line down in the "port" tag, you'll need to use the port number specified in the "request" attribute.

Wednesday, July 16, 2008

Add attachment to BPEL Human Task at creation

Consider the sample BPEL flow below, which contains a Human Task activity. It may be useful at times to have BPEL attach data to the task as an attachment file. How can this be done?




















We can do this by expanding the human task activity and providing some custom initialization code in the first, the AssignTaskAttributes, assignment section.




By adding four additional Copy directives to the existing assignment block, we can effectively cause the file attachment to be created:


<copy>
<from>
<attachment xmlns="http://xmlns.oracle.com/bpel/workflow/task">
<name/>
<mimeType/>
<content/>
</attachment>
</from>
<to variable="initiateTaskInput" part="payload"
query="/taskservice:initiateTask/task:task/task:attachment"/>
</copy>

<copy>
<from expression="string('text/plain')"/>
<to variable="initiateTaskInput" part="payload"
query="/taskservice:initiateTask/task:task/task:attachment[1]/task:mimeType"/>
</copy>

<copy>
<from expression="string('Test.txt')"/>
<to variable="initiateTaskInput" part="payload"
query="/taskservice:initiateTask/task:task/task:attachment[1]/task:name"/>
</copy>

<copy>
<from expression="string('Some string of text content here...')"/>
<to variable="initiateTaskInput" part="payload"
query="/taskservice:initiateTask/task:task/task:attachment[1]/task:content"/>
</copy>


The first copy block, creates a new attachment element and adds to the task. The next three copies initialize the properties of that attachment including the mime type, file name, and content.

In the above example we will have attached a simple text document with the name of Test.txt to the human task. Mime type can be any type desired, one of the more useful being application/octet-stream. Using that type I will show in my next article how to send a file attachment from a JSP/Servlet to the BPEL process and have it attached to the Human Task.

Monday, July 14, 2008

Invoke BPEL Process with Java API

This is a frequently asked question on the Oracle BPEL Forums, so I thought I would address it here.

import com.oracle.bpel.client.Locator;

import com.oracle.bpel.client.NormalizedMessage;
import com.oracle.bpel.client.delivery.IDeliveryService;

public class InvokeBpel {

public static void main(String args[]) {
Properties props = new java.util.Properties();
Locator locator = null;
IDeliveryService svc = null;
NormalizedMessage msg = null;

try {

// load the propertiess object with the server info
props.put("orabpel.platform", "ias_10g");
props.put("java.naming.factory.initial",
"com.evermind.server.rmi.RMIInitialContextFactory");
props.put("java.naming.provider.url",
"opmn:ormi://ibp103.mascocs.com:6012:OC4J_BPEL/orabpel");
props.put("java.naming.security.principal", "oc4jadmin");
props.put("java.naming.security.credentials", "test123");

// get the reference to the locator object
locator = new Locator("default", "test123", props);

// get the service provider
svc = (IDeliveryService)locator.lookupService(IDeliveryService.SERVICE_NAME);

// create a normalized message with our xml string as the payload
String xmlString = "Your valid XML goes here";

msg = new NormalizedMessage();
msg.addPart("payload" , xmlString);

// async request so we post/initiate it
svc.post("TestBPELProcess", "initiate", msg);

// for a sync request we would request/process it like so:
//
// resp = svc.request("TestBPELProcess", "process", msg);
//
// our resp would be the NormalizedMessage response from the server

} catch (Exception e) {
e.printStackTrace();
}

} // end of main()

} // end of class

Monday, April 28, 2008

Including Task History from Previous Task in Oracle BPEL Human Workflow


Oracle BPEL allows the use of Human Task process activities, in conjunction with the Oracle Worklist Application, to allow human interaction with BPEL processes.

Sometimes, it would be desirable to have two human flows that include the same information. Consider a purchase request -- such a request may have a management approval flow, and then a flow for the purchasing department to actually obtain the item. If any comments or attachments are added during the management flow, the purchasers will probably need to see them.

The advanced tab of the human task process has a check box for including the task history from a previous human task process. Unfortunately, as of this writing, JDeveloper (10.1.3.3) does not correctly generate the BPEL copy operations for the new process. Compiling the process will generate several (usually 2 or more) errors relating to invalid XPATH expressions.

The problem usually lies in the copy blocks just prior to or just after the invoke for the new process. For example, the following code might be generated:

<copy>
<from variable="reinitiateTaskResponseMessage" part="payload"
query="/taskservice:initiateTaskResponse/wfcommon:workflowContext"/>
<to variable="workflowContext" query="/wfcommon:workflowContext"/>
</copy>

The problem here lies in the fact that the query should be changed to look like:

<copy>
<from variable="reinitiateTaskResponseMessage" part="payload"
query="/taskservice:reinitiateTaskResponse/wfcommon:workflowContext"/>
<to variable="workflowContext" query="/wfcommon:workflowContext"/>
</copy>

Notice, that the only change required was from initiateTaskResponse to reinitiateTaskResponse. That will be true for all of the errors, and by simply inserting a 're' you can fix them all.

One final note is that its easier to pick these out using the source editor view than the graphical view.

Saturday, March 22, 2008

Programatically controlling the length of expiration/escalation on BPEL Human Task

By default Oracle BPEL lets you expire or escalate tasks on a pre-determined duration basis. But consider the situation where you want the task duration to be 8 working hours before expiration, and working hours are only the time between 8 am an 5 pm, Monday through Friday -- how can you do this?

The options in the standard task dialog don't really accommodate this, but you can work around it. You can provide a variable to the task that has this information. Here are the five basic steps to making this work:

1) Create a BPEL variable of type string. For this example I use "strExpirationMinutes". This will hold the number of minutes to wait before expiring a task.

2) In your human task definition, define a parameter of type string. For this example let's call it "ExpirationTime"

3) In your human task block, assign the value of strExpirationMinutes ( bpws:getVariableData('strExpirationMinutes') ) to the parameter ExpirationTime

4) In your human task definition, set the expiration/escalation policy to be "By Expression" and provide the XPath to reference the passed in parameter. For this example the XPath would be: /task:task/task:payload/task:ExpirationTime

5) Prior to the human task block in the BPEL flow assign a value to strExpirationMinutes of the form "PTxM" where x is the number of minutes before expiration. I typically do this via a embedded Java block.

Here is the Java code for our example:

String tmp = "PT";   
int iMins = 0;
int hrs8 = 60*8;
int hrs23 = 60*23;
int hrs24 = 60*24;

java.util.Calendar calNow = java.util.Calendar.getInstance();

int hours = calNow.get( java.util.Calendar.HOUR_OF_DAY );
int minutes = calNow.get( java.util.Calendar.MINUTE );
int dow = calNow.get( java.util.Calendar.DAY_OF_WEEK );

// if a M-T
if (dow>=2 && dow<=5) {
// if starting before 8 am - add 8 hours plus the interval between now and 8 am
if (hours<8) {
iMins = (60-minutes) + ( (8-hours-1)*60 )+ hrs8;

// if starting between 8 am and 9 am - just add 8 hours
} else if (hours==8 || ( hours==9 && minutes==0) ) {
iMins = hrs8;

// if starting between 9 am and 5 pm - just add 23 hours
} else if (hours>=9 && (hours<17 || (hours==17 && minutes==0)) ) {

iMins = hrs23;

// if starting after 5 pm - add 8 hours plus the interval between now and 8 am
} else {
iMins = (60-minutes) + ( (24-hours-1)*60 ) + hrs8 + hrs8;
}

// if Fri
} else if (dow==6) {
// if starting before 8 am - add 8 hours plus the interval between now and 8 am
if (hours<8) {
iMins = (60-minutes) + ( (8-hours-1)*60 )+ hrs8;

// if starting between 8 am and 9 am - just add 8 hours
} else if (hours==8 || ( hours==9 && minutes==0) ) {
iMins = hrs8;

// if starting between 9 am and 5 pm - add 23 hours plus the two we days
} else if (hours>=9 && (hours<17 || (hours==17 && minutes==0)) ) {

iMins = hrs23 + hrs24 + hrs24;

// if starting after 5 pm - add 8 hours duration plus the interval between
// now and 8 am saturday, plus the 2 we days
} else {
iMins = (60-minutes) + ( (24-hours-1)*60 ) + hrs8 + hrs8 + hrs24 + hrs24;
}

// if Sat
} else if (dow==7) {
// add 8 hrs duration, plus the time between now and midnight sat, plus
// all of sunday, plus 8 hrs for monday before 8 am
iMins = (60-minutes) + ( (24-hours-1)*60 ) + hrs8 + hrs8 + hrs24;

// if Sun
} else {
// add 8 hrs duration, plus the time between now and midnight sun,
plus
// 8 hrs for monday before 8 am
iMins = (60-minutes) + ( (24-hours-1)*60 ) + hrs8 + hrs8;
}

// finish build the time string
tmp = tmp + iMins + "M";

// save the string to bpel proc and audit
setVariableData("strExpirationMinutes", tmp);
addAuditTrailEntry("strExpirationMinutes:",tmp);

Programmatically determining BPEL manual recovery status

It's been a while since I've written an article, and I hope no one has been waiting all this time for part 2 of my BAM/BPEL alert article. I promise to complete that write up soon.

Today, however, I'd like to talk about an interesting problem I ran across this week. If anyone has experience with Oracle's BPEL product you know that sometimes BPEL processes can go to manual recovery. The two most common issues are: 1) an issue on the invoke of the process (BPEL server Out of Memory error for example), and 2) an issue with the callback on an asynchronous BPEL process.

BPEL process manager has a nice UI for looking at and managing these, but what if we need to be alerted when a process goes into one of these states? Well, BPEL PM doesn't have that capability, so we need to write our own. Another useful thing might be to always clear the processes, especially those that are in manual recovery for callback. Typically in that situation the callback failed, most often because the original calling process is no longer around to receive the callback.

In the rest of the article we talk about finding processes in both invoke and callback manual recovery. For both cases, it will be necessary to get a reference to the Locator object via the BPEL Java API. I talk about that in an earlier post, so I've assumed that you've read up on that before continuing.

Getting process manual recovery for inoke:

            String Buffer buf = new StringBuffer();

WhereCondition where = new WhereCondition(buf.append(SQLDefs.IM_state).append( " = " ).append(IDeliveryConstants.STATE_UNRESOLVED ).toString() );

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

String ids[] = new String[imd.length];

// print out the partial process information
// for processes in manual recovery status on invoke
for (i = 0; i <>
{
System.out.println("ConversationId=" +
imd[i].getConversationId());
System.out.println("ProcessId=" +
imd[i].getProcessId());
System.out.println("State=" + imd[i].getState());

ids[i] = imd[i].getConversationId();
}

// if we wanted to automatically recover these processes
// we would uncomment the following line:
// locator.lookupDomain().recoverInvokeMessages(ids);

That's it! For callbacks the code flow is the exact same. Instead of IInvokeMetaData, we get an array of ICallbackMetaData using the locator.listCallbackMessages(where) method. One other slight change is the where clause itself -- for callbacks the where clause should be built as:

WhereCondition where = new WhereCondition(buf.append(SQLDefs.DM_state).append( " = " ).append(
IDeliveryConstants.TYPE_callback_soap ).toString() );

The last change for callbacks is how they are recovered -- locator.lookupDomain.recoverCallbackMessages(ids) will do the trick!

Using the API's above you can see how we could build a custom notification process or an automatic or work flow based recovery method that does not involve the actual BPEL PM.

Oracle BAM - Calling an external web service as an alert - Part 1 - BAM Configuration

Oracle's BAM tool is very useful not only for reporting on enterprise data, but also in taking action on certain events.

For instance, suppose you have a BAM object defined and other processes are interested in the fact that the BAM object has been populated with a new row. In the SOA world, there are many ways we could notify another process -- via a message queue or simply by calling a web service. It is the second technique that we will be discussing in this and a follow up article.

In my example I have a BPEL process that needs to be called every time my BAM data object has a new row added to it. BAM doesn't know anything about using the BPEL Java API, but it can call a web service via an external action hooked to an alert. We'll talk about the BPEL process in the next post -- the rest of this post will described how to set up the external action and alert in BAM.

Step 1 - Creating an External Action in BAM


The external action will define the actual call to the web service. External actions are created using the BAM Architect module.

1) Select 'Data Objects' from the architect drop down
2) Expand the 'System' folder and then click the 'Alert' folder
3) Click on the 'External Actions' data object link
4) If you select the 'Layout' link you'll see that an external action has five fields that must be filled in:
  1. Name - The name that will appear in the alert's dialog
  2. Description - A description of the action
  3. Library Name - The library that contains the action class
  4. Class Name - The class which implements the external action
  5. Argument - Argument to pass to the action class
5) Click the 'Contents' link and then the 'Edit Contents' button.
6) Click the 'Add' button to add a new row. The name and description fields are self-explanatory. The library name field should be set to 'Internal' and the class name field should be set to 'WebServiceCaller'. Finally the argument field needs to be set to 'URL=' followed by the complete endpoint URL to the web service -- for example:
'URL=http://myhost:myport/orabpel/mydomain/bpelprocessname/1.0,SOAPAction=process'
7) Verify everything and click 'Save' to save your new row

Note: Once you have defined your new external actions, you should restart the BAM Event Engine service.

Step 2 - Creating the Alert in BAM to Call the Action

The alert will set up the conditions under which our alert is called. Alerts are created using the BAM Architect module.

1) Select 'Alerts' from the architect drop down
2) Click the 'Create a New Alert' link
3) A new window will pop up, click the 'Create Rule' button
4) Give the rule a meaningful name in the 'Rule Name' field
5) Select the trigger event. For my example I want to call the web service everytime a row is added to a data object, so I would select 'When a data field changes in a data object'
6) Click the 'Selet data field' link
7) Pick the appropriate data object and data field.
8) By default, BAM will add a frequency constraint that will launch your rule no more than once every 5 seconds. You can change this frequency constraint to any number of minutes, hours, or seconds, or you can disable the constraint by unchecking the 'Constraint Enabled' box. Make sure that you uncheck the box if you need the alert to fire on every single event (BAM will warn you about this and ask you to click 'OK' if you want the constraint disabled).
9) Click the 'Next' button
10) You can add any time conditions if required and most importantly you can now select the action to perform. If you restarted the event service after you created you external action, you should now see your external action as an option at the end of the list. Select it.
11) Click 'OK'. Your new alert will be created and appear in the alert list.

Note: You can enable and disable alerts, simply by checking or unchecking the 'Activate' box by the alert.

If you've filled in everything correctly, your alert should fire when you add a row to the data object that you selected. If you're reading this in anticipation of calling a BPEL process, here's an important point in advance of my next post -- BAM can only call synchronous BPEL processes.