Tuesday, May 1, 2012

Scheduling a Process in SOA 11g


"Scheduling" is one of the most common tasks that many projects require. We may need to initiate a process on a specific time based on a schedule i.e,  in a particular day of a month or in an hour/min in a day .This feature is not available out-of-box  in SOA 11g. Most common and easy to use approach is to use the Quartz scheduler and its supported java classes that can be used to schedule the process based on a time schedule.

Outline of the Steps to Schedule a BPEL Process using Quartz are as follows:

0. Create and make the wsdl of the BPEL process that needs to be scheduled available.

1. Create a WebService Proxy for the BPEL process that you may want to schedule.
This will create a Client and a Port java classes which can be used to invoke the bpel process operations from java code.

2. Create a Job( class that implements org.quartz.Job) that calls the BPELProcess operation(s)( using its client and port classes that were created as a result of step 1).

3. Create a JobTrigger class trigger that will trigger the above Job in a specific schedule.

In Detail here is  how it works :

Assuming the Process to be scheduled is HelloWorld Process and its wsdl is - http://localhost:8001/soa-infra/services/default/HelloWorldProject/HelloProcess.wsdl

Detailed Steps :

a. Create a Generic Application and name it SoaScheduleApp.

b. Create a Project(say SoaSchedulerProject) and select java,webservices as project technologies.

c. Create a new Web Service Proxy (webservices),selecting JAX-WS client style for the HelloWorld wsdl -
 http://localhost:8001/soa-infra/services/default/HelloWorldProject/HelloProcess.wsdl. You may prefer to copy the wsdl into the project. Select some package name and root package for genric types. Click finish.

d.  This will create the HelloProcess_client_ep.java which is a client Service that contains methods returning Port of the Service.( ex: getHelloProcess_pt()). The Port is of type HelloProcess interface which represents  a Port that defines operations of the Service.

e. Add Quartz Library to the project.
   Click on “Application”-> “Project Properties”,-> " Libraries and Classpath"->“Add JAR/Directory”.
   Select in your JDeveloper home “…\jdeveloper\soa\modules\quartz-all-1.6.5.jar”. Click “Select”.
   Click Ok.

f.    Create a Job component - a Java class which contains the following code.

package sample.oracle.otn.soascheduler.job;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import sample.oracle.otn.soascheduler.proxy.Helloprocess_client_ep;
import sample.oracle.otn.soascheduler.proxy.HelloProcess;
import javax.xml.ws.WebServiceRef;
import org.quartz.Job;
import org.quartz.JobExecutionContext;


import sample.oracle.otn.soascheduler.proxy.Helloprocess_client_ep;


public class DemoJob implements Job{
    @WebServiceRef
private static Helloprocess_client_ep helloworldprocess_client;
public DemoJob() {
        helloworldprocess_client = new Helloprocess_client_ep();
 }
public void execute(JobExecutionContext jobExecutionContext) {
        DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
System.out.println("HelloWorldJob started");
try {
          helloworldprocess_client = new Helloprocess_client_ep();
HelloProcess helloWorldProcess = helloworldprocess_client.getHelloProcess_pt();
// Add your code to call the desired methods.
System.out.println("HelloWorld Response: " + helloWorldProcess.process("SOAScheduler@" +
df.format(date)));
        } catch (Exception e) {
          System.out.println("HelloWorld Process failed: " + e.toString());
            e.printStackTrace();
        }
    }
}

Observe that it Implements org.quartz.Job and contains the code to call the BPEL process operation in its method execute(..)

g.  Create a Job Scheduler class that triggers the above Job using Cron expressions.
This JobScheduler does 3 things .
i. Creates multiple JobDetails  using org.quartz.JobDetail.
ii. Configure Scheduler time using org.quartz.CronTrigger.setCronExpression(..)
iii. Create the schedule using org.quartz.Scheduler.

Code looks like this :

package sample.oracle.otn.soascheduler.job;


import java.util.Map;


import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.impl.StdSchedulerFactory;


