Custom XPath functions in Service Bus

Custom XPath functions in Service Bus

Gepubliceerd: Categorie: Oracle

How can you create custom XPath functions in Oracle Service Bus 12c? And how can you use these in both  XSLT and XQuery? Let’s find it out. I like to show you how they’re different in behaviour.

The context of my post comes from one of my projects, where we were migrating quite some (stateless) services from the SOA (BPEL) platform to Service Bus. Since our clients were already ‘virtualized’ to our clients (i.e. clients invoked them through the Service Bus), we could easily change the implementation platform without changing the service contracts.

For the transformation, we were reusing the existing XSL transformations, so a couple of the custom XPath functions had to be made available in Service Bus. Additionally, we were also introducing the DVM (Domain Value Maps) as a replacement for a custom coded lookup-implementation, created when DVMs did not yet exist. For this purpose, we had to create a custom XPath wrapper function, in order to implement some custom logging that the customer did not want to lose.

Test XPath function

As a simple scenario, I am using base-64 encoding and decoding to be implemented as custom XPath functions (code is shared through GitHub). In order to test the custom XPath function, I have created some very simple proxies and pipelines that do not route to any other service but instead just call upon a transformation in order to test my custom functions:

Schema for testing

Child elements

In the reply element I have three different child elements, the first contains the untransformed contents of the input string and the second contains the base64 encoded contents. The last element contains the value after invoking the decode operation on the encoded string, to verify that the inverse operation restores the original value.

