Next Thursday, June 15th, I will present on the Dutch Java User Group Conference, on the integration between Java on the one hand and BPEL (specifically Oracle BPEL PM) on the other. The presentation is titled: How Java and BPEL join forces – What every Java developer should know about BPEL . In the presentation, I want to a brief introduction of SOA (Service Oriented Architecture) and BPEL and their importance and likely invasion into the world of application development. Then I want to discuss how to make use of BPEL Services from Java Applications – both formally through the SOAP WebService protocol and less formally and better performing – theoretically at least – as a RMI call to either a local or remote Session Bean. The second part of the presentation will discuss the reverse process: leveraging Java based services from a BPEL process: how can the BPEL process engage functionality that already exists in the organization, implemented using Java. Finally when time permits, we will go into the role Java based Web Applications can play to implement the Human Workflow steps that are part of many (BPEL) Processes.
In this article I will share some of my findings with regards to the local or remote invocation – using RMI – to the Session Bean Oracle BPEL PM exposes for invoking BPEL process programmatically from Java without going to full blown SOAP road.
The BPEL Service: CustomerProfileService
First we need a BPEL Service. And a simple one it is! Two PL/SQL packages – representing a CRM application and a Financial Module of an ERP system in the demo application – expose services that provide parts of the Customer Profile we want the BPEL Service to return. Note that most services will not be implemented in PL/SQL; however, for the purpose of the demo, the way in which the BPEL Process is constructed and the way it is invoked, it makes no difference. If you feel like it, just replace one of the PL/SQL package based services with a Siebel API and the other with an Oracle Financials interface and neither the BPEL process nor the calls to it will change.
The service interfaces of these two PL/SQL packages are as follows (note: all source code is downloadable in the Resources section at the end of the article):
package crm
procedure retrieve_customer_contact_data
( p_customer_id in number
, p_first_name out varchar2
, p_last_name out varchar2
, p_street out varchar2
, p_house_number out varchar2
, p_postal_code out varchar2
, p_city out varchar2
, p_country out varchar2
, p_birthdate out date
, p_receive_mailings out varchar2 -- Y or N
, p_email_address out varchar2
, p_telephone out varchar2
, p_customer_status out varchar2 -- BRONZE, SILVER, GOLD or nothing at all
);
package financials
procedure retrieve_financial_status
( p_customer_id in number
, p_total_revenue out number
, p_credit_limit out number
, p_outstanding_debt out number
, p_avg_pay_delay out number -- avg number of days from billing-date to pay-date
);
Building a BPEL process that returns a (partial) CustomerProfile, leveraging these two services, is almost trivial. Let’s go through the motions – assuming that you have installed the packages and underlying tables in a schema in some Oracle database.
1. Start Oracle BPEL PM 10.1.2.0.2 JDeveloper BPEL Designer
2. Create a new Application and a new BPEL Process Project, for example: CustomerProfileService
Select the Template Synchronous BPEL Process.
3. Import Services aka Create PartnerLinks
From the (BPEL) Process Activities Component Palette, drag and drop a Partner Link to the Partner Links zone in the BPEL diagram. In the Edit Partner Link window that opens, type the Name as CRM and click on the Define Adapter Service icon. This brings up the Adapter Configuration Wizard. In this wizard, choose Database Adapter, select Call PL/SQL Procedure, choose (and if necessary first define) the Connection with the database schema that contains the packages CRM and FINANCIALS. The select the procedure CRM.retrieve_customer_contact_data and click on Finish. Click on OK back in the Edit Partner Link Window. Now we have set up the Partner Link representing the CRM(.retrieve_customer_contact_data) service and we are ready to invoke it from the BPEL process.
Now repeat these steps for the FINANCIALS service, based on the retrieve_financial_status procedure.
4. Prepare the proper data structure for the input and output of the BPEL Service
So far, we have not bothered with the data structures that go into and come out of the BPEL Service CustomerProfileService that we are creating. Now is a good time to start doing so. We will do it the simple and dirty way by creating all complexType definitions in the service WSDL instead of a separate XSD that we then import from the WSDL.
Open the file CustomerProfileService.wsdl (the name depends on the name you have given to the project). Locate the element describing the input to the service, in my case CustomerProfileServiceProcessRequest. Change its definition to something like:
<element name="CustomerProfileServiceProcessRequest">
<complexType>
<sequence>
<element name="customerId" type="integer"/>
</sequence>
</complexType>
</element>
Here we have specified that our service will take a String input parameter that is called customerId. Clearly this is not yet spectacular. Let’s also redefine the CustomerProfileServiceProcessResponse. The next piece of WSDL contains only a small part of what it should be if we have worked out in all detail the data available from the PartnerLink invocations.
<element name="CustomerProfileServiceProcessResponse">
<complexType>
<sequence>
<element name="contactDetails">
<complexType>
<sequence>
<element name="name" type="string"/>
<element name="street" type="string"/>
<element name="houseNumber" type="string"/>
</sequence>
</complexType>
</element>
<element name="financialStatus">
<complexType>
<sequence>
<element name="totalRevenue" type="float"/>
<element name="creditLimit" type="float"/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
We have created two sections in the response message: one with ContactDetails (provided by the CRM service) and one with the FinancialStatus (based on the results from the FINANCIALS service). Based on these definitions for the inputVariable and outputVariable, we can now start creating invocation of the PartnerLinks.
5. Invoke the PartnerLinks and retrieve the results
Our service is almost trivially simple. We receive the inputVariable with a customerId. With that customerId we invoke – in parallel – the CRM and the FINANCIALS partner link and copy the results from these calls to the OutputVariable. Next, we are done!
To have the two services invoked in parallel – will improve performance and there is no reason not to since the calls to CRM and FINANCIALS are unrelated – we first drag a Flow component from the Component Palette onto the BPEL diagram. In the flow, we drag and drop two Scope components, one for each of our service invocations. Call one scope CRM and the other one FINANCIALS. The diagram now looks like this:
Next we will flesh out each of the two scopes, creating the actual service calls. Drill into the CRM scope. Drag and drop an Invoke Activity into the CRM scope. Connect the call end of the Invoke to the CRM PartnerLink:
The Invoke dialog window pops up. Use the Auto Create variable for both the Input Variable and the Output Variable. Set the scope to Local Variable.
Now we need to do two more things: assign the customerId value in the InputVariable to the Invoke_CRM_CRM_InputVariable_1 and retrieve the values from the Invoke_CRM_CRM_OutputVariable_1 into the OutputVariable of the BPEL Service.
Drag and drop two Assign Activities; one goes before the Invoke, the other one after.
Double click the first Assign. Specify the assignment of the input to the CRM Service:
Click OK twice. Now double click on the second Assign activity. Here we need to specify the retrieval of the service outcome of the OutputVariable. Specify for example using an XPath expression that the contactDetails/name element in the OutputVariable is populated from P_FIRST_NAME and P_LAST_NAME in the service output:
The specification for this Assign activity in BPEL syntax is like this:
<assign name="Assign_CRM_output">
<copy>
<from expression="concat(bpws:getVariableData('Invoke_CRM_CRM_OutputVariable_1','OutputParameters','/ns2:OutputParameters/P_FIRST_NAME'),' ',bpws:getVariableData('Invoke_CRM_CRM_OutputVariable_1','OutputParameters','/ns2:OutputParameters/P_LAST_NAME'))"/>
<to variable="outputVariable" part="payload" query="/client:CustomerProfileServiceProcessResponse/client:contactDetails/client:name"/>
</copy>
<copy>
<from variable="Invoke_CRM_CRM_OutputVariable_1" part="OutputParameters" query="/ns2:OutputParameters/P_STREET"/>
<to variable="outputVariable" part="payload" query="/client:CustomerProfileServiceProcessResponse/client:contactDetails/client:street"/>
</copy>
</assign>
Now set up the Scope FINANCIALS in the same, with two Assign operations and Invoke of the FINANCIALS partner link.
The complete BPEL Service now looks like this:
6. Deploy and Test
Our BPEL Process is ready for deployment. Nothing could be simpler. First ensure that the Oracle BPEL PM Server 10.1.2.0.2 is running. I will assume in this article a local BPEL PM Server. Right click the BPEL Project in the Application Navigator, choose Deploy and select LocalBPELServer, Deploy to defaul domain. The default password for the default domain is bpel.
The BPEL project is now compiled and when successfully compiled will be packaged and deployed on the local Oracle BPEL PM Server. When that is done, start the Oracle BPEL Console to see the Service Status and to test it.
In the Dashboard of the BPEL Console, select the CustomerProfileService. This takes us to the Initiate Page where we can easily test-run our service. Type a number (1..4) for the customerId and click on Post XML Message. This will run the service:
The result of running the service looks like this:
and the visual flow is this one:
Invoking the CustomerProfileService from Java using RMI
Now we come to the beef of this article: the first part is pretty trivial stuff, though still a pleasure to go through. How to create a client application that makes use of this BPEL Service CustomerProfileService.
1. Start JDeveloper 10.1.3. Create a new Application and a new Project, for example: CustomerProfileRMIClient.
2. Set the Libraries for the project
One of the steps that caused me the most headaches, attaching the correct set of JARs to my Client Application. The following set works for me – though I am not sure whether there are better alternatives or a subset that suffices:
The first four jars are all from the ORACLE_BPEL_HOME\integration\orabpel\lib directory. The Oc4j.jar is from ORACLE_BPEL_HOME\integration\orabpel\system\appserver\oc4j\j2ee\home.
3. Create a CustomerProfile bean
Create a simple class with all properties we need in a CustomerProfile – typically at least the properties returned by the CustomerProfileService.
4. Create a CustomerProfileManager class
This class will do the actual invocation through RMI to the remote BPEL Service CustomerProfileService. The class will make a local service available – through a public static method – that allows other classes to retrieve a CustomerProfile for a certain customer(Id):
public static CustomerProfile findCustomerProfile(String customerId)
The class needs imports from a number of RMI and Oracle BPEL related classes and interfaces:
import com.oracle.bpel.client.Locator;
import com.oracle.bpel.client.NormalizedMessage;
import com.oracle.bpel.client.ServerException;
import com.oracle.bpel.client.delivery.IDeliveryService;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Hashtable;
import java.util.Map;
import javax.naming.Context;
import org.w3c.dom.Element;
The crucial part of the findCustomerProfile method is setting up the properties for making the RMI call:
Hashtable jndi = null;
jndi = new Hashtable();
// Change the PROVIDER_URL to your BPEL PM host...
jndi.put(Context.PROVIDER_URL, "ormi://localhost/orabpel");
jndi.put(Context.INITIAL_CONTEXT_FACTORY,
"com.evermind.server.rmi.RMIInitialContextFactory");
// connect to the admin principal
jndi.put(Context.SECURITY_PRINCIPAL, "admin");
jndi.put(Context.SECURITY_CREDENTIALS, "welcome");
jndi.put("dedicated.connection", "true");
// call to default domain using (default) password bpel
// note: if we run this code in the same JVM as the Oracle BPEL PM, then jndi can be null
Locator locator = new Locator("default", "bpel", jndi);
We provide the location of the RMI server, in this case the OC4J instance that is running the Oracle BPEL PM Server (in my case on my laptop, hence the localhost). Next is the credentials for the OC4J Administrator. Using these RMI connection details, we create a Locator object. This is an Oracle BPEL specific object that helps us connect to a BPEL Domain (default) on the BPEL PM Server. The next piece of code has us link to the generic IDeliveryService SessionBean interface that will take our BPEL Service call and route it to the specific service (BPEL Process):
IDeliveryService deliveryService =
(IDeliveryService)locator.lookupService(IDeliveryService.SERVICE_NAME);
// construct the normalized message and send to collaxa server
NormalizedMessage nm = new NormalizedMessage();
nm.addPart("payload", xml);
The main thing we have to do before we can make the call, is prepare the CustomerProfileServiceRequestMessage to pass into the BPEL Service (the xml variable in the last line of the previous snippet):
String xml =
"<CustomerProfileServiceProcessRequest xmlns=\"http://xmlns.oracle.com/CustomerProfileService\"><customerId>" +
customerId +
"</customerId></CustomerProfileServiceProcessRequest> ";
Now we can make the call:
NormalizedMessage res =
deliveryService.request("CustomerProfileService", "process", nm);
Map payload = res.getPayload();
Element partEl = (Element)payload.get("payload");
Now the partEl element contains CustomerProfileServiceProcessResponse. A private static method is used to populate a new CustomerProfile Bean instance from the CustomerProfileServiceProcessResponse:
...
return buildCustomerProfile(partEl);
}
private static CustomerProfile buildCustomerProfile(Element serviceResponse) {
CustomerProfile profile = new CustomerProfile();
Element contactDetails = (Element)(serviceResponse.getElementsByTagName("contactDetails")).item(0);
profile.setName((contactDetails.getElementsByTagName("name")).item(0).getNodeValue());
profile.setStreet((contactDetails.getElementsByTagName("street")).item(0).getNodeValue());
Element financialStatus = (Element)(serviceResponse.getElementsByTagName("financialStatus")).item(0);
profile.setTotalRevenue(Float.parseFloat((financialStatus.getElementsByTagName("totalRevenue")).item(0).getNodeValue()));
profile.setCreditLimit(Float.parseFloat((financialStatus.getElementsByTagName("creditLimit")).item(0).getNodeValue()));
return profile;
}
The client application that leverages this local CustomerProfileService offered by the CustomerProfileManager looks like this:
package nl.amis.crm;
import java.util.Date;
import nl.amis.crm.bpel.client.CustomerProfileManager;
public class CustomerProfileClient {
public CustomerProfileClient() {
}
public static void printCustomerReport( String customerId) {
try {
CustomerProfile profile = CustomerProfileManager.findCustomerProfile(customerId);
System.out.println("The Customer Report for "+profile.getName()+" on "+new Date());
System.out.println("============================================================================");
System.out.println("Contact Details:");
System.out.println("----------------");
System.out.println("Address:"+profile.getStreet()+" "+profile.getHouseNumber());
System.out.println("Financial Status:");
System.out.println("----------------");
System.out.println("Total Revenue:"+profile.getTotalRevenue());
System.out.println("Credit Limit:"+profile.getCreditLimit());
}
catch (Exception e) {
System.out.println("The Customer Report is not available due to an exception. Our sincere apologies.");
e.printStackTrace();
}
}
public static void main(String[] args) {
// if no argument passed in on command line, then use 1 as default customerId
String customerId = args.length>0?args[0]:"1";
CustomerProfileClient.printCustomerReport(customerId);
}
}
The output of runing the client application:
Resources
Download the sources for this article:
- The PL/SQL Services:
plsqlServices.zip
- The BPEL Project: CustomerProfileService.zip
- The CustomerProfileClientApplication:
CustomerProfileRMIClient.zip
https://technology.amis.nl/wp-content/uploads/images/CustomerProfileRMIClient.zip
This code is real cool. I have a different quiestion:
I have Oracle BPEL processes deployed on one server. I have a scheduler on a different server and this scheduler needs to invoke the BPEL process. I want to secure the BPEL process – maybe using OWSM. This scheduler can invoke anything using a shell script or just command line. How can I achieve this?
Lucas I appreciate your efforts for sharing the information and I have been a regular vistor to your blog and found ur blog quite informative. Curiously following your posts. Thank you.
I’m sorry to say that the presentation has not been approved for public availability. The seminar was intended for (could-be) clients and not for “concullega’s”.
What we (there were 4 presentations) showed to the audience was how to use webservices and the SOA to integrate their business. Internally or with external parties. A live-demo of the creation (in BPEL-designer) and running of a BPEL process was one of the presentations. What we showed was how a simple form that changed the status of a record from NEW to ACCEPTED could trigger a step in the newly created BPEL process. Thus without accessing the actual webservice, and all possible from Oracle Designer, since it’s only PL/SQL. That’s what a lot of clients know and/or want to use.
I guess you’re giving your demo now, since today is the 15th. Good luck.
Reginald,
Thanks for your compliments on the contents of the article. The presentation on the 15th will be on a much wider range of topics than just the Java based call into a BPEL Service. I will make available the contents of the entire presentation and all demonstrations in subsequent postings on this weblog.
Can I find your presentation/demonstration somewhere on the internet? I am very interested to see what you did. Glad it was such a huge success.
best regards,
Lucas
Lucas,
It strikes me as a little odd that you will be giving the presentation on the 15th, but you are basically giving away the contents NOW.
Today we (Transfer Solutions) have given a seminar around the BPEL subject too and it was a huge success. Lots of (potential) clients still use forms, so we decided to demonstrate a process that was controlled by updating a column in a table via a form (e.g. changing a statusfield). Simple, but very understandable for the audience, since they all know forms and PL/SQL.
Good luck next Thursday.
Reginald