public class JobTrigger {
  public static void main( String[] args ) throws Exception
      {
        //scheduler task details
        JobDetail job = new JobDetail();
        job.setName("someJob");
        job.setJobClass(DemoJob.class);
        //
        JobDetail job2 = new JobDetail();
        job2.setName("someOtherJob");
        job2.setJobClass(DemoJob.class);


        //configure scheduler time
        CronTrigger trigger = new CronTrigger();
        trigger.setName("someJobTrigger");
        trigger.setCronExpression("0/30 * * * * ?");


        //configure scheduler time 2
        CronTrigger trigger2 = new CronTrigger();
        trigger2.setName("someOtherTrigger");
        trigger2.setCronExpression("0/20 * * * * ? 2013");


        //create the schedule
        Scheduler scheduler = new StdSchedulerFactory().getScheduler();
        scheduler.start();
        scheduler.scheduleJob(job, trigger);
        scheduler.scheduleJob(job2, trigger2);
      }


}


h. Thats it !!. Now its time for execution. Run the  JobTrigger  class's main method and observe that your BPEL process is being triggered for every 30 seconds.

For understanding the cron expression refer - http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger

Re-usability:


Now that we are aware of the steps to schedule a bpel service, lets talk about how we can extend and reuse this across projects.
Lets say, in some later point of time, you may need to schedule another process with a different time schedule. example : A Pharma company wants to check its inventory availability every 10 days and make some decisions to reach or not reach to suppliers. For doing so rather than re-inventing the wheel all again we shall do the following.


1. Create WebService Proxy for new Process WSDL.
2. Create another Job component to execute the new process Operation.
3. Reuse the JobTrigger class by making the required additions to create a new JobDetail and schedule the JobDetail using the new cron expression.

Wednesday, November 2, 2011

Correlate one event into multiple instances of the same composite in SOA 11g

Inspired by the blog written by Lucas on this topic , I thought of extending it by providing an implementation for the same ( with a slightly different approach).

The Use-Case is as follows :


We have a SOA Composite Application for the Order process. Whenever a customer places an order, a new instance of this process kicks off. At any one time we will potentially have multiple instances for orders from the same customer. These instances are uniquely identified by the order id.

It happens that the F&A department – because of for example financial difficulties with a certain customer or government regulations regarding certain countries – publishes an event: “Terminate Customer”. This event should result in having all running Orders for that Customer being terminated


In Simple terms, we need to fire an event that abruptly terminates all the running instances of the Order Process for a specific customer.


The Approach :

Customer's can place an Order using the operation 'process' of Order Process bpel component exposed as SOAP service to the external users. The OrderProcess component has another operation 'terminate' that is used to terminate the order instance whose OrderId (the correlation key) matches with the orderId passed in the payload.while terminating the order we match it with the same correlation key(defined earlier in the process) to terminate the right instance of the process.

When the Organisation processing the orders intends to terminate a specific customer's(identified by customerId) running orders , it calls another bpel process with the customerId as its payload. This BPEL process in turn calls a java api ( using Spring Component) passing the customerId as input. The api responses back to the call returning it with the list of all OrderId's for that CustomerId. For each of these OrderId's an AbortEvent is published .

The Abort Event is subscribed by another BPEL process which in turn calls the 'terminate' operation of the OrderProcess bpel compoenent passing the OrderId as input.
Back inside the OrderProcess a mid process receive which is waiting on the terminate operation wakes up and terminates the instance(based on the OrderId correlation key) after doing neccessary processing.

Depicted below the process:

a) The Composite



b) OrderProcess BPEL Component:




c) BPEL Component that calls the Java Api ( using Spring Context Component) to get the Order Id's for which Abort Order Event is published.





d) Spring Component :

The following entry in the SpringContext.xml file is added :

Contents of "MyInterface.java":

public interface MyInterface
{
List getInstancesWithCompositeSensorFilter(String customer);
}

Contents of "MyInterface.class":

package com.sridhar;

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


import javax.naming.Context;



import oracle.soa.management.facade.bpel.BPELInstance;
import oracle.soa.management.facade.ComponentInstance;
import oracle.soa.management.facade.Composite;
import oracle.soa.management.facade.CompositeInstance;
import oracle.soa.management.facade.Locator;
import oracle.soa.management.facade.LocatorFactory;
import oracle.soa.management.facade.Sensor;



import oracle.soa.management.util.ComponentInstanceFilter;
import oracle.soa.management.util.CompositeInstanceFilter;
import oracle.soa.management.util.Operator;
import oracle.soa.management.util.SensorFilter;


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

