Archiving Audit Diagrams as Images in Oracle SOA Suite BPM Processes

In my previous post I had described how we can create a custom Java Class to save custom business indicators in a formatted PDF file. It might be useful in cases where businesses wants process data be presented in a meaningful way.

Another practical case that i had encountered in the past while designing business processes is to save the Audit diagram of the process once it is complete. The audit instance diagram can be viewed in the EM console by clicking on Flow Trace for any process instance.

In the good all Oracle BPM 10g days (prior to Oracle SOA/BPM Suite 11g) we had a fully documented API’s for interacting with process instances. PAPI interfaces were available both as web service and java API’s to connect to a in-flight or completed instance and retrieve all audit data from it.

Getting an audit image from a business process instance using PAPI was a cake walk. The below code sample shows its ease

ProcessService processService = null;
ProcessServiceSession session = null;
try {
processService = ProcessService.create(configuration);
session = processService.createSession(USERNAME, PASSWORD, HOSTNAME);
for (String processId : session.processesGetIds())
{
 fuego.papi.Process process = session.processGet(processId);
 ProcessDiagram diagram = session.processGetDiagram(processId);
 diagram.setTheme(ProcessDiagram.THEME_COLOR_BPMN);
 diagram.setDrawFlowOnly(true);
 Image image = diagram.getImage();
 File pngImage = new File(createPngFilename("image"));
 image.write(pngImage, ImageExtension.PNG);
}
catch (Exception e)
{
 e.printStackTrace();
}
finally
{
 session.close();
 processService.close();
}

Whoa! Pretty Simple and elegant.

However if we want to achieve something similar in Oracle SOA Suite 11g it is a lot more challenging.

Oracle BPM Suite 11g doesn’t have any published API’s that developers can refer. This makes it an even bigger nightmare.

The process instance data in Oracle SOA suite 11g is stored in the dehydration store in the SOA_INFRA schema. In all practical scenarios this store will be subjected to purging and maintenance. So many a people/project might need to archive the flow trace of a process instance as an image. As we all know how significant is historical data for business process improvements and reengineering.

In this blog post I will show how the same functionality of getting an instance image from a process can be achieved using Oracle SOA Suite 11g and explain the code in steps.

Assuming we are using Oracle SOA Suite 11g PS3 that has a running domain and a BPM process deployed with a couple of running/completed instances.

Create a Generic Java project in JDeveloper say ArchiveInstanceImage. Create a Java class of the same name inside it. Right click on the project and add the following JAR’s to the project’s classpath.

Oracle.bpm.bpm-services.client.jar
Oracle.bpm.bpm-services.interface.jar
Oracle.bpm.client.jar
Bpm-infra.jar
Bpm-services.jar
Oracle.bpm.project.model.jar
Oracle.bpm.project.draw.jar
Oracle.bpm.project.catalog.jar
Wlfullclient.jar
Wsclient_extended.jar
Oracle.bpm.core.jar
Oracle.bpm.lib.jar
Oracle.bpm.papi.jar
Oracle.bpm.xml.jar
Oracle.bpm.diagram.draw.jar
Oracle-bpm.jar
Oracle.bpm.bpm-services.implementation.jar
Oracle.bpm.bpm-services.internal.jar
Oracle.bpm.bpmobject.jar
Oracle.bpm.runtime.jar
Oracle.bpm.ui.jar

All these above JAR’s can be found at the following directories

<JDevHome>\soa\modules\oracle.bpm.client_11.1.1
<JDevHome>\soa\modules\oracle.soa.fabric_11.1.1
<JDevHome>\soa\modules\oracle.soa.workflow_11.1.1
<JDevHome>\soa\modules\oracle.bpm.project_11.1.1
<MiddlewareHome>\wlserver_10.3\server\lib
<MiddlewareHome>\oracle_common\webservices
<JDevHome>\soa\modules\oracle.bpm.runtime_11.1.1
<JDevHome>\soa\modules\oracle.bpm.workspace_11.1.1

You can created Wlfullclient.jar as under

Change directories to the server/lib directory.

cd <MiddlewareHome>wlserver_10.3/server/lib

Use the following command to create wlfullclient.jar in the server/lib directory:

java -jar wljarbuilder.jar

You can now copy and bundle the wlfullclient.jar with client applications.

Add the wlfullclient.jar to the client application’s classpath.

See here for more information

http://download.oracle.com/docs/cd/E12840_01/wls/docs103/client/jarbuilder.html

First and foremost like any remote client we need to get an instance of the SOA server runtime to be able to gain access to any running processes inside it. This is pretty simple. The following lines of code demonstrates how we can use BPMServiceClientFactory class to get an instance of the server runtime.

Next initialize an IBPMContext from BPMServiceClientFactory.

// URL of the SOA Server and PORT on which the application is deployed
private static String soaURL = "t3://localhost:4003";

public static BPMServiceClientFactory getBPMServiceClientFactory()
{
Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY,String> properties = new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY,String>();
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,WorkflowServiceClientFactory.REMOTE_CLIENT);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,soaURL);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,"weblogic");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,"welcome123");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
return BPMServiceClientFactory.getInstance(properties, null, null);
}

public static IBPMServiceClient getBPMServiceClient(){
return getBPMServiceClientFactory().getBPMServiceClient();
}
public static IBPMContext getIBPMContextForAuthenticatedUser() throws Exception{
return getBPMServiceClientFactory().getBPMUserAuthenticationService().getBPMContextForAuthenticatedUser();
}

We also have to get a handle to the IBPMServiceClient interface. Create a self initializing constructor for our main class to get a hook to this interface.

// Get a handle to the IBPMServiceClient interface in the ArchiveInstanceImage class
public ArchiveInstanceImage(IBPMServiceClient bpmServiceClient)
{
this.bpmServiceClient = bpmServiceClient;
}

Using the IBPMContext and a searchable instance id from the process we can get process data using the IProcessInstance interface.

See the code snippet below that shows how we can retrieve the audit diagram for a process instance

public InputStream getProcessAuditImage(IBPMContext bpmContext, String instanceId)
throws BPMException
{
IInstanceQueryService instanceQueryService = this.bpmServiceClient.getInstanceQueryService();
IProcessInstance processInstance = instanceQueryService.getProcessInstance(bpmContext, instanceId);

if (processInstance == null) {
return null;
}
IProcessModelPackage processModelPackage = this.bpmServiceClient.getProcessModelService().getProcessModel(bpmContext, processInstance.getSca().getCompositeDN(), processInstance.getSca().getComponentName());

AuditProcessDiagrammer auditProcessImage  = new AuditProcessDiagrammer(processModelPackage.getProcessModel());
return getProcessImage(auditProcessImage);
}

private InputStream getProcessImage(AuditProcessDiagrammer processImage)
throws BPMException
{
InputStream processImageStream  = null;
ByteArrayOutputStream auditImageOutputStream = new ByteArrayOutputStream();
try {
// Get base64 encoded image String from processImage
String base64Image = processImage.getImage();
Image image = Image.createFromBase64(base64Image);
BufferedImage bufferedImage = (BufferedImage)image.asAwtImage();
// Use the Image Extension that suites you from .PNG, .JPG and .GIF
ImageIOFacade.writeImage(bufferedImage, ImageExtension.PNG, auditImageOutputStream);
processImageStream  = new ByteArrayInputStream(auditImageOutputStream.toByteArray());
// Archives the Process Image at any suitable location
archiveDiagramToFile(processImageStream);
}
catch (Exception e)
{
throw new BPMException(e);
}
finally {
}
return processImageStream;
}

// Utility method to Archive the InputStream into a PNG File
private void archiveDiagramToFile(InputStream istream) throws IOException {
File outputFile = new File("C:\\Arrun\\ProcessAuditImage\\ProcessImage.png");
OutputStream out = new FileOutputStream(outputFile);

// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = istream.read(buf)) > 0) {
out.write(buf, 0, len);
}
istream.close();
out.close();
}

Finally to test the code that we had written add a main method to invoke getProcessAuditImage(iBPMContext, instanceId) to see the process audit image created.

public static void main (String args[]) throws BPMException, Exception
{
ArchiveInstanceImage instImage = new ArchiveInstanceImage(getBPMServiceClient());
// Use any instanceId that is existing for the process in the server
InputStream istream= instImage.getProcessAuditImage(getIBPMContextForAuthenticatedUser(),"840001");
}

Running the standalone Java program from inside JDeveloper creates the following process image in the target archive directory.

