Oracle BPEL PM – Invoking a (remote) BPEL Service from Java using RMI

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

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi1

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.

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi2

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi3 

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi4

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.

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi5

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.

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi8 

Double click the first Assign. Specify the assignment of the input to the CRM Service:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi6

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi7 

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi9 

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.

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi10

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi11 

 The result of running the service looks like this:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi12

and the visual flow is this one:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi13

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi14

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.

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi15 

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:

Oracle BPEL PM - Invoking a (remote) BPEL Service from Java using RMI bpelrmi16

Resources

Download the sources for this article: 

https://technology.amis.nl/wp-content/uploads/images/CustomerProfileRMIClient.zip

5 Comments

  1. Shanthi Viswanathan December 15, 2007
  2. Chandra June 22, 2007
  3. SwitchBL8 June 15, 2006
  4. Lucas Jellema June 9, 2006
  5. SwitchBL8 June 9, 2006