public List getInstancesWithCompositeSensorFilter(String customer){
System.out.println("Customer Name ---------> "+customer);

List instaceList = new ArrayList();
Locator locator = null;
Hashtable jndiProps = new Hashtable();
jndiProps.put(Context.PROVIDER_URL, "t3://localhost:8001/soa-infra");
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
jndiProps.put(Context.SECURITY_PRINCIPAL, "weblogic");
jndiProps.put(Context.SECURITY_CREDENTIALS, "weblogic123");
jndiProps.put("dedicated.connection", "true");

try
{
locator = LocatorFactory.createLocator(jndiProps);

Composite composite =
(Composite)locator.lookupComposite("default/SpringProjectTrials!1.0");


CompositeInstanceFilter compInstFilter = new CompositeInstanceFilter();
List sFilterList = new ArrayList ();
SensorFilter sFilter =
new SensorFilter("customerIdCompositeSensor" /* sensorname */,
Sensor.SensorDataType.STRING /* type */,
Operator.EQUALS /* operator for comparison */,
customer);
sFilterList.add(sFilter);

compInstFilter.setSensorFilter(sFilterList);
compInstFilter.setCompositeDN(composite.getDN());

List compInstances =
composite.getInstances(compInstFilter);

if (compInstances != null && compInstances.size()>0) {


for (CompositeInstance instance : compInstances)
{
// setup a component filter
ComponentInstanceFilter cInstanceFilter = new ComponentInstanceFilter ();
// get child component instances ..
List childComponentInstances = instance.getChildComponentInstances(cInstanceFilter);
// for each child component instance (e.g. a bpel process)

for (ComponentInstance cInstance : childComponentInstances)
{
BPELInstance bpelInstance = (BPELInstance)cInstance;
//The OrderId which is corelation key for the Order Process BPEL is set as Index 1
instaceList.add(bpelInstance.getIndex(1));
}
}
}//if
else{
System.out.println("====InstaceList -->No Elements Found ===");
}
return instaceList;
}//try
catch (Exception e) {
return instaceList;
}
}
}

e) The BPEL Process Subcribed(Listening) to AbortOrder Event , that inturn calls 'terminate' operation on the Order Process.



That's it about implementing this.


Some Clarifications/ Open Questions :

1) You would have got a doubt about why I created another bpel process ( listed in section 'e' above , that subcribes to the Abort event and routing the same to the terminate operation of the order process , instead of having the mid process recieve activity in the Order Process to directly listen to the Abort Event.

Reason : Having a mid process recieve activity of an event type and setting the correlation set same as defined in the initial receive gives me an error. SCA-50012. I didn't get the straightforward description of the error in the logs , but by trial and error i understood that doing so is probably not supported in this release.

2) In the Spring Context file while retrieving the OrderId's, I am not sure of how to retrieve the field's value of OrderId in the input paylod. ( In 10 g I know we have getField() method on IInstanceHandle interface where the same is not supported in 11g). As a workaround I have created Index( here First Index) on the that field in the Order BPEL process and used the following api to retrieve the Index value thus getting the required OrderId's.

BPELInstance bpelInstance = (BPELInstance)cInstance;
instaceList.add(bpelInstance.getIndex(1));

There should be a better way to do this by directly getting the feild's value. I appreciate input from the blog readers on how to do this as I spent enough time to find how this can be done but unsuccessful.

Comments are welcome...

Friday, July 15, 2011

Using ws-adressing to callback to another process.

Scenario :
1. Service-A.
2. Service-B.
3. Service-C ("Asynchronous request-response" service )

Service C is a legacy service that takes an input from a client , process it and then response back to the same client (by default ). However your business is in such a way that your response should not go to the initial caller(Service-A) but to another process(Service-B). Depicted below is the same:



For this , you may need to tweak the SOAP Headers / WS-Adressing properties by sending the WSA ReplyTo to the Service-C's URI in the call from Service-A to Service-C.


ie., The Service-C's input looks like this:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<span style="font-weight:bold;"> <soap:Header xmlns:ns1="http://schemas.xmlsoap.org/ws/2003/03/addressing">
<ns1:MessageID>ws:uniqueAddress</ns1:MessageID>
<ns1:ReplyTo>
<ns1:Address>http://localhost:8001/soa-infra/services/default/SOATestProject/ServiceB?WSDL</ns1:Address>
</ns1:ReplyTo>
</soap:Header>
</span> <soap:Body xmlns:ns1="http://xmlns.oracle.com/TestApplication_jws/SOATestProject/MyAsynchronousService">
<ns1:process>
<ns1:input>Order123</ns1:input>
</ns1:process>
</soap:Body>
</soap:Envelope>