image

A quick look at the image and we will realize what it is lacking. Off course we have been quite able to get the image for the process (similar to what we used to get using PAPI in OBPM 10g). However we don’t see the flow trace i.e the sequence of activities that the instance traversed in its flow.

To get that we have get a List of DiagramEvent and highlight the process image by passing this list to it.

Create another private function to get a list of all Events that the instance encountered in its flow as below.

private List<DiagramEvent> getHighlightEvents(Process processModel, IAuditInstance auditInstance)
{
List events = new ArrayList();

String activityId = auditInstance.getActivityId();
Date eventDate = auditInstance.getCreateTime().getTime();
DiagramEvent nodeEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.FLOW_NODE_IN, activityId, eventDate);

events.add(nodeEvent);
String sourceActivity;
String targetActivity;
if (auditInstance.getAuditInstanceType().equalsIgnoreCase("START")) {
FlowNode flowNode = (FlowNode)processModel.findDescendant(FlowNode.class, auditInstance.getActivityId());

if (flowNode != null) {
sourceActivity = auditInstance.getSourceActivity();
Sequence<SequenceFlow> incommingSequenceFlows = flowNode.getIncomingSequenceFlows();
if ((incommingSequenceFlows != null) && (!incommingSequenceFlows.isEmpty()) && (sourceActivity != null)) {
Iterator<SequenceFlow> seqIterator  = incommingSequenceFlows.iterator();
while(seqIterator.hasNext())
{
SequenceFlow sequenceFlow= seqIterator.next();
if (sequenceFlow.getSource().getId().equalsIgnoreCase(sourceActivity)) {
DiagramEvent sequenceEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.SEQUENCE_FLOW, sequenceFlow.getId(), eventDate);
events.add(sequenceEvent);
}}
}}
}
else if (auditInstance.getAuditInstanceType().equalsIgnoreCase("END")) {
FlowNode flowNode = (FlowNode)processModel.findDescendant(FlowNode.class, auditInstance.getActivityId());

if (flowNode != null) {
targetActivity = auditInstance.getTargetActivity();
Sequence<SequenceFlow> outgoingSequenceFlows = flowNode.getOutgoingSequenceFlows();

if ((outgoingSequenceFlows != null) && (!outgoingSequenceFlows.isEmpty()) &&
(targetActivity != null)) {
Iterator<SequenceFlow> seqIterator  = outgoingSequenceFlows.iterator();
while(seqIterator.hasNext())
{
SequenceFlow sequenceFlow= seqIterator.next();
if (sequenceFlow.getTarget().getId().equalsIgnoreCase(targetActivity))
{
DiagramEvent sequenceEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.SEQUENCE_FLOW, sequenceFlow.getId(), eventDate);
events.add(sequenceEvent);
}}
}}
}

Add these following line to the getProcessAuditImage(IBPMContext bpmContext, String instanceId) method before the return statement to highlight the image

List auditInstances = this.bpmServiceClient.getInstanceQueryService().queryAuditInstanceByProcessId(bpmContext, instanceId);
List diagramEvents = new ArrayList();

for (int IAuditInstance=0; IAuditInstance< auditInstances.size(); IAuditInstance++)
{
diagramEvents.addAll(getHighlightEvents(process, (IAuditInstance)auditInstances.get(IAuditInstance)));
}
auditProcessImage.highlight(diagramEvents);

Run the Java program once again and view the image created this time.

image

This time you can see that the activities and transitions that the instance took are highlighted in the image.

The complete Java Class can be found below

package blog.beatechnologies.soasuiteutil;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import oracle.bpel.services.bpm.common.IBPMContext;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;

import oracle.bpm.client.BPMServiceClientFactory;
import oracle.bpm.collections.Sequence;
import oracle.bpm.draw.diagram.AuditProcessDiagrammer;
import oracle.bpm.draw.diagram.DiagramEvent;
import oracle.bpm.project.model.processes.FlowNode;
import oracle.bpm.project.model.processes.Process;
import oracle.bpm.project.model.processes.SequenceFlow;
import oracle.bpm.services.client.IBPMServiceClient;
import oracle.bpm.services.common.exception.BPMException;
import oracle.bpm.services.instancemanagement.model.IProcessInstance;
import oracle.bpm.services.instancequery.IAuditInstance;
import oracle.bpm.services.instancequery.IInstanceQueryService;
import oracle.bpm.services.internal.processmodel.model.IProcessModelPackage;
import oracle.bpm.ui.Image;
import oracle.bpm.ui.utils.ImageExtension;
import oracle.bpm.ui.utils.ImageIOFacade;

public class ArchiveInstanceImage
{
private IBPMServiceClient bpmServiceClient;
// URL of the SOA Server and PORT on which the application is deployed
private static String soaURL = "t3://soasitapp03.us.dell.com:5411";

public ArchiveInstanceImage(IBPMServiceClient bpmServiceClient)
{
this.bpmServiceClient = bpmServiceClient;
}

public InputStream getProcessAuditImage(IBPMContext bpmContext, String instanceId)
throws BPMException
{
IInstanceQueryService instanceQueryService = this.bpmServiceClient.getInstanceQueryService();
IProcessInstance processInstance = instanceQueryService.getProcessInstance(bpmContext, instanceId);

if (processInstance == null) {
return null;
}
IProcessModelPackage processModelPackage = this.bpmServiceClient.getProcessModelService().getProcessModel(bpmContext, processInstance.getSca().getCompositeDN(), processInstance.getSca().getComponentName());

AuditProcessDiagrammer auditProcessImage  = new AuditProcessDiagrammer(processModelPackage.getProcessModel());
List auditInstances = this.bpmServiceClient.getInstanceQueryService().queryAuditInstanceByProcessId(bpmContext, instanceId);

List diagramEvents = new ArrayList();

for (int IAuditInstance=0; IAuditInstance< auditInstances.size(); IAuditInstance++)
{
diagramEvents.addAll(getHighlightEvents(processModelPackage.getProcessModel(), (IAuditInstance)auditInstances.get(IAuditInstance)));
}
auditProcessImage.highlight(diagramEvents);
return getProcessImage(auditProcessImage);
}

private InputStream getProcessImage(AuditProcessDiagrammer processImage)
throws BPMException
{
InputStream processImageStream  = null;
ByteArrayOutputStream auditImageOutputStream = new ByteArrayOutputStream();
try {
// Get base64 encoded image String from processImage
String base64Image = processImage.getImage();
Image image = Image.createFromBase64(base64Image);
BufferedImage bufferedImage = (BufferedImage)image.asAwtImage();
// Use the Image Extension that suites you from .PNG, .JPG and .GIF
ImageIOFacade.writeImage(bufferedImage, ImageExtension.PNG, auditImageOutputStream);
processImageStream  = new ByteArrayInputStream(auditImageOutputStream.toByteArray());
// Archives the Process Image at any suitable location
archiveDiagramToFile(processImageStream);
}
catch (Exception e)
{
throw new BPMException(e);
}
finally {
}
return processImageStream;
}

private List<DiagramEvent> getHighlightEvents(Process processModel, IAuditInstance auditInstance)
{
List events = new ArrayList();

String activityId = auditInstance.getActivityId();
Date eventDate = auditInstance.getCreateTime().getTime();
DiagramEvent nodeEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.FLOW_NODE_IN, activityId, eventDate);

events.add(nodeEvent);
String sourceActivity;
String targetActivity;
if (auditInstance.getAuditInstanceType().equalsIgnoreCase("START")) {
FlowNode flowNode = (FlowNode)processModel.findDescendant(FlowNode.class, auditInstance.getActivityId());

if (flowNode != null) {
sourceActivity = auditInstance.getSourceActivity();
Sequence<SequenceFlow> incommingSequenceFlows = flowNode.getIncomingSequenceFlows();

if ((incommingSequenceFlows != null) && (!incommingSequenceFlows.isEmpty()) && (sourceActivity != null)) {
Iterator<SequenceFlow> seqIterator  = incommingSequenceFlows.iterator();

while(seqIterator.hasNext())
{
SequenceFlow sequenceFlow= seqIterator.next();
if (sequenceFlow.getSource().getId().equalsIgnoreCase(sourceActivity)) {
DiagramEvent sequenceEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.SEQUENCE_FLOW, sequenceFlow.getId(), eventDate);
events.add(sequenceEvent);
}}
}}
}
else if (auditInstance.getAuditInstanceType().equalsIgnoreCase("END")) {
FlowNode flowNode = (FlowNode)processModel.findDescendant(FlowNode.class, auditInstance.getActivityId());

if (flowNode != null) {
targetActivity = auditInstance.getTargetActivity();
Sequence<SequenceFlow> outgoingSequenceFlows = flowNode.getOutgoingSequenceFlows();

if ((outgoingSequenceFlows != null) && (!outgoingSequenceFlows.isEmpty()) && (targetActivity != null)) {
Iterator<SequenceFlow> seqIterator  = outgoingSequenceFlows.iterator();
while(seqIterator.hasNext())
{
SequenceFlow sequenceFlow= seqIterator.next();
if (sequenceFlow.getTarget().getId().equalsIgnoreCase(targetActivity)) {
DiagramEvent sequenceEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.SEQUENCE_FLOW, sequenceFlow.getId(), eventDate);
events.add(sequenceEvent);
}}
}}
}
return events;
}
public static BPMServiceClientFactory getBPMServiceClientFactory() {
Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String> properties = new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String>();
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,WorkflowServiceClientFactory.REMOTE_CLIENT);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,soaURL);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,"arun_pareek");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,"#Jannu12");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
return BPMServiceClientFactory.getInstance(properties, null, null);
}
public static IBPMServiceClient getBPMServiceClient(){
return getBPMServiceClientFactory().getBPMServiceClient();
}
public static IBPMContext getIBPMContextForAuthenticatedUser() throws Exception{
return getBPMServiceClientFactory().getBPMUserAuthenticationService().getBPMContextForAuthenticatedUser();
}

