Invoke SOA Composite application through RMI as remote EJB (using binding.adf)

In a recent post I described how we can use the EJB Binding in SOA Suite 11g PS2 to invoke a SOA Composite application through RMI as a remote EJB. This interaction can be fully based on Java interfaces – no WSDL or XML required. However convenient that is, I have not yet been able to make it work for complex arguments or return parameters: methods that use simple types work fine, but using custom POJOs as input or return value did not result in a properly exposed EJB (even though deployment seems successful).

There is another way to invoke a SOA Composite as EJB via RMI. This other way is through the binding.adf binding type. The main difference with the inbound EJB binding is that we communicate in terms of XML (over RMI) with the SOA Suite’s generic Client API – and not directly to a service specific EJB. I was able to get this to work thanks to articles by Edwin Biemond (http://biemond.blogspot.com/2009/11/invoking-soa-suite-11g-service-from.html) and Changjae Lee (http://blogs.oracle.com/jaylee/2009/08/invoking_composite_from_javajs.html). They laid the foundation – and I adapted it for SOA Suite 11g R1 PS2.

The steps we go through:

Expose the Composite service through the binding.adf:

1. Create the SOA Composite application like you always do.

2. Duplicate the service with SOAP binding (binding.ws) and configure the clone as binding.adf (based on the same WSDL).

3. Deploy the composite with this new service of type binding.adf

Invoke SOA Composite application through RMI as remote EJB (using binding.adf) compositeWithInboundADFBinding

Create the client application that consumes the service

1. Create a new application and project

2. Add three libraries to the project: WebLogic 10.3 Remote-Client, SOA Runtime and Oracle XML Parser v2

3. Create a class that uses the Locator() class in the SOA Suite Java Client API. Write code to get hold of the Composite, the Service within the Composite and call the service with the name of the operation and the input message – a String with valid XML . The response is received – valid XML. It can be parsed as (XML) Element and further processed.

Expose the Composite service PatientDataService through binding.adf

The composite application we will expose is called PatientDataService. It is one of the services discussed as case in the SOA Suite 11g Handbook. This composite application exposes services to retrieve data for a specific patient and create a new patient. The former is to be exposed with binding.adf in order to make available to Java Clients for invocation over RMI.

Open the composite.xml file for PatientDataService in the source tab. The service element

<service name="client" ui:wsdlLocation="PatientDataService.wsdl">
 <interface.wsdl interface="http://stmatthews.hospital.com/patient/PatientDataService#wsdl.interface(PatientDataService)"/>
 <binding.ws port="http://stmatthews.hospital.com/patient/PatientDataService#wsdl.endpoint(client/PatientDataService_pt)"/>
 </service>

describes the SOAP interface for retrieving patient data. Copy this element and configure the clone as binding.adf (based on the same WSDL):

<service name="PatientDataServiceEJBClient" ui:wsdlLocation="PatientDataService.wsdl">
  <interface.wsdl interface="http://stmatthews.hospital.com/patient/PatientDataService#wsdl.interface(PatientDataService)"/>
  <binding.adf serviceName="ejbPatientDataServiceClient" registryName=""/>
</service>

Note: the values for the attributes serviceName and registryName are not used in this example.

Deploy the composite with this new service of type binding.adf

Create the client application that consumes the service

1. Create a new application and project

2. Add three libraries to the project: WebLogic 10.3 Remote-Client, SOA Runtime and Oracle XML Parser v2

Invoke SOA Composite application through RMI as remote EJB (using binding.adf) importlibraries

3. Create a class PatientDataService.

package com.stmatthews.hospital.patients;

import java.util.Hashtable;
import javax.naming.Context;
import oracle.soa.management.facade.Locator;
import oracle.soa.management.facade.LocatorFactory;

public class PatientDataService {
private Locator locator = null;

...
private void prepareLocator() {
 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, "weblogic1");
 jndiProps.put("dedicated.connection", "true");
 // connect to the soa server
try {
 locator = LocatorFactory.createLocator(jndiProps);
 } catch (Exception e) {
 }
 }
public static void main(String[] args) {
 PatientDataService pds = new PatientDataService();
 Element patient = pds.getPatientDetails("William", "Tacker");
 pds.logXMLElement(patient);

}

}

that uses the Locator() class in the SOA Suite Java Client API.

We will now implement the getPatientDetails() method:

public Element getPatientDetails(String firstName, String lastName) {
 String inputPayload =
 "<ns1:PatientDataServiceProcessRequest xmlns:ns1=\"http://stmatthews.hospital.com/patient/PatientDataService\">\n"
  + "<firstName>"+firstName+"</firstName>\n"
  + "<lastName>"+lastName+"</lastName>\n" +
 "</ns1:PatientDataServiceProcessRequest>";
 Element response = getResponseFromCompositeService(inputPayload,
 "default/PatientDataService!1.0",
 "PatientDataServiceEJBClient",
 "process", "payload", "payload");
 return response;
 }

This method leverages a generic method that invokes an operation on a service exposed by a composite application:

private Element getResponseFromCompositeService(String inputPayload,
 String compositeName,
 String serviceName,
 String operationName,
 String partNameRequestMessage,
 String partNameResponseMessage) {
 prepareLocator();
 Composite composite=null;
 try {
 composite = locator.lookupComposite(compositeName);
 } catch (Exception e) {
 }
 Service service = composite.getService(serviceName);
 NormalizedMessage input = new NormalizedMessageImpl();
 String uuid = "uuid:" + UUID.randomUUID();
 input.addProperty(NormalizedMessage.PROPERTY_CONVERSATION_ID, uuid);
 input.getPayload().put(partNameRequestMessage, inputPayload);
 NormalizedMessage res = null;
 try {
 res = service.request(operationName, input);
 } catch (Exception e) {
 e.printStackTrace();
 }

 Map payload = res.getPayload();
 Element element = (Element)payload.get(partNameResponseMessage);
 return element;
 }

The response is received – the payload contains one or multiple parts. The one specified is return,  as  an XML Element.

The main method invokes this method for printing the XML string of an Element to the output:

public void logXMLElement(Element xml) {
 TransformerFactory tFactory = TransformerFactory.newInstance()
 Transformer transformer;
 try {
 transformer = tFactory.newTransformer();
 transformer.setOutputProperty("indent", "yes");
 StringWriter sw = new StringWriter();
 StreamResult result = new StreamResult(sw);
 DOMSource source =  new DOMSource(xml);
 try {
   transformer.transform(source, result);
 } catch (TransformerException e) {
 }
 System.out.println("XML content: \n"+sw.toString());
 } catch (TransformerConfigurationException e) {
 }
 }

When we run the main method, we get the following output:

Invoke SOA Composite application through RMI as remote EJB (using binding.adf) runClientADFBinding

This response is created for us by the Composite application – as we can tell from the Flow Trace in the FMW EM Console accessed from the dashboard:

Invoke SOA Composite application through RMI as remote EJB (using binding.adf) instancesPatientDataService

Invoke SOA Composite application through RMI as remote EJB (using binding.adf) pdsFlowTrace

And the detailed audit trail:

Invoke SOA Composite application through RMI as remote EJB (using binding.adf) bpelAuditTrace