Call a BPEL Process from Java

A BPEL White Paper
Written by Olivier Le Diouris, Oracle Corporation
October, 2005

Introduction

Top

There are two major kinds of BPEL processes, synchronous and asynchronous.

We will see in that document how to

Call a synchronous process

Top

A Synchronous process is not the default pattern when you create a new BPEL project. It has to be handled with care, as some timeout might occur, if the requested response does not come fast enough from the process.
However, the java client for such a process is the simplest one, which is the reason why we begin with this one in this document.
If the process has been built with the JDeveloper/BPEL Designer, a synchronous BPEL process begins - by default - with a receiveInput task, and ends - still by default - with a replyOutput task, as opposed to an asynchronous one, that begins with a receiveInput task, but ends with a callbackClient task.
The complexity of the process is not the purpose of this document, we will use a very simple process, that will take a string of character containing a name, and return another string like "Hello <name>!". No big deal, indeed.
The project containing this simple synchronous process is available here.
To be able to call a process, wether it is a synchronous or asynchronous one - you need to identify the following data:

In the example provided above: Those data being identified, we can proceed.
The whole java code for the client can be read here.
We are going to give more details about the way it works.

Libraries and imports

To resolve the imports we need to make in this class, we need to associate the following libraries to the JDeveloper project:

Context

