The Oracle SOA Suite 11g HttpBinding or another way to call RESTful services from SOA Composite Applications

2

I wanted to take a quick look at REST(ful) WebServices and see how those can be integrated into the SCA based SOA Composite Applications that we create with the Oracle SOA Suite. Currently, it does not have the HTTP binding that the 10.1.3 release of the SOA Suite used to have. So what are the alternatives?

In this article, I want to demonstrate a way of calling RESTful (simple http request based) services into a SOA Composite application. I show one way of doing so using the Google Translation Service, a RESTful service described at http://code.google.com/apis/ajaxlanguage/documentation/ and to be accessed at http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=hello%20world&langpair=en%7Cit. This service takes a string to translate and an indication of a source and a destination language. Though maybe not formally resource oriented enough to be called REST-style (or RESTful) service by some, it is a service that does not require SOAP or WS* but simply a HTTP Get request. So at least quite restful.

In this article I will use the work I did and described in the previous article: Leveraging RESTful Services from Java application using Jersey (Introduction). Using project Jersey's support for clients of RESTful services and the JSON-SIMPLE library to interpret the response I get from the GoogleTranslation service, I will hook up with the RESTful Service. Then I will leverage the Mediator Java Callout mechanism to integrate this REST-service-client into a SOA Composite application. The Mediator I create will be the fully SOA Suite integrated front-end of this RESTful Service.

With this Mediator in place, I can make calls such as:

There are several ways to go about this DIY HttpBinding. Which one is best and ultimately my favorite is hard to say at this stage. A few of the ways that I can see right now – and I presume at some point Oracle will add the HttpBinding again – and perhaps even a RESTful, JSON aware service binding. Then that would be the favorite you would think.

For now we could use:

  • use the Mediator Java Callout mechanism to call a Java client of the RESTful service
  • create a Java client of the RESTful service and expose that client as a SOAP WebService that can be bound to from composite applications
  • create a custom JCA connector to take on this task
  • create a custom XPath function and use it in the mapping
  • use the new (currently in preview mode) Java Spring context (that allows integration of beans in a Spring Bean context to be invoked); this seems an easier approach than my Mediator Java Callout; however, at the time of writing (11gPS1) it is not yet a production ready feature
  • create an SDO enabled EJB and invoke it using the EJB binding service
  • put a translation request to be fulfilled by the RESTful service on a JMS queue; have a queue-listener pick up the message, make the call and put a correlated response on another queue; have the composite application wait for the response to arrive (it seems that the work recently published by Edwin Biemond could help here:JMS Request Reply Interaction Pattern in Soa Suite 11g)
  • and many other ways I am sure exist to.

As stated before, I will make use of the Mediator Java Callout mechanism to have the mediator reach out to the Java class that invokes the RESTful service and so indirectly calls the HTTP based RESTful translation service.

However, the original approach based on the Jsersey client for consuming RESTful services failed. I kept on running into a problem with Jersey deployed to SOA Suite (I put the libraries in the SCA-INF/lib folder in my SOA Composite Application. This fails after deployment when I try to run the application by invoking its WebService interface, complaining about com.sun.jersey.core.impl.provider.header.LocaleProvider. There are some resources on the internet that suggest OSGi clashes). Whatever the cause, this article was not about Jersey but about binding RESTful http services into SOA Composite applications. So I will go for a more straightforward alternative for invoking the Google Translation Service.

Steps to implement Http Binding (well, sort of)

1. Create new SOA Composite Application in JDeveloper 11g with SOA extension; pick the Composite with Mediator component template; call the Mediator Translator and choose the Synchronous service interface. Add the Oracle XML Parser v2 library to the project.

2. Edit the XSD created for the mediator: singleString.xsd. Add a new element:

	<element name="translationSource">
		<complexType>
			<sequence>
				<element name="input" type="string"/>
				<element name="sourceLanguage" type="string"/>
				<element name="targetLanguage" type="string"/>
			</sequence>
		</complexType>
	</element>