As you see we give the Service-B's WSDL(http://localhost:8001/soa-infra/services/default/SOATestProject/ServiceB?WSDL) in the Reply To tag.

However care has to be taken while creating Service B that you use the same port type and message types that Service C uses to callback. Otherwise you may end up with exception : javax.xml.ws.soap.SOAPFaultException: oracle.fabric.common.FabricException: Unable to find operation: null

Conclusion:


Not changing your legacy Asynchronous request/response service,you can route the call back response to another service apart from the caller using WS-Addressing.

Monday, July 11, 2011

FTP Adapter - Reading File Name that changes for each instance

We often end up in some business requirements where we need to read a file or write to a file where file name is decided based on the message of the instance.

Consider an example where we have one order process which calls another application asynchronously to validate the order. If the validation is successful , it places a file in a FTPshare with name _Success.xml else _Failed.xml. Now the Order process instance will need to read the file from that FTP share. So here the requirement is to read a file whose name would change for every Order ( ie., for every instance).
So here we need to dynamically change the "file name" of the FTPAdapter configuration.

Providing FileName Dynamically :

1. Create a OutboundHeaderVar(lets say outHeaderVar) in the scope. This is of messageType {http://xmlns.oracle.com/pcbpel/adapter/ftp/}OutboundHeader_msg. (Or) you can select the message Type -> click on browse -> Type Explorer -> Project WSDL files -> Select ftpAdapterOutboundHeader.wsdl -> MessageTypes->OutboundHeader_msg.

2. Drag an assign activity and select the from-spec query like for eg : concat('bpws:getVariableData("orderNumber")','_Success.xml').In the To-Spec Query select
outHeaderVar/outboundHeader Query = "/ns2:OutboundFTPHeaderType/ns2:fileName"

This will assign the file name as required at the design time dynamically.

Tuesday, July 5, 2011

MQ Adapters with Opaque message type - Different ways to send a message.

MQ Adapters define two kinds of message payloads:

1. Opaque Schema
2. Schema file ( xsd) that contains a specified schema element.

Chosing opaque schema do not need to specify a message schema.By default the file/message is passed through in base-64 encoding.However

If the message is already base-64 encoded, the MQ Adapter will decode the message before placing in the Queue.


This is very useful certain times i) when the destination service who is listening/dequeuing to/from the MQ is expecting the message in the already decoded format.ii) (i) and when the destination is not confirmed to one message type and hence sender forced to use Opaque format.iii)when destination is listening to a message schema which has targetnamespace as null/empty.

For Case iii) situations , sender could have used the message xsd(destination is expecting) while sending instead of using an opaque schema , if the schema has a proper namespace(not null or empty) defined. As an empty target namespace xsd restricts the wizard of MQ adapter to define it , we are forced to use opaque schema.
Although it's not a right design for the destination app to have bad namespaces defined,we often end up in having this kind of services in the real scnarios.

So in these cases sender can encode the message to binary-64 before sending it to MQ so that the destination gets a decoded non binary64 message.

Given below is a sample code that encodes a message using a java embedding activity in BPEL process :

try
{

String input = (String)getVariableData("invServiceRequestStr");
String encoded;
com.collaxa.common.util.Base64Encoder Encoder = new com.collaxa.common.util.Base64Encoder();
encoded = Encoder.encode(input);
setVariableData("encodedMessageVar", encoded);
}
catch(Exception e)
{
e.printStackTrace();
}

Caveats:
1. If you are using 11g then you must use "oracle.soa.common.util.Base64Encoder" instead of com.collaxa.common.util.Base64Encoder.
In your .bpel you may need this :

FYI - This is present at - $JDEVHOME\soa\modules\oracle.soa.fabric_11.1.1\fabric-runtime.jar.

2. If your input to be converted is not plain string but element then use the below
String base64 = ((oracle.xml.parser.v2.XMLElement)getVariableData("MyElement")).getFirstChild().getNodeValue();

Summary :
Sender of the MQ message can encode the message which will be decoded by the MQ Adapater framework internally before placing into the queue ,if the schema used is a Opaque.