You've notice in the main method the reference made to a file named context.properties.
    try
    {
      Properties props = new java.util.Properties();
      java.net.URL url = new File("context.properties").toURL();
      props.load(url.openStream());
      ...
          
Here is the content of this file:
orabpel.platform=oc4j_10g
java.naming.factory.initial=com.evermind.server.rmi.RMIInitialContextFactory
java.naming.provider.url=ormi://localhost/orabpel
java.naming.security.principal=admin
java.naming.security.credentials=welcome
          
We choosed to refer to a properties file for flexibility reasons. We could have as well hard-coded the values, like in here:
  Properties props = new java.util.Properties();

  props.setProperty("orabpel.platform",                 "oc4j_10g");
  props.setProperty("java.naming.factory.initial",      "com.evermind.server.rmi.RMIInitialContextFactory");
  props.setProperty("java.naming.provider.url",         "ormi://localhost/orabpel");
  props.setProperty("java.naming.security.principal",   "admin");
  props.setProperty("java.naming.security.credentials", "welcome");
    ...
          
The result would have been exactly the same. In either case, this Proprties set will be refered to in the java line:
  locator = new Locator("default", "bpel", props);
          

Incoming message

You can notice in the code that the message to send to the process - the one that contains the user input - is an XML document, provides as a String. Most parsers provide the possibility to stream an XML Document into a String; for simplicity, we will just generate a String containing the XML Document.
This is certainly not the best way to do it, as a small typo can invalidate the whole XML document. A real-world application would use an XML Parser to generate a well-formed and valid XML document that would be streamed into a String afterwards.
Anyhow, we put together an XML-String, containing the data we are intereted in:
  String xml = "<SynchronousBPELProcessProcessRequest xmlns=\"" + NAMESPACE + "\">\n" +
               "  <input>" + name + "</input>\n" +
               "</SynchronousBPELProcessProcessRequest>";
          
The name variable comes from the user input, as you can see in the code.

The process request

Now we have all the pieces we need to invoke the process. This invocation takes place in the following lines:
  Locator locator = new Locator("default", "bpel", props);
  IDeliveryService deliveryService = 
          (IDeliveryService)locator.lookupService(IDeliveryService.SERVICE_NAME);

  // construct the normalized message and send to BPEL server
  NormalizedMessage nm = new NormalizedMessage();
  nm.addPart("payload", xml);
  NormalizedMessage res = deliveryService.request(PROCESS_NAME, 
                                                  "process", 
                                                  nm);
          
Notice specially: The "process" value is found in the bpel file, at //receive[@partnerLink = 'client']/@operation.

Getting the response

A Synchronous process will get back to its client, with a callbackClient.
This response is actually caught after requesting the execution of the process - like above - as the request method returns also a NormalizedMessage, as you've seen.
We only need to extract the payload:
  Map payload = res.getPayload();
  String result = payload.get("payload").toString();
          
This is possible, as the "payload" object is a com.collaxa.cube.xml.dom.CubeDOMElement, which has a toString() method that returns the XML Content we are interested in.
The rest of the code is only here to parse that response, and extract from it the value we are interested in. We know the structure of this XML Element, because it matches the structure mentionned above. As a result, we know how to parse it, and where the value we are interested in is.

Warning!

If the process takes too long to get back to its client, then the client can potentially raise a timeout, which would not necessary mean that something went wrong..., it just took some time.
The client timeout can be set via the BPEL Console, in the "Manage BPEL Domain" section, by setting the value of syncMaxWaitTime.

Call an asynchronous process, request only

Top

Triggering an asynchronous BPEL process is very easy, if you do not expect a response in return. Most of the client code is the same as before, the main difference is in the way to invoke the process.
In the synchronous case, the process was invoked on the Delivery Service by the request method.
In the asynchronous case, that would be post. And the value of //receive[@partnerLink = 'client']/@operation is this time "initiate". As a result, the way to invoke the process becomes:

  deliveryService.post(PROCESS_NAME, "initiate", nm);
        
Notice this time that no NormalizedMessage is returned.
We will use for this example an asynchronous process fulfilling the same requirements as in the previous case, for the synchronous process. This BPEL Process can be found here.
The client invoking the process - and not waiting for its result - can be read here.
You can see that there is no major difference with what we had before. The only important one to notice is that nothing is returned by the invocation, it only starts the process.
After running this class, you would have to go to the BPEL Console, to see exactly what happened to your process.

Call an asynchronous process, request and response

Top

This steps starts exactly the same way as in the previous one.
The trick is to identify what the responding task would be, so we can get its result. This will be done by using a conversation id, which must be unique.
The whole java client performing this task can be found here.
Just like before, we are going to give some details about the different extra steps we have to go through.

Getting and setting a Conversation ID

A conversation ID must be associated with the NormalizedMessage, in order to refer to it after invoking the process, to retirieve the response we are interested in.
For now, a suitable way to get such an ID, which must be unique, is to use the generateGUID static method on the com.collaxa.cube.util.GUIDGenerator Class.
  String uniqueKey = GUIDGenerator.generateGUID();
  nm.setProperty(Configuration.CONVERSATION_ID, uniqueKey);
          
As you can see, this conversation ID is set at the NormalizedMessage level.
Next, the process is triggered just like in the previous case:
  deliveryService.post(PROCESS_NAME, "initiate", nm);          
          

Polling the process

Just like in the previous case, once the post method is called, the process is on its way. This is now where there is a big difference between the synchronous and the asynchronous case. We are going to "ping" the process, and check its status out. In this example, we are going to loop until the process is completed, and wait between each loop, not to saturate the CPU...
  boolean go = true;
  while (go)
  {
    try
    {
      IInstanceHandle iih = locator.lookupInstance(uniqueKey);
      while (!iih.isClosed() && !iih.isComplete())
      {
        try { Thread.sleep(1000); } catch (Exception ignore) {}
        iih = locator.lookupInstance(uniqueKey);
      }
      go = false;    
      // Now get the result
      ...
    }
    catch (Exception e)
    {
      try { Thread.sleep(1000); } catch (Exception ignore) {}
    }
  }
          
The instance handle we need is obtained by calling the lookupInstance method on the locator object we had before. As seen in the code above, we wait one second between each lookup, and we loop until the instance we ping is completed or closed.
Once it is completed or closed, it is time to get the result.

Getting the result

We are going to use the Instance Handle obtained in the previous step to get the data we want.
  Object o = iih.getField("outputVariable");
  if (o instanceof HashMap)
  {
    HashMap hm = (HashMap)o;
    Set keys = hm.keySet();
    Iterator iterator = keys.iterator();
    while (iterator.hasNext())
    {
      Object next = iterator.next();
      if (next instanceof String)
      {
        String str = (String)next;
        Object value = hm.get(next);
        if (value instanceof CubeDOMElement)
        {
          CubeDOMElement cde = (CubeDOMElement)value;
          String str2 = cde.toString();

          String xpathExpr = "//resp:result";
          org.collaxa.thirdparty.jaxen.XPath xpath = 
            new org.collaxa.thirdparty.jaxen.dom4j.Dom4jXPath(xpathExpr);
    
          String prefix =  "resp";
          String uri = NAMESPACE;
          xpath.addNamespace(prefix, uri);

          response = xpath.stringValueOf(cde);
        }
      }
    }
  }
          
The only prupose of the code presented above is to get the BPEL Variable named outputVariable, and parse it to get its result element, which is pretty straight forward.

Bonus: The same from PLSQL

Top

In order to avoid to load big jar-files inside the DataBase, we are going to use http.
We are going to write a Servlet that will call the BPELClient, and use the UTL_HTTP package to call it.
Here is the code of the servlet:

package http;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.PrintWriter;
import java.io.IOException;
import my.client.BPELClient;

public class BPELServlet extends HttpServlet 
{
  private static final String CONTENT_TYPE = "text/plain; charset=windows-1252";
  private static final String DOC_TYPE;

  public void init(ServletConfig config) throws ServletException
  {
    super.init(config);
  }

  public void service(HttpServletRequest request, HttpServletResponse response) 
       throws ServletException, 
              IOException
  {
    String name = "";
    try
    {
      name = request.getParameter("name");
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }

    response.setContentType(CONTENT_TYPE);
    PrintWriter out = response.getWriter();
    if (DOC_TYPE != null)
    {
      out.println(DOC_TYPE);
    }
    if (name != null && name.trim().length() > 0)
    {
      String resp = BPELClient.callProcess(name);
      out.println(resp);      
    }

    out.close();
  }
}
        
And then we write a PLSQL Function:
create or replace function scott_greetings (name in varchar2) return varchar2 is
  response varchar2(256) := '';
  url varchar2(256) := 'http://oliv-lap:9700/BPELServlet/servlet/BPELServlet?name=';
begin
  url := url || name;

  response := utl_http.request(url, null);
  return response;
end scott_greetings;
/
        
That's it! We can now use it from SQL*Plus:
SQL> select scott_greetings(ename) as "Greetings" from emp where deptno = 10;

Greetings
-------------------------------------------------------------------------------

Hello CLARK
Hello KING
Hello MILLER

SQL>
        
Calling such a function from a Database Trigger would probably be more appropriate, but this is just an example!

Resources

Top


The A-Team, 2005