public static void main (String args[]) throws BPMException, Exception
{
ArchiveInstanceImage instImage = new ArchiveInstanceImage(getBPMServiceClient());
InputStream istream= instImage.getProcessAuditImage(getIBPMContextForAuthenticatedUser(),"10002");
}
void archiveDiagramToFile(InputStream istream) throws IOException {
File outputFile = new File("C:\\Arrun\\process.png");
OutputStream out = new FileOutputStream(outputFile);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = istream.read(buf)) > 0) {
out.write(buf, 0, len);
}
istream.close();
out.close();
}
}

This was all about getting this Java API’s to work with SOA Suite 11g PS3. I also tried to test the same with a  PS2 domain and with PS2 libraries.

Everything was almost same except for a few things.

There is no class called IProcessModelPackage that I could find in PS2 BPM jar’s

So the process image was obtained using IProcessModelService class like below

IProcessModelService processModelService =  this.bpmServiceClient.getProcessModelService();
Process process = processModelService.getProcessModel(bpmContext, processInstance.getSca().getCompositeDN(), processInstance.getSca().getComponentName());
AuditProcessDiagrammer auditProcessImage = new AuditProcessDiagrammer(process);

The additional libraries that we need to put in the project’s classspath are

Oracle.bpm.project.io.jar                    <JDevHome>\soa\modules\oracle.bpm.project_11.1.1
Oracle.bpm.project.jar                         <JDevHome>\soa\modules\oracle.bpm.project_11.1.1
Oracle.bpm.project.compile.jar        <JDevHome>\soa\modules\oracle.bpm.project_11.1.1
Oracle.bpm.vfilesystem.jar                 <JDevHome>\soa\modules\oracle.bpm.runtime_11.1.1

In case you would need the entire class here is what you should use in case you are on Oracle SOA Suite 11g PS2

package blog.beatechnologies.soautil;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import oracle.bpel.services.bpm.common.IBPMContext;
import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;

import oracle.bpm.client.BPMServiceClientFactory;
import oracle.bpm.collections.Sequence;
import oracle.bpm.draw.diagram.AuditProcessDiagrammer;
import oracle.bpm.draw.diagram.DiagramEvent;
import oracle.bpm.project.model.processes.FlowNode;
import oracle.bpm.project.model.processes.Process;
import oracle.bpm.project.model.processes.SequenceFlow;
import oracle.bpm.services.client.IBPMServiceClient;
import oracle.bpm.services.common.exception.BPMException;
import oracle.bpm.services.instancemanagement.model.IProcessInstance;
import oracle.bpm.services.instancequery.IAuditInstance;
import oracle.bpm.services.instancequery.IInstanceQueryService;
import oracle.bpm.services.internal.processmodel.IProcessModelService;
import oracle.bpm.services.util.AuditTrail;
import oracle.bpm.ui.Image;
import oracle.bpm.ui.utils.ImageExtension;
import oracle.bpm.ui.utils.ImageIOFacade;

public class ArchiveInstanceImage
{
private IBPMServiceClient bpmServiceClient;
// URL of the SOA Server and PORT on which the application is deployed
private static String soaURL = "t3://wxp-fxhp7bs.blr.amer.dell.com:4003";

// Get a handle of IBPMServiceClient through the class constructor
public ArchiveInstanceImage(IBPMServiceClient bpmServiceClient)
{
this.bpmServiceClient = bpmServiceClient;
}

public InputStream getProcessAuditImage(IBPMContext bpmContext, String instanceId)
throws BPMException
{
IInstanceQueryService instanceQueryService = this.bpmServiceClient.getInstanceQueryService();
IProcessInstance processInstance = instanceQueryService.getProcessInstance(bpmContext, instanceId);

System.out.println("Composite DN " + processInstance.getSca().getCompositeDN());
System.out.println("Composite DN " + processInstance.getSca().getComponentName());

if (processInstance == null) {
return null;
}

IProcessModelService processModelService =  this.bpmServiceClient.getProcessModelService();
Process process = processModelService.getProcessModel(bpmContext, processInstance.getSca().getCompositeDN(), processInstance.getSca().getComponentName());
AuditProcessDiagrammer auditProcessImage = new AuditProcessDiagrammer(process);
List auditInstances = this.bpmServiceClient.getInstanceQueryService().queryAuditInstanceByProcessId(bpmContext, instanceId);

List diagramEvents = new ArrayList();

for (int IAuditInstance=0; IAuditInstance< auditInstances.size(); IAuditInstance++)
{
diagramEvents.addAll(getHighlightEvents(process, (IAuditInstance)auditInstances.get(IAuditInstance)));
}
auditProcessImage.highlight(diagramEvents);
//auditProcessImage.getImage()
return getProcessImage(auditProcessImage);
}

private InputStream getProcessImage(AuditProcessDiagrammer processImage)
throws BPMException
{
InputStream processImageStream = null;
ByteArrayOutputStream auditImageOutputStream = new ByteArrayOutputStream();
try {
// Get base64 encoded image String from processImage
String base64Image = processImage.getImage();
Image image = Image.createFromBase64(base64Image);
BufferedImage bufferedImage = (BufferedImage)image.asAwtImage();
// Use the Image Extension that suites you from .PNG, .JPG and .GIF
ImageIOFacade.writeImage(bufferedImage, ImageExtension.PNG, auditImageOutputStream);
processImageStream = new ByteArrayInputStream(auditImageOutputStream.toByteArray());
// Archives the Process Image at any suitable location
archiveDiagramToFile(processImageStream);
}
catch (Exception e)
{
throw new BPMException(e);
}
finally {
}
return processImageStream;
}

private List<DiagramEvent> getHighlightEvents(Process processModel, IAuditInstance auditInstance)
{
List events = new ArrayList();
String activityId = auditInstance.getActivityId();
Date eventDate = auditInstance.getCreateTime().getTime();
DiagramEvent nodeEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.FLOW_NODE_IN, activityId, eventDate);
events.add(nodeEvent);
String sourceActivity;
String targetActivity;
if (auditInstance.getAuditInstanceType().equalsIgnoreCase("START")) {
FlowNode flowNode = (FlowNode)processModel.findDescendant(FlowNode.class, auditInstance.getActivityId());

if (flowNode != null) {
sourceActivity = auditInstance.getSourceActivity();
Sequence<SequenceFlow> incommingSequenceFlows = flowNode.getIncomingSequenceFlows();

if ((incommingSequenceFlows != null) && (!incommingSequenceFlows.isEmpty()) && (sourceActivity != null)) {
Iterator<SequenceFlow> seqIterator  = incommingSequenceFlows.iterator();
while(seqIterator.hasNext())
{
SequenceFlow sequenceFlow= seqIterator.next();
if (sequenceFlow.getSource().getId().equalsIgnoreCase(sourceActivity)) {
DiagramEvent sequenceEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.SEQUENCE_FLOW, sequenceFlow.getId(), eventDate);
events.add(sequenceEvent);
}}
}}
}
else if (auditInstance.getAuditInstanceType().equalsIgnoreCase("END")) {
FlowNode flowNode = (FlowNode)processModel.findDescendant(FlowNode.class, auditInstance.getActivityId());

if (flowNode != null) {
targetActivity = auditInstance.getTargetActivity();
Sequence<SequenceFlow> outgoingSequenceFlows = flowNode.getOutgoingSequenceFlows();

if ((outgoingSequenceFlows != null) && (!outgoingSequenceFlows.isEmpty()) &&
(targetActivity != null)) {
Iterator<SequenceFlow> seqIterator  = outgoingSequenceFlows.iterator();
while(seqIterator.hasNext())
{
SequenceFlow sequenceFlow= seqIterator.next();
if (sequenceFlow.getTarget().getId().equalsIgnoreCase(targetActivity))
{
DiagramEvent sequenceEvent = DiagramEvent.create(DiagramEvent.DiagramEventType.SEQUENCE_FLOW, sequenceFlow.getId(), eventDate);
events.add(sequenceEvent);
}}
}}
}
return events;
}
public static BPMServiceClientFactory getBPMServiceClientFactory() {
Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String> properties = new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, String>();
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.CLIENT_TYPE,WorkflowServiceClientFactory.REMOTE_CLIENT);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_PROVIDER_URL,soaURL);
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_PRINCIPAL,"weblogic");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_SECURITY_CREDENTIALS,"welcome123");
properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.EJB_INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
return BPMServiceClientFactory.getInstance(properties, null, null);
}
public static IBPMServiceClient getBPMServiceClient(){
return getBPMServiceClientFactory().getBPMServiceClient();
}
public static IBPMContext getIBPMContextForAuthenticatedUser() throws Exception{
return getBPMServiceClientFactory().getBPMUserAuthenticationService().getBPMContextForAuthenticatedUser();
}
public static void main (String args[]) throws BPMException, Exception {

ArchiveInstanceImage instImage = new ArchiveInstanceImage(getBPMServiceClient());
InputStream istream= instImage.getProcessAuditImage(getIBPMContextForAuthenticatedUser(),"840001");
}
// Utility method to Archive the InputStream into a PNG File
private void archiveDiagramToFile(InputStream istream) throws IOException {
File outputFile = new File("C:\\Arrun\\ProcessAuditImage\\ProcessImage.png");
OutputStream out = new FileOutputStream(outputFile);

// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = istream.read(buf)) > 0) {
out.write(buf, 0, len);
}
istream.close();
out.close();
}
}