Pipeline for test implementation
  1. package com.qualogy.soa.xpath;
  2. import oracle.soa.common.util.Base64Decoder;
  3. import oracle.soa.common.util.Base64Encoder;
  4. import javax.xml.xpath.XPathFunctionException;
  5.  
  6. public class Base64 {
  7.  
  8. /**
  9.   * Apply base64 encoding.
  10.   * @param input
  11.   * @return input after encoding into Base64
  12.   * @throws XPathFunctionException on failure
  13.   */
  14. public static String encodeBase64(final String input) throws XPathFunctionException{
  15. String enc = null;
  16. try {
  17. enc = Base64Encoder.encode(input);
  18. } catch(Exception exc) {
  19. throw new XPathFunctionException("Failed to Base64-encode your input string");
  20. }
  21. return enc;
  22. }
  23.  
  24. /**
  25.   * Apply base64 decoding.
  26.   * @param input
  27.   * @return input after decoding from Base64
  28.   * @throws XPathFunctionException on failure
  29.   */
  30. public static String decodeBase64(final String input) throws XPathFunctionException{
  31. String dec = null;
  32. try {
  33. dec = Base64Decoder.decode(input);
  34. } catch(Exception exc) {
  35. throw new XPathFunctionException("Failed to Base64-decode your input string");
  36. }
  37. return dec;
  38. }

Implementation in Java

The custom XPath function is implemented in Java, as a static method call. The logic itself is already implemented in one of the underlying JARs from the platform, so I am basically wrapping this for both Base-64 encode and decode operations. The only dependency that needs to be resolved is to the oracle.soa.common.util.Base64Decoder & Base64Encoder, by adding the fabric-runtime.jar from your FMW_HOME/soa/soa/modules/oracle.soa.fabric_11.1.1/ to the classpath.

  1. package com.qualogy.soa.xpath;
  2. import oracle.soa.common.util.Base64Decoder;
  3. import oracle.soa.common.util.Base64Encoder;
  4. import javax.xml.xpath.XPathFunctionException;
  5.  
  6. public class Base64 {
  7.  
  8. /**
  9.   * Apply base64 encoding.
  10.   * @param input
  11.   * @return input after encoding into Base64
  12.   * @throws XPathFunctionException on failure
  13.   */
  14. public static String encodeBase64(final String input) throws XPathFunctionException{
  15. String enc = null;
  16. try {
  17. enc = Base64Encoder.encode(input);
  18. } catch(Exception exc) {
  19. throw new XPathFunctionException("Failed to Base64-encode your input string");
  20. }
  21. return enc;
  22. }
  23.  
  24. /**
  25.   * Apply base64 decoding.
  26.   * @param input
  27.   * @return input after decoding from Base64
  28.   * @throws XPathFunctionException on failure
  29.   */
  30. public static String decodeBase64(final String input) throws XPathFunctionException{
  31. String dec = null;
  32. try {
  33. dec = Base64Decoder.decode(input);
  34. } catch(Exception exc) {
  35. throw new XPathFunctionException("Failed to Base64-decode your input string");
  36. }
  37. return dec;
  38. }

Usage of custom XPath functions in XSLT

After compiling this code into a JAR file, you’re good to go as far as XSLT is concerned. The only thing that is required is to put the JAR somewhere in the classpath where the Service Bus can access it (of course, you will need to restart the Service Bus after you placed the jar…). For example:

Making the custom functions available

After making the JAR available (preferably in $DOMAIN_HOME/config/osb/xpath-functions), you can access the custom XPath functions it implements. You should:

  • define a namespace in your XSL transformation that maps to http://www.oracle.com/XSL/Transform/java/com.qualogy.soa.xpath.Base64. The first part is fixed, the second part is the fully qualified classname that implements your custom XPath function
  • invoke the custom XPath function by using a namespace-qualified function name. The namespace is defined in the step above, the function name is the exact same function name as it is being called in the implementation (so that would be encodeBase64 or decodeBase64).

Below the implementation of the XSL transformation (some of the clutter introduced by the JDeveloper mapper has been removed):

  1. <xsl:stylesheet version="1.0"
  2. xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:ns0="http://xmlns.qualogy.com/blog/mnuman/xpath/custom"
  4. xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  5. <xsl:template match="/">
  6. <ns0:CustomXPathResponse>
  7. <ns0:StringToEncode>
  8. <xsl:value-of select="/ns0:CustomXPathRequest/ns0:StringToEncode"/>
  9. </ns0:StringToEncode>
  10. <ns0:EncodedString>
  11. <xsl:value-of select="customxp:encodeBase64(/ns0:CustomXPathRequest/ns0:StringToEncode)"/>
  12. </ns0:EncodedString>
  13. <ns0:RoundTrip>
  14. <xsl:value-of select="customxp:decodeBase64(customxp:encodeBase64(/ns0:CustomXPathRequest/ns0:StringToEncode))"/>
  15. </ns0:RoundTrip>
  16. </ns0:CustomXPathResponse>
  17. </xsl:template>
  18. </xsl:stylesheet>

Proof of the pudding

As always, the proof of the pudding is in the ‘testing’. This can be easily verified after deploying your service using the Service Bus Console’s tester for the proxy service:

Testing the XSLT with custom XPath functions

Usage of custom XPath functions in XQuery

In order to use custom XPath function in XQuery, some additional steps have to be performed, apart from making the JAR file available in Service Bus’ Classpath.

First, you must create a mapping file, mapping the namespace qualified function name that you want to use in XQuery to its Java implementation:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <xpf:xpathFunctions xmlns:xpf="http://www.bea.com/wli/sb/xpath/config">
  3. <xpf:category id="Qualogy Custom Functions">
  4. <xpf:function>
  5. <xpf:name>base64encode</xpf:name>
  6. <xpf:comment>Perform Base-64 encoding on the input string, e.g. base64encode($input as xs:string) as xs:string</xpf:comment>
  7. <xpf:namespaceURI>http://www.qualogy.com/soa/xpath</xpf:namespaceURI>
  8. <xpf:className>com.qualogy.soa.xpath.Base64</xpf:className>
  9. <xpf:method>java.lang.String encodeBase64(java.lang.String)</xpf:method>
  10. <xpf:isDeterministic>false</xpf:isDeterministic>
  11. <xpf:scope>Pipeline</xpf:scope>
  12. <xpf:scope>SplitJoin</xpf:scope>
  13. </xpf:function>
  14. <xpf:function>
  15. <xpf:name>base64decode</xpf:name>
  16. <xpf:comment>Perform Base-64 decoding on the input string, e.g. base64decode($input as xs:string) as xs:string</xpf:comment>
  17. <xpf:namespaceURI>http://www.qualogy.com/soa/xpath</xpf:namespaceURI>
  18. <xpf:className>com.qualogy.soa.xpath.Base64</xpf:className>
  19. <xpf:method>java.lang.String decodeBase64(java.lang.String)</xpf:method>
  20. <xpf:isDeterministic>false</xpf:isDeterministic>
  21. <xpf:scope>Pipeline</xpf:scope>
  22. <xpf:scope>SplitJoin</xpf:scope>
  23. </xpf:function>
  24. </xpf:category>
  25. </xpf:xpathFunctions>

Mapped XQuery function

So in this example, we have mapped an XQuery function  {http://www.qualogy.com/soa/xpath}base64encode to the java method encodeBase64 implemented in com.qualogy.soa.xpath.Base64. Note that you also need to specify the method signature (input/output arguments, whether it is deterministic — it actually is and the allowed use of the function: Pipeline and/or SplitJoin).

Next, you need to make this mapping file available inside your Service Bus runtime environment. This file will be placed alongside the implementation jar. There are no naming restrictions, it should only be a valid XML file.

After restarting the Service Bus, the custom XPath function may also be used from XQuery… or can it?

XQuery Design Time

After defining the custom XPath function to the runtime, we should have been able to use it in an XQuery, but alas: JDeveloper will prevent this since we are yet to make this function available to the design time environment as well:

Usage of custom XPath function in XQuery

As you can see, JDeveloper fails to recognize the new XPath function. We should have made this available to JDeveloper as well, by placing both the JAR and the mapping file in the PRODUCT_HOME/osb/config/xpath-functions for JDeveloper:

Making custom XPath functions available for design time
  1. xquery version "1.0" encoding "utf-8";
  2.  
  3. (:: OracleAnnotationVersion "1.0" ::)
  4.  
  5. declare namespace customxp="http://www.qualogy.com/soa/xpath";
  6. declare namespace ns1="http://xmlns.qualogy.com/blog/mnuman/xpath/custom";
  7. (:: import schema at "../CustomXPathFunctions.xsd" ::)
  8.  
  9. declare variable $inputRequest as element() (:: schema-element(ns1:CustomXPathRequest) ::) external;
  10. declare function local:doEncodeStuff($inputRequest as element() (:: schema-element(ns1:CustomXPathRequest) ::)) as element() (:: schema-element(ns1:CustomXPathResponse) ::) {
  11. <ns1:CustomXPathResponse>
  12. <ns1:StringToEncode>{fn:data($inputRequest/ns1:StringToEncode)}</ns1:StringToEncode>
  13. <ns1:EncodedString>{customxp:base64encode(fn:data($inputRequest/ns1:StringToEncode))}</ns1:EncodedString>
  14. <ns1:RoundTrip>{customxp:base64decode(customxp:base64encode(fn:data($inputRequest/ns1:StringToEncode)))}</ns1:RoundTrip>
  15. </ns1:CustomXPathResponse>
  16. };
  17.  
  18. local:doEncodeStuff($inputRequest)

For XQuery then, the actual invocation just uses the defined namespace qualified name from the mapping file. Of course, the service yields the same results for a happy flow.

Handling failures

What is most interesting, is the different behaviour in case of failures for both transformation engines. In this project, we were surprised a couple of times on different environments that had been configured manually… almost a guarantee for differences. I won’t go into the details, but in more than one occasion, either the JAR file or the mapping file were missing or a wrong version had been made available.

To simulate this behaviour in my ‘Jip-and-Janneke’ (for non-Dutch: simple) scenarios, I moved the JAR and XML file out of the way and restarted the Service Bus. When the actual implementation is unavailable, the outputs for the unhappy flows are differing like night and day:

XSLT Unhappy flow
XQuery unhappy flow

XSLT unhappy flow

As you can see from the XSLT unhappy flow, it simply returns an empty response. But with a regular ‘OK’ status (HTTP 200), whereas the XQuery unhappy flow will return a proper SOAP Fault message showing it could not find the actual custom transformation function.

The XSLT implementation does output some information to the console:

Always inspect the gifts

So at the end of the day you can use your custom XPath function implementations in both XSTL and XQuery. Whatever transformation language you choose to use will depend on things like possibility of reuse of existing resources, knowledge and preference of the developer. Perhaps it is also prescribed by your customer’s or project’s development standards. However, you should be aware that although usage XPath functions in XQuery require some more steps. This also comes with the advantage of clearer error messages. So you may fear the Greeks, but you should always inspect the gifts (see *explanation)!

*Explanation

The quote used for testing “Timeo Danaos et dona ferentes” is taken from the Virgil’s Aeneid and refers to the Trojan Horse.

Milco Numan
Over auteur Milco Numan

Technical Consultant, specialized in integration and design/development of extensions to Oracle e-Business Suite using Developer (Forms/Reports), Workflow, PL/SQL, Java (OAF) and SOA Suite 10g/11g/12c. Certified in Java (SCJP for Java 5), Oracle SOA Suite 11g, Oracle SOA Suite 12c, Oracle BPM Suite 11g, Oracle IT Architecture SOA 2013 Essentials & Oracle Enterprise Linux 6, SOA Architect & SOA Security Specialist (SOASchool.com). View my LinkedIn profile: nl.linkedin.com/in/milconuman/

Meer posts van Milco Numan
Reacties (1)
  1. om 20:08

    How is it different from doing a java callout ?

Reactie plaatsen