3. Edit the WSDL created for the mediator – change the element defining the requestMessage, replace it with the translationSource element that was defined in the XSD:

   <wsdl:message name="requestMessage">
        <wsdl:part name="request" element="inp1:translationSource"/>
    </wsdl:message>

I also prefer to have more meaningful names for portType and operation – instead of execute_ptt and execute – so I have changed them too. However, these changes need to be propagated into the composite.xml, .componentType and .mplan files.

4. From the composite editor, double click the mediator component in order to start editing. Click the green plus sign, to create a new static routing rule. Click on the Echo button (instead of Service and Event) to create a roundtrip mediator that takes on the request and returns it back to the requestor, after performing transformations and java callouts.

5. Edit the mapping/transformation for this routing rule.

The underlying XSLT source for this mapping is:

  <xsl:template match="/">
    <inp1:singleString>
      <inp1:input>
        <xsl:value-of select="concat(/inp1:translationSource/inp1:input,',',/inp1:translationSource/inp1:sourceLanguage,',',/inp1:translationSource/inp1:targetLanguage)"/>
      </inp1:input>
    </inp1:singleString>
  </xsl:template>


this mapping is used to have the translation request available in the response message for the Java Callout to act on.

 

6. Specify the Java Callout for the Mediator. Set it to nl.amis.soasuite11g.mediator.MediatorCalloutToHttpRestProcessor – the name of a class we are about to create.

7. Create new class MediatorCalloutToHttpRestProcessor that extends AbstractJavaCalloutImpl:

package nl.amis.soasuite11g.mediator;

public class MediatorCalloutToHttpRestProcessor extends AbstractJavaCalloutImpl{
  @Override
  public boolean postRouting(CalloutMediatorMessage calloutMediatorMessage,
                             CalloutMediatorMessage calloutMediatorMessage2,
                             Throwable throwable) throws MediatorCalloutException {
    System.out.println("postRouting");
    boolean returnValue =
      super.postRouting(calloutMediatorMessage, calloutMediatorMessage2,
                        throwable);
    String sPayload = "null";

    for (Iterator msgIt = calloutMediatorMessage2.getPayload().entrySet().iterator();msgIt.hasNext(); ) {
      Map.Entry msgEntry = (Map.Entry)msgIt.next();
      Object msgKey = msgEntry.getKey();
      if (msgKey.equals("reply")) {
        try {
          Object msgValue = msgEntry.getValue();
          sPayload = XmlUtils.convertDomNodeToString((Node)msgValue);
          XMLDocument changedoc;
          changedoc = XmlUtils.getXmlDocument(sPayload);
          XMLNode inputS =
            (XMLNode)changedoc.selectSingleNode("//inp1:input", new LocalNamespaceResolver());
          String[] params = inputS.getTextContent().split(",");
          XMLDocument doc = prepareReturnMessage(params[0], params[1], params[2]);

          String mykey = "reply";
          calloutMediatorMessage2.addPayload(mykey, doc.getDocumentElement());
        } catch (Exception f) {
          System.out.println(f);
        }

      }
    } //for
    return returnValue;
  }
  private XMLDocument prepareReturnMessage(String input, String sourceLanguage, String targetLanguage) throws ParserConfigurationException {
    JXDocumentBuilderFactory factory =
      (JXDocumentBuilderFactory)JXDocumentBuilderFactory.newInstance();
    JXDocumentBuilder documentBuilder =
      (JXDocumentBuilder)factory.newDocumentBuilder();
    XMLDocument doc = (XMLDocument)documentBuilder.newDocument();
    doc.setVersion("1.0");
    doc.setEncoding("UTF-8");
    //http://xmlns.oracle.com/singleString
    // singleString/input
    XMLElement rootElement = (XMLElement)(doc.createElementNS("http://xmlns.oracle.com/singleString","singleString"));
    doc.appendChild(rootElement);
    XMLElement inputElement =
      (XMLElement)(doc.createElement("input"));
    rootElement.appendChild(inputElement);
    String translation = NoJerseyTranslationService.translate(input, sourceLanguage, targetLanguage);
    XMLText translationElement =
      (XMLText)doc.createTextNode(translation);
    inputElement.appendChild(translationElement);
    return doc;
  }

