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:
<types>
<schema attributeFormDefault="qualified"
elementFormDefault="qualified"
targetNamespace="http://xmlns.oracle.com/SynchronousBPELProcess"
xmlns="http://www.w3.org/2001/XMLSchema">
<element name="SynchronousBPELProcessProcessRequest">
<complexType>
<sequence>
<element name="input" type="string"/>
</sequence>
</complexType>
</element>
<element name="SynchronousBPELProcessProcessResponse">
<complexType>
<sequence>
<element name="result" type="string"/>
</sequence>
</complexType>
</element>
</schema>
</types>
<inputVariable>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="payload">
<SynchronousBPELProcessProcessRequest xmlns="http://xmlns.oracle.com/SynchronousBPELProcess">
<input>Olivier</input>
</SynchronousBPELProcessProcessRequest>
</part>
</inputVariable>
<outputVariable>
<part xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="payload">
<SynchronousBPELProcessProcessResponse xmlns="http://xmlns.oracle.com/SynchronousBPELProcess">
<result>Hello Olivier!</result>
</SynchronousBPELProcessProcessResponse>
</part>
</outputVariable>
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);
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.
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:
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.
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.
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.
String uniqueKey = GUIDGenerator.generateGUID();
nm.setProperty(Configuration.CONVERSATION_ID, uniqueKey);
As you can see, this conversation ID is set at the NormalizedMessage level.
deliveryService.post(PROCESS_NAME, "initiate", nm);
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.
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 |