Now there are a couple of ways to use this Java Code in real practical cases

  1. Use it from any Custom UI/ Workflow UI to access the process image if need be.
  2. Create a Web service wrapper over the Java Class and use it as a WS API that can be invoked from any BPM process and hence it can be reused across multiple BPM processes.
  3. In case you would want to limit the use inside a composite then create a Spring SCA component for this custom Java class and then invoke it from inside a BPM process

In my future blogs I will try to come up with more interesting utilities with these BPM API’s. This will probably be of great use to folks who have worked with the PAPI API’s and find it a great miss in Oracle SOA Suite 11g.

Meanwhile also wondering what is stopping Oracle to publish a well document API for these interfaces. 🙂

.

Creating A PDF Document within a BPEL Process

Quite recently I had this requirement in one of my project to create a PDF snapshot from a BPEL Process. The PDF document was required to be archived and contain a brief detail of key business indicators for the order and also whether the was processed manually or automatically.

The business requirement thus set it was now evident that we needed to define a schema to extract the key order indicators and use it in an Embedded Java activity inside a BPEL process to call a custom piece of Java code that create a PDF document.

Defining the schema

Here is snapshot of the schema I choose to create to describe the Order Indicators for a Sales Order Instance.

image

The Order message is intercepted in the bpel process to determine whether it is to be processed manually or automatically.

A first Look at the PDF

Creating an instance and letting the Order process manually I was able to derive a PDF capturing the order summary at an archived location. The below screenshot shows how the document looked like.

image

Creating the Process

The first step in the process would be to instantiate a salesOrderIndicator variable based on the above schema and extract information from the Sales Order payload inside the BPEL process.

I created a custom Java class to print a PDF document using the iText api for Java.

The library can be obtained from sourceforge here

Create a Java project in JDeveloper and name it OrderApprovalDocument. Add the Itextpdf-5.1.1.jar to the project classpath.

image

The OrderApprovalDocument class will create an instance of PdfWriter, take in value from the BPEL JAVA Embedded activity and create the pdf document in a tabular format.

package com.beatech.salesapp;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.Barcode;
import com.itextpdf.text.pdf.BarcodeEAN;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;

import java.awt.Color;

import java.io.FileOutputStream;

import java.util.Date;
import java.util.Iterator;
import java.util.Map;

public class OrderApprovalDocument {
public OrderApprovalDocument() {
super();
}

private static Font catFont =new Font(Font.FontFamily.HELVETICA, 11, Font.BOLD);
private static Font redFont =new Font(Font.FontFamily.TIMES_ROMAN, 9, Font.NORMAL, BaseColor.RED);
private static Font subFont =new Font(Font.FontFamily.HELVETICA, 6, Font.BOLD);
private static Font smallBold =new Font(Font.FontFamily.HELVETICA, 8, Font.BOLD);

public void createPDFSummary( Map varMap, String approvalStatus)
{

String fileName = "c:/Arrun/OrderApprovalSnapshot_"+(String)varMap.get("PURCHASE ID")+".pdf";

try {
Document document = new Document();
PdfWriter writer =PdfWriter.getInstance(document, new FileOutputStream(fileName));
document.open();
addDocumentMetaData(document);
addDocumentTitle(document,varMap);
addContent(document,writer,varMap, approvalStatus);
document.close();
} catch (Exception e) {
e.printStackTrace();
}
}

// iText allows to add metadata to the PDF which can be viewed in your Adobe Reader under File -> Properties

private static void addDocumentMetaData(Document document) {
document.addTitle("Order Approval Summary");
document.addSubject("Sales Order Summary Document");
document.addKeywords("Sales Orchestraction Project,Order Approval Summary");
document.addAuthor("Sales Orchestraction Project");
document.addCreator("Sales Orchestraction Project");
}

private static void addDocumentTitle(Document document, Map contentMap) throws DocumentException {
Paragraph preface = new Paragraph();
// Adding one empty line
addEmptyLine(preface, 1);
// Adding a Document header
preface.add(new Paragraph("APPROVAL FLOW FOR ORDER : " + (String)contentMap.get("PURCHASE ID"), catFont));
addEmptyLine(preface, 1);
// A Small body: Report generated by  _Name, _Date
preface.add(new Paragraph("Report Generated by Order Approval Flow,  " +new Date(), redFont));
addEmptyLine(preface, 2);
document.add(preface);

}

private static void addContent(Document document, PdfWriter writer, Map contentMap, String approvalStatus) throws DocumentException {
// Add a table
createTable(document, writer,contentMap, approvalStatus);
}

private static void createTable(Document document, PdfWriter writer, Map varMap, String approvalStatus) throws BadElementException, DocumentException {
PdfPTable table = new PdfPTable(3);
table.setTotalWidth(500);
table.setLockedWidth(true);

PdfPCell c1 = new PdfPCell(new Phrase("ORDER APPROVAL SUMMARY", new Font(smallBold)));
c1.setColspan(3);
c1.setFixedHeight(40);
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);
PdfContentByte cb = writer.getDirectContent();
BarcodeEAN codeEAN = new BarcodeEAN();
codeEAN.setCodeType(Barcode.EAN13);

String s= "000000000000"+(String)varMap.get("PURCHASE ID"); // twelve zeros prepended

codeEAN.setCode(s.substring(s.length()-13));
Image imageEAN = codeEAN.createImageWithBarcode(cb, null, null);
c1 = new PdfPCell(imageEAN,false);
c1.setHorizontalAlignment(Element.ALIGN_RIGHT);
c1.setColspan(3);
c1.setBorder(1);
c1.setFixedHeight(5);
table.addCell(c1);

c1 = new PdfPCell(new Phrase("ORDER ID",new Font(smallBold)));
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);

c1 = new PdfPCell(new Phrase((String)varMap.get("PURCHASE ID"),new Font(smallBold)));
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
c1.setColspan(2);
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);

c1 = new PdfPCell(new Phrase("KEY INDICATORS",new Font(smallBold)));
c1.setRowspan(8);
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
c1.setVerticalAlignment(Element.ALIGN_CENTER);
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);
Iterator entries = varMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
c1 = new PdfPCell(new Phrase( (String)entry.getKey(),new Font(subFont)));
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
table.addCell(c1);

c1 = new PdfPCell(new Phrase((String)entry.getValue(),new Font(subFont)));
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
c1.setBackgroundColor(new BaseColor(255,181,188));
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);

}

c1 = new PdfPCell(new Phrase("APPROVAL STATUS",new Font(subFont)));
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
table.addCell(c1);
c1 = new PdfPCell(new Phrase(approvalStatus,new Font(subFont)));
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
c1.setBorderColor(new BaseColor(Color.RED));
c1.setBorder( Rectangle.BOX);
c1.setBorderWidth(1);
c1.setBackgroundColor(new BaseColor(227,7,7));
c1.setColspan(2);
table.addCell(c1);
document.add(table);

}
private static void addEmptyLine(Paragraph paragraph, int number) {
for (int i = 0; i < number; i++) {
paragraph.add(new Paragraph(" "));
}
}

}

Feel free to add your own custom style to the pdf. You can even add your corporate logo into the snapshot. 🙂

Secondly use the Embedded Java Activity from the BPEL process to call this custom class and pass a Map containing the key indicators for the sales order instance.

You can even pass the entire XML from the embedded java activity and unmarshall the XML in the custom class. Its a matter of choice.

image

Here is the java code for the embedded activity

System.out.println("<<<=====================Entering JAVA Embedding===============================>>>");
Map variableMap = new HashMap();
try{
String approvalStatus ="ORDER AUTOMATICALLY APPROVED";
// Setting the Map
variableMap.put("FULFILMENT ID",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:fulfillmentID")).getFirstChild().getNodeValue());

variableMap.put("ORDER GUID",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:orderGUID")).getFirstChild().getNodeValue());

variableMap.put("PURCHASE ID",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:purchaseID")).getFirstChild().getNodeValue());

variableMap.put("ORDER AMOUNT",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:orderAmount")).getFirstChild().getNodeValue());

variableMap.put("DISCOUNT AMOUNT",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:discountAmount")).getFirstChild().getNodeValue());

variableMap.put("TAX AMOUNT",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:taxAmount")).getFirstChild().getNodeValue());

variableMap.put("PROMISED SHIP TIME",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:promisedShipDateTime")).getFirstChild().getNodeValue());

variableMap.put("BOOKING TIME",((XMLElement)getVariableData("salesOrderIndicator","/ns12:SALOrderIndicator/ns12:bookingDateTime")).getFirstChild().getNodeValue());

if(((XMLElement)getVariableData("ruleProcessingResponse","payload","/ns20:callFunctionStatefulDecision/ns20:resultList/ns21:OrderClass/ns21:isManualOrder")).getFirstChild().getNodeValue()  == "true")
{
approvalStatus= "MANUAL APPROVAL NEEDED";
}
// Calling the Custom Class with the Map and approval status
OrderApprovalDocument createDoc = new OrderApprovalDocument();
createDoc.createPDFSummary(variableMap,approvalStatus);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println("<<<=====================Leaving JAVA Embedding===============================>>>");

Open the .bpel file inside the composite and import the required classes for the Java Embedding activity using the <bpelx:exec/> extension.

image

Create a JAR from the custom java class project as OrderApprovalDocument.jar and add it to the classpath of the composite project.

image

Also copy the OrderApprovalDocument.jar into the SCA-INF\lib directory of the project inside the composite application.

Deploy the onto the soa server of the SOA Suite 11g runtime domain and test the process with a simple order message.

You can see the generated PDF in the directory mentioned in the custom java class. The pdf file is appended with the instance order number.

.

Anatomy of a process in Oracle SOA Suite : Behind the Scenes Processing

For those of us who are working with Oracle SOA Suite 11g and who interested to figure out in brief as what happens behind the scenes for synchronous and asynchronous processes, what is the thread and transaction model etc, here is a document that briefly describes it.

Follow any of the link below to view it.

https://beatechnologies.wordpress.com/?attachment_id=735

https://beatechnologies.wordpress.com/wp-content/uploads/2011/07/anatomy-of-soa-suite-processes-behind-the-scenes.pdf

.

 

Fault Handling in Oracle SOA Suite : Advanced Concepts

This tutorial is meant to cover extensively the mechanism that we can adopt for Fault Management for a SOA Suite composite. It will deal with a fairly overall strategy for handling faults and dealing with them in various ways.

Before diving more into advanced concepts of Handling Faults let me present a small introduction covering the basics of a Service composite.

Basic Architecture of a Service Composite in Oracle SOA Suite

image

  1. Service components – BPEL Processes, Business Rule, Human Task, Mediator. These are used to construct a SOA composite application. A service engine corresponding to the service component is also available.
  2. Binding components – Establish connection between a SOA composite and external world.
  3. Services provide an entry point to SOA composite application.
  4. Binding defines the protocols that communicate with the service like SOAP/HTTP, JCA adapter etc.
  5. WSDL advertises capabilities of the service.
  6. References enables a SOA composite application to send messages to external services
  7. Wires enable connection between service components.

Coming to Fault Handling in a composite there are primarily two types of faults

  1. Business faults – Occurs when application executes THROW activity or an invoke receives fault as response. Fault name is specified by the BPEL process service component. This fault is caught by the fault handler using Fault name and fault variable.
  2. Runtime faults – Thrown by system. Most of these faults are provided out of the box. These faults are associated with RunTimeFaultMessage and are included in http://schemas.oracle.com/bpel/extension namespace.

Oracle SOA Suite gives us an option of configuring fault and fault actions using policies. This means that we can create policies in response to a specific type of exception. Policies are defined in a file that by default is called fault-policies.xml

Policies for fault handling consist of two main elements:

  1. The fault condition that activates the policy block—we specify what type of fault(s) the policy is relevant for. We can then apply even more finer grained policy and actions based on error codes, error messages etc.
  2. The action(s) that should be performed when the condition is satisfied. An action for an fault may be to retry it for a certain number of time at a specified interval, or to mark it in recovery for human intervention, use a custom Java code or simply to throw the fault back. If the fault is rethrown then if we have specified any explicit ‘catch’ block in our BPEL process that will be executed.

It should also be noted that fault policies need to be explicitly associated with composites, components, or references. This is done in a fault-bindings.xml file. Fault bindings link the composite, specific components in the composite, or specific references in the components on the one hand to one of the fault policies on the other.

Have a look at the diagram below to understand a mechanism to throw a fault from a service composite, identify the fault type and then take necessary action.

image

The following post will try and cover all aspects of what is covered in the diagram above.

Consider the following fault-policies.xml. Read the comments in the XML to understand what each of the condition, action and property is about.

<faultPolicies xmlns="http://schemas.oracle.com/bpel/faultpolicy">
<faultPolicy version="2.0.1" id="CompositeFaultPolicy" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.oracle.com/bpel/faultpolicy">
<Conditions>
<!-- Conditions can be fine grained to include Actions based on Error Codes. If a remotefault occurs check whether it is a WSDLReadingError. If yes then rethrow it else retry it."-->
<faultName xmlns:bpelx="http://schemas.oracle.com/bpel/extension" name="bpelx:remoteFault">
<condition>
<test>$fault.code/code="WSDLReadingError"</test>
<action ref="ora-rethrow-fault"/>
</condition>
<condition>
<action ref="ora-retry"/>
</condition>
</faultName>
<faultName xmlns:bpelx="http://schemas.oracle.com/bpel/extension" name="bpelx:bindingFault">
<condition>
<action ref="java-fault-handler"/>
</condition>
</faultName>
<faultName xmlns:bpelx="http://schemas.oracle.com/bpel/extension" name="bpelx:runtimeFault">
<condition>
<action ref="java-fault-handler"/>
</condition>
</faultName>
</Conditions>
<Actions>
<!-- This Action will invoke a Custom Java Class to process Faults. Also depending upon the returnValueanother action will be invoked whic is specified by the ref attribute. This demosntrates chaining of Actions"-->
<Action id="java-fault-handler">
<javaAction className="com.beatech.faultapp.CustomFaultHandler" defaultAction="ora-human-intervention" propertySet="properties">
<returnValue value="Manual" ref="ora-human-intervention"/>
</javaAction>
</Action>
<!-- This Action will mark the instance as "Pending for Recovery" in the EM console -->
<Action id="ora-human-intervention">
<humanIntervention/>
</Action>
<!--This is an action will bubble up the fault to the Catch Blocks-->
<Action id="ora-rethrow-fault">
<rethrowFault/>
</Action>
<!--This action will attempt 3 retries with intervals of 120 seconds -->
<Action id="ora-retry">
<retry>
<retryCount>3</retryCount>
<retryInterval>120</retryInterval>
<retryFailureAction ref="java-fault-handler"/>
</retry>
</Action>
<!--This action will cause the instance to terminate-->
<Action id="ora-terminate">
<abort/>
</Action>
</Actions>
<!--Properties can be used to pass values to the Java class as a Map that can be used by the Class -->
<Properties>
<propertySet name="properties">
<property name="myProperty1">propertyValue1</property>
<property name="myProperty2">propertyValue2</property>
<property name="myPropertyN">propertyValueN</property>
</propertySet>
</Properties>
</faultPolicy>
</faultPolicies>

Inside the custom Java fault handler we can also use a switch that acts on the returnValue to chain another Action.

<javaaction classname="com.beatech.faultapp.CustomFaultHandler" defaultaction="ora-rethrow">
<returnValue ref="ora-rethrow" value="Rethrow"/>
<returnvalue ref="ora-terminate" value="Abort"/>
<returnvalue ref="ora-retry" value="Retry"/>
<returnvalue ref="ora-human-intervention" value="Manual"/>
</javaaction>

The next step will be to create a fault-bindings.xml file to simply bound the fault policy file to the composite.

<faultPolicyBindings version="2.0.1" xmlns="http://schemas.oracle.com/bpel/faultpolicy">
<composite faultPolicy="CompositeFaultPolicy"/>
</faultPolicyBindings>

Finally we have to add two properties in the composite.xml to let the composite know about them

<property name=”oracle.composite.faultPolicyFile”>fault-policies.xml></property>
<property name=”oracle.composite.faultBindingFile”>fault-bindings.xml></property>

We can use different names and locations for the fault policies and fault bindings files, by setting the properties oracle.composite.faultPolicyFile and oracle.composite.faultBindingFile in the composite.xml to configure these custom files.

For example we can refer to these files even from the MDS.

<property name=”oracle.composite.faultPolicyFile”>oramds://apps/policy/fault-policies.xml></property>
<property name=”oracle.composite.faultBindingFile”>oramds://apps/policy/fault-bindings.xml></property>

Once we hit a fault in our composite that has a custom Java Action the java class CustomFaultHandler will be instantiated. Here is one example of a Java Class.

The custom Java class has to implement the interface IFaultRecoveryJavaClass that defines two methods i.e handleRetrySuccess and handleFault. The custom Java class CustomFaultHandler has access to the IFaultRecoveryContext containing information about the composite, the fault, and the policy.

If the fault is thrown by a BPEL process we can check if it’s an instanceof BPELFaultRecoveryContextImpl to get further fault details.

package com.beatech.faultapp;
import com.collaxa.cube.engine.fp.BPELFaultRecoveryContextImpl;</pre>
import com.oracle.bpel.client.config.faultpolicy.IBPELFaultRecoveryContext;
import java.util.Map;
import oracle.integration.platform.faultpolicy.IFaultRecoveryContext;
import oracle.integration.platform.faultpolicy.IFaultRecoveryJavaClass;

public class CustomFaultHandler implements IFaultRecoveryJavaClass {
public CustomFaultHandler() {
super();
}

public void handleRetrySuccess(IFaultRecoveryContext iFaultRecoveryContext) {
System.out.println("Retry Success");
handleFault(iFaultRecoveryContext);
}

public String handleFault(IFaultRecoveryContext iFaultRecoveryContext) {
//Print Fault Meta Data to Console
System.out.println("****************Fault Metadata********************************");
System.out.println("Fault policy id: " + iFaultRecoveryContext.getPolicyId());
System.out.println("Fault type: " + iFaultRecoveryContext.getType());
System.out.println("Partnerlink: " + iFaultRecoveryContext.getReferenceName());
System.out.println("Port type: " + iFaultRecoveryContext.getPortType());
System.out.println("**************************************************************");
//print all properties defined in the fault-policy file
System.out.println("Properties Set for the Fault");
Map props = iFaultRecoveryContext.getProperties();
for (Object key: props.keySet())
{
System.out.println("Key : " + key.toString()  + " Value : " + props.get(key).toString());
}
//Print Fault Details to Console if it exists
System.out.println("****************Fault Details********************************");
if(iFaultRecoveryContext instanceof BPELFaultRecoveryContextImpl)
{
BPELFaultRecoveryContextImpl bpelCtx = (BPELFaultRecoveryContextImpl) iFaultRecoveryContext;
System.out.println("Fault: " + bpelCtx.getFault());
System.out.println("Activity: " + bpelCtx.getActivityName());
System.out.println("Composite Instance: " + bpelCtx.getCompositeInstanceId());
System.out.println("Composite Name: " + bpelCtx.getCompositeName());
System.out.println("***********************************************************");
}
//Custom Code to Log Fault to File/DB/JMS or send Emails etc.
return "Manual";
}
}

Now here is what we can do from here

  1. Log the fault/part of fault in a flat file, database or error queue in a specified enterprise format.
  2. We can even configure to send an Email to the support group for remedy and action. (Use custom Java Email code or use the UMS java/ejb APIs to do so)
  3. Return a flag with an appropriate post Action. This flag determines what action needs to be taken next

The java class would require the SOA and BPEL runtime in classpath to compile and execute.

image

To make sure that when the composite instances faults out and the fault-policy.xml is able to instantiate this class we have to make it available in the server’s classpath.

There are a couple of ways to do that. Here is one of the way to achieve it.

  1. Compile your Java Project and export it as a jar file (say CustomFaultHandling.jar)
  2. Go to <Middleware Home>\Oracle_SOA1\soa\modules\oracle.soa.bpel_11.1.1 directory of your Oracle SOA Suite installation.
  3. Copy the CustomFaultHandling.jar in the above directory
  4. Unjar the oracle.soa.bpel.jar and edit the MANIFEST.MF to add an entry of the above jar in the classpath.

image

  1. Pack the jar again and restart both the Managed and Admin Server.
  2. Another way is to drop the CustomFaultHandling.jar in the <Middleware Home>\Oracle_SOA1\soa\modules\oracle.soa.ext_11.1.1 and run Ant on the build.xml file present in the directory.

Interestingly we also have an option in the EM console to retry all faults for a composite by setting some values in custom MBeans. They are available as Advanced BPEL properties in the SOA Infra engine.

The MBean that allows recovery of faulted instances is RecoveryConfig. It has two options for retrying

  1. RecurringScheduleConfig : A recovery window may be specified (most probably off peak hours) to recover all faulted instances. Messages being recovered can be throttled by limiting the number of messages picked up on each run by specifying the maxMessageRaiseSize.
  2. StartupScheduleConfig : With this setting on all faulted instances are automatically retried when the soa server is booted up for restart.

More details on how to use RecoverConfig Mbean can be found here

http://download.oracle.com/docs/cd/E14571_01/relnotes.1111/e10133/soa.htm#RNLIN1052

image

There is a small lacuna though here. It is not always possible to recover automatically. Auto-recovery is subject to some conditions.

Consider the scenarios below.

  1. Scenario A: The BPEL code uses a fault-policy and a fault is handled using the “ora-human-intervention” activity, then the fault is marked as Recoverable and the instance state is set to “Running”.
  2. Scenario B: The BPEL code uses a fault-policy and a fault is caught and re-thrown using the “ora-rethrow-fault” action, then the fault is marked as Recoverable and the instance state is set to “Faulted”; provided the fault is a recoverable one (like URL was not available).

In Scenario A, the Recoverable fault CANNOT be auto-recovered using the RecoveryConfig MBean.

In Scenario B, the Recoverable fault can be auto-recovered on server startup and/or pre-scheduled recovery.

All is not lost however. The instances can still be recovered from the console though. However for most practical purposes it isn’t desirable that a huge number of composite instances that are marked for recovery for a remote fault (say end point not available) are retried automatically. It is natural that we will yearn to automate this part as well.

Here is a sample code that gets all remote faults that are marked as recoverable from the Custom Java Class and retries them.

package com.beatech.salapp;

import java.util.Hashtable;
import java.util.List;

import javax.naming.Context;

import oracle.soa.management.facade.Fault;
import oracle.soa.management.facade.FaultRecoveryActionTypeConstants;
import oracle.soa.management.facade.Locator;
import oracle.soa.management.facade.LocatorFactory;
import oracle.soa.management.facade.bpel.BPELServiceEngine;
import oracle.soa.management.util.FaultFilter;

public class FaultRecovery {

private Locator locator = null;
private BPELServiceEngine mBPELServiceEngine;

public FaultRecovery() {
locator = this.getLocator();
try {
mBPELServiceEngine =
(BPELServiceEngine)locator.getServiceEngine(Locator.SE_BPEL);
} catch (Exception e) {
e.printStackTrace();
}
}

public Hashtable getJndiProps() {
Hashtable jndiProps = new Hashtable();
jndiProps.put(Context.PROVIDER_URL,"t3://localhost:4003/soa-infra");
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
jndiProps.put(Context.SECURITY_PRINCIPAL, "weblogic");
jndiProps.put(Context.SECURITY_CREDENTIALS, "welcome123");
jndiProps.put("dedicated.connection", "true");
return jndiProps;
}

public Locator getLocator() {

try {
return LocatorFactory.createLocator(getJndiProps());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

public void recoverFaults() {
try {
System.out.println("Get All Recoverable Faults");
/* Set Search Filters like composite Name, Instance Ids, fault Names etc.
Here I am setting the setRevoverable filter to true to retrieve all Recoverable Faults.
Also I am setting filter on faultName as i want to programatically retry all remote Faults for resubmission.
*/
FaultFilter filter = new FaultFilter();
filter.setFaultName("{http://schemas.oracle.com/bpel/extension}remoteFault");
filter.setRecoverable(true);

//Get faults using defined filter
List<Fault> faultList = mBPELServiceEngine.getFaults(filter);
System.out.println("=========================Recoverable Faults==================================================");
for (Fault fault : faultList) {
System.out.println("=============================================================================================");
System.out.println("Composite DN: " + fault.getCompositeDN().getStringDN());
System.out.println("Composite Instance ID: " + fault.getCompositeInstanceId());
System.out.println("Component Name: " + fault.getComponentName());
System.out.println("Component Instance ID: " + fault.getComponentInstanceId());
System.out.println("Activity Name: " + fault.getLabel());
System.out.println("Fault ID: " + fault.getId());
System.out.println("Fault Name: " + fault.getName());
System.out.println("Recoverable flag: " + fault.isRecoverable());
System.out.println("Fault Message: " + fault.getMessage());
}
// Recovering all Recoverable Faults in a single call
//mBPELServiceEngine.recoverFaults(faultList.toArray(new Fault[faultList.size()]), FaultRecoveryActionTypeConstants.ACTION_RETRY);

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

}

public static void main(String[] args) {
FaultRecovery faultRecovery = new FaultRecovery();
faultRecovery.recoverFaults();
}
}

Replace the values in the property map with the ones in your server. Remember to give the managed server port in the Provider URL. Run the Java Class and you would see that the recoverable faults are printed.

image

Verify the same from the console

image

Run the Java program again but this time uncomment the line below

//mBPELServiceEngine.recoverFaults(faultList.toArray(new Fault[faultList.size()]), FaultRecoveryActionTypeConstants.ACTION_RETRY);

This will result in all faults marked with <strong>Recovery</strong> icon to be retried. So if the remote endpoint is responding and active now the processes will complete.

There are a host of other things that we can do in this Java Class.

Using the <strong>BPELServiceEngine</strong> object we can write messages to the BPEL audit trail, inspect the current activity, and read and update the values of BPEL variables.

The following code snippet if inserted in the code will replace any process variable with a new value before retrying. (May be used in case of Binding or Business Faults)

//Get faults using defined filter
List<Fault> faultList = mBPELServiceEngine.getFaults(filter);
System.out.println("=========================Recoverable Faults ==================================================");
for (Fault fault : faultList)
{
System.out.println("=========================Read Process Variables =========================================");
//Get Process Instance variables from fault object
String[] variables = mBPELServiceEngine.getVariableNames(fault);
System.out.println("Process Instance Variables:");
for (int i = 0; i < variables.length; i++)
{
System.out.println("Variable Name: " + variables[i]);
}
//Get Input Variable Data from the Activity, Modify it with a new value and recover
System.out.println("=========================Replace Variable and Recover ====================================");
System.out.println("Activity Input Variable Data:");
String value =mBPELServiceEngine.getVariable(fault, "activityRequestMessage");
System.out.println("Present value: " + value);
value = value.replace("remoteFault", "Modified Value");
System.out.println("New value: " + value);
mBPELServiceEngine.setVariable(fault,"activityRequestMessage",value);

// Recover Faults one by one
mBPELServiceEngine.recoverFault(fault,FaultRecoveryActionTypeConstants.ACTION_RETRY,null);
}

The following JARS would be required in the classpath for the above Java Code

image

 

<MWHOME>\oracle_common\modules\oracle.fabriccommon_11.1.1\fabric-common.jar
<MWHOME>\jdeveloper\soa\modules\oracle.soa.mgmt_11.1.1\soa-infra-mgmt.jar
<MWHOME>\wlserver_10.3\server\lib\weblogic.jar
<MWHOME>\jdeveloper\soa\modules\oracle.soa.fabric_11.1.1\oracle-soa-client-api.jar
<MWHOME>\oracle_common\webservices\wsclient_extended.jar

The  post discussed the different approaches and strategies for handling faults in a composite in SOA Suite. Let me conclude this article by describing a few best practices around Fault Handling.

Oracle SOA Suite Fault Handling Best Practices

  1. Create fault (catch block) for each partner link. For each partner link, have a catch block for all possible errors. Idea is not to let errors go to catchAll block.
  2. CatchAll should be kept for all errors that cannot be thought of during design time.
  3. Classify errors into various types – runtime, remote, binding, validation, Business errors etc.
  4. Notification should be setup in production, so that, errors are sent to concerned teams by E-Mail. Console need not be visited for finding out status of execution.
  5. Use Catch Block for non-partner link error.
  6. Every retry defined in fault policy causes a commit of the transaction. Dehydration will be reached and threads released.
  7. Automated recovery can be created by creating a fault table, persisting the queue and having an agent  to re-submit the job (For example writing a Timer agent to invoke the Java code we wrote to recover instances) . Can be achieved through scripts. Use only PUBLISHED API of ESB or QUEUE (AQ etc.) for re-submission. Another example would be to use WLST to change the RecoveryConfig MBean to configure recovery window to retry all faulted instances.
  8. Handle Rollback fault by providing ‘No Action’ in fault policy.
  9. Remember – Receive, OnMessage, On Alarm, Wait, CheckPoint (Activity for forcing Java thread to store its current state to Dehydration store) will cause storing of current state to dehydration store and threads will be released.
  10. Always use MDS to store fault policies and bindings to increase their reuse for multiple composites and projects.

All artifacts used for demonstration in this article can be found at this link.

.

Testing Oracle Business Rules using Java

Foreword

In my previous blog post on Creating and Testing Complex Business Rules in Oracle SOA Suite 11g, I have  pretty much covered how we can create a fairly complex rule and test it in multiple ways. In the same article I have explored how we can Test business rules at design time using RL functions or use Enterprise Manager to test them. We have also seen as how rules in Oracle SOA Suite can be exposed as standard soap service and thus can be invoked by multiple parties.

However one practical problem in using these above approaches is that we may have Business Rules that might run into hundreds of assertions and rule conditions that may prove to be a nightmare to test.

So how do we go about it. One elegant way would to create Test cases for business rules in SOAP UI and test the rule service like we generally test any other soap services. SOAP UI provides a wonderful framework for testing soap based services where in we can use assertions on the responses and even print test reports.

Another approach and an old school one is to write Java classes to invoke your Business rules. Oracle Business rules can be instantiated and invoked using Java code as well. This opens up the possibility of creating JUnit type test cases and test suites for your Business rules and test them extensively. This way we can further use any code coverage tool also to see how much of the rules are we testing already.

In the following article I will show how we can write a custom Java class to invoke and test a Business rule created in Oracle SOA Suite. For the purpose of this tutorial I will use the same example and code as used in the previous article about Oracle Rules. You can find the article at this link and even download the source code.

Prerequisites

Oracle JDeveloper 11g with SOA Suite Extensions

The Solution

Download the Business rules sample from the previous tutorial from the above link and Open it in JDeveloper.

Create a New Java Project inside the same application i.e BusinessRuleApplication wherein we can create our Java class to run our business rules.

image

image

Now Create a new Java Class called RuleTester in the Java Project that has been created.

image

image

To create a Java Class that can initialize and invoke Oracle Business rules we would need to add the standard Oracle Rules jars to our project’s classpath. Add the following libraries to the project dependencies as shown in the figure.

image

Also note that when we create any Business rules in Oracle SOA Suite 11g in JDeveloper it creates the JAXB classes for them. Make sure that even these classes are added to the project classpath.

image

The libraries to be added are also mentioned in the table below.

Oracle Rules Editor Component
Oracle Rules Dictionary Component
Oracle Rules
JDeveloper Runtime
Java EE 1.5
Oracle JDBC
ADF Model Runtime
ADF DVT Faces Databinding Runtime
Trinidad Runtime 11
Oracle JEWT

We are good now as far as creating the Project and adding rules libraries is concerned. Now let us jump on to creating the Java code to invoke and test the rule.

The code used to test the decision function created in the rule is given below.

package com.beatechnologies.sample.rules.testclient;

import com.wordpress.beatechnologies.gradeallocation.CandidateInformationType;
import com.wordpress.beatechnologies.gradeallocation.ObjectFactory;
import com.wordpress.beatechnologies.gradeallocation.CandidateGradeType;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import oracle.rules.sdk2.decisionpoint.DecisionPoint;
import oracle.rules.sdk2.decisionpoint.DecisionPointBuilder;
import oracle.rules.sdk2.decisionpoint.DecisionPointDictionaryFinder;
import oracle.rules.sdk2.decisionpoint.DecisionPointInstance;
import oracle.rules.sdk2.dictionary.RuleDictionary;
import oracle.rules.sdk2.exception.SDKWarning;

public class RuleTester {
public RuleTester() {
super();
}

private DecisionPointInstance decPointInstance = null;

// Initialize the Rule Tester Class
public void initialize(String ruleDictionary, String decisionFunctionName) throws Exception {

if (ruleDictionary == null || decisionFunctionName == null)
throw new Exception("Enter rule dictionary location and decision service name for Rule Engine to successfully initialize.");
// Load Decision Point using Dictionary on File System
DecisionPoint decPoint =new DecisionPointBuilder().with(decisionFunctionName).with(loadRuleDictionary(ruleDictionary)).build();
decPointInstance = decPoint.getInstance();
System.out.println("Rule Tester Class is now Initialized");
return;
}

// Loads the rule dictionary from the specified dictionaryPath to return a rule dictionary object
private static RuleDictionary loadRuleDictionary(String dictionaryPath) throws Exception {

RuleDictionary ruledictionary = null;
Reader reader = null;
Writer writer = null;
try {
reader = new FileReader(new File(dictionaryPath));
ruledictionary = RuleDictionary.readDictionary(reader, new DecisionPointDictionaryFinder(null));
List<SDKWarning> warnings = new ArrayList<SDKWarning>();
ruledictionary.update(warnings);
if (warnings.size() > 0) {
System.err.println("Rule Dictionary returned the followingv validation warnings: " + warnings);
}
}
finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ioe)
{
ioe.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
return ruledictionary;
}

// Executes the Rule Decision Function by taking candidateInformationlist input and return the results.
public List runRules(ArrayList candidateInformationInputList) throws Exception {

List<Object> candidateGradeList = null;
if (decPointInstance == null)
throw new Exception("RuleTester not intialized.");
System.out.println("Running Rules");
if (candidateInformationInputList != null)
{
decPointInstance.setInputs(candidateInformationInputList);
// invoke the decision point with Candiate Marks Information
candidateGradeList = decPointInstance.invoke();
if (candidateGradeList == null || candidateGradeList.isEmpty())
{
System.out.println("RuleTester: No results returned by Rules Service");
}
else
System.out.println("RuleTester: " + candidateGradeList.size() + " result(s) returned.");
}
return candidateGradeList;
}

// Creates a test data candidate Information object for input to the rule engine which is an XML type Fact.
public static CandidateInformationType createTestData() throws JAXBException {

// Create the sample input XML as string
String candidateMarksInformation =
"<exam:CandidateInformation xmlns:exam=\"https://beatechnologies.wordpress.com/GradeAllocation\">\n" +
"  <exam:name>Arun</exam:name>\n" +
" <exam:rollNumber>7000001</exam:rollNumber>\n" +
"<exam:class>X</exam:class>\n" +
" <exam:section>SCIENCE</exam:section>\n" +
" <exam:remarks>Good</exam:remarks>\n" +
"<exam:subject>\n" +
"<exam:subjectName>ENGLISH</exam:subjectName>\n" +
"<exam:subjectCode>001</exam:subjectCode>" +
"<exam:subjectMark>85</exam:subjectMark>" + "</exam:subject>" +
"<exam:subject>" +
"<exam:subjectName>GEOGRAPHY</exam:subjectName>" +
"<exam:subjectCode>002</exam:subjectCode>" +
"<exam:subjectMark>85</exam:subjectMark>" + "</exam:subject>" +
"</exam:CandidateInformation>";

CandidateInformationType candidateInformation = null;

// XML Facts are represented by JAXB types.
// Use the Generated types to parse an xml test message into an JAXB element
JAXBContext jaxbContext = JAXBContext.newInstance("com.wordpress.beatechnologies.gradeallocation");
Unmarshaller unMarsh = jaxbContext.createUnmarshaller();
ByteArrayInputStream is =new ByteArrayInputStream(candidateMarksInformation.getBytes());
Object obj = unMarsh.unmarshal(is);
JAXBElement jaxbElement = (JAXBElement)obj;
candidateInformation = (CandidateInformationType)jaxbElement.getValue();

// Print Candidate Information input to stdout by marshalling it back to xml
System.out.println("Candidate Information Avaliable Is:");
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
marshaller.marshal(obj, System.out);

return candidateInformation;
}

// Main funtion to Test the Business Rule as a Java class in JDeveloper

public static void main(String[] args) throws Exception {

RuleTester tester = new RuleTester();

// Load the Rules Dictionary, provide location of .rules file and Decision Function name
tester.initialize("C:\\JDeveloper\\mywork\\BusinessRulesApplication\\BusinessRulesProject\\" +
"oracle\\rules\\com\\obr\\sample\\app\\GradeAllocationRule.rules","GradeAllocationRule_decideGrade");

// Create an array of inputs that match the CandidateInformationType xml used in the  Rules Decision Function
ArrayList candidateInformationInputList = new ArrayList();
candidateInformationInputList.add(createTestData());

// Execute the Rules passing in the candidateInformation input
List candidateGradeList = tester.runRules(candidateInformationInputList);

if (candidateGradeList != null) {
//Candidate Grade Object returned from rules decision function
CandidateGradeType result = (CandidateGradeType)candidateGradeList.get(0);

// Print Candidate Grade XML returned from Rules Engine to stdout
JAXBContext jaxbContext =JAXBContext.newInstance(CandidateGradeType.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
ObjectFactory objFactory = new ObjectFactory();
marshaller.marshal(objFactory.createCandidateGrade(result), System.out);
}
}
}

On a high level here is what the code does

Initializes the Decision function inside the Rule project. We have to provide the absolute path to .rule file that gets created in the Rules project directory and also the name of decision service.

image

It then reads a hard coded xml string that is a mocked input to the decision service. However we cannot pass a string content to the rule function. We have to unmarshall the xml string into a JAXB object before we can send it to the service.

You can see more about JAXB (Java Architecture for XML Binding) at the below link

http://www.oracle.com/technetwork/articles/javase/index-140168.html

We invoke the business rule service and get the response in a list. Simply take the first child of the list and marshall it into an xmlstring from a JAXB object.

Also write some sysouts to display the input as well as the output to the console.

We are now ready to run this Java class and test the rule. Remember we do not need to have a server runtime for this. We can simply run this as a simple java program.

image

After the Java class compiles and is executed we can see the following output in the console

image

We can now create a JUNIT test suite to read from multiple input files and assert the rule results. This approach would be very beneficial if we have hundreds of rules created in a Decision Service and we require a thorough testing for them.

The entire rules application along with the Java Project created for the tutorial can be downloaded from here.