  class LocalNamespaceResolver implements NSResolver {
    public String resolveNamespacePrefix(String prefix) {
        return "http://xmlns.oracle.com/singleString";
    }
  }
}

(see the previous blog article on SOA Suite 11g Mediator Java Callout for details on this class)

8. This class uses the NoJerseyTranslationService, another class that needs to be created. However, before we can do so, we need to download the json-simple library from http://code.google.com/p/json-simple/ and add the jar file json_simple-1.1.jar to the SCA-INF/lib directory in our project.

9. Create class NoJerseyTranslationService – see for details about its implementation the blog article Leveraging RESTful Services from Java application using Jersey (Introduction).

package nl.amis.soasuite11g.mediator;

public class NoJerseyTranslationService {
  private static String googleTranslationService =
    "http://ajax.googleapis.com/ajax/services/language/translate";

  private static String extractTranslationFromJSON(String response) {
    final JSONObject jsonObj = (JSONObject)JSONValue.parse(response);
    String translation=null;
    if (jsonObj != null && jsonObj.containsKey("responseData")) {
      final JSONObject responseData = (JSONObject)jsonObj.get("responseData");
      translation=  responseData.get("translatedText").toString();
    }
    return translation;
  }

  public static String translate(String sourceString, String sourceLanguage, String targetLanguage) {
    return extractTranslationFromJSON(translateString(sourceString, sourceLanguage, targetLanguage));
  }

  private static String translateString(String sourceString,
                                        String sourceLanguage,
                                        String targetLanguage) {
    HttpURLConnection connection = null;
    OutputStreamWriter wr = null;
    BufferedReader rd  = null;
    StringBuilder sb = null;
    String line = null;

    URL serverAddress = null;

    try {
        serverAddress = new URL(googleTranslationService +"?v=1.0&&q="+sourceString.replace(' ', '+')+"&&langpair="+sourceLanguage+"%7C"+targetLanguage);
        //set up out communications stuff
        connection = null;

        //Set up the initial connection
        connection = (HttpURLConnection)serverAddress.openConnection();
        connection.setRequestMethod("GET");

        connection.setDoOutput(true);
        connection.setReadTimeout(10000);

        connection.connect();

        //get the output stream writer and write the output to the server
        //not needed in this example
        //wr = new OutputStreamWriter(connection.getOutputStream());
        //wr.write("");
        //wr.flush();

        //read the result from the server
        rd  = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        sb = new StringBuilder();

        while ((line = rd.readLine()) != null)
        {
            sb.append(line + '\n');
        }

        return(sb.toString());

    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (ProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    finally
    {
        //close the connection, set all objects to null
        connection.disconnect();
        rd = null;
        sb = null;
        wr = null;
        connection = null;
    }
    return null;
  }
}


10. Deploy the composite application to the SOA Suite. Test the Web Service exposed by the Mediator – to have for example the string "Hello World" translated from english to french.

Reusing the Translator component

We now have a reusable SOA/SCA component, based on the RESTful translation service – and a way to create such a component for every RESTful service. That is nice. To celebrate, let's use this component in the following scenario:

A file with a list of words (in some source language) arrives on the file system. A SOA application picks up the file and translates every word into an indicated target language. The result of this operation is a file with all the translation results

How to set this up?

The Mediator Routing rule is configured as:

 

Resources

Download JDeveloper 11g Application (11.1.1.2): HttpBindingRestfulTranslationService.zip.

Article on Jersey and calling RESTful services: http://technology.amis.nl/blog/6678/leveraging-restful-services-from-java-application-using-jersey-introduction .

Article on SOA Suite 11g Mediator Java Callout: http://technology.amis.nl/blog/6669/soa-suite-11g-introducing-mediator-java-callouts-for-debug-audit-and.

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

2 Comments