Creating Custom XPath Functions using Oracle JDeveloper

JDeveloper supports the addition of custom XPath functions to extend the functions available in both
the XSLT Mapper and the Expression Builder.

Developers can implement new functions by writing Java methods which can then be easily integrated
into the JDeveloper graphical tooling.

Once the functions are registered with JDeveloper they appear
in the Component Palette in the User Defined section.

For example, this image shows the two custom functions in this posting displayed in the JDeveloper Component Palette.

component_palette


The SOA Suite Developer's Guide does a nice job of explaining the configuration steps
for user-defined functions at the following link

http://download.oracle.com/docs/cd/E21764_01/integration.1111/e10224/bp_appx_functs.htm#BEIIAHJH

This posting provides examples of two different custom functions: using SOA Suite 11.1.1.5
  • A simple function for use with the XSLT Mapper
  • A complex example that can be used with the expression builder in a BPEL, Mediator or Worklist component.


A Simple XSLT Mapper Function

The smiple function is named "checkDept", and its intended use is to provide a default department number of 999
if an employee is not assigned to a department

Here is the code for the function

package com.example.reusable.asset;
/**
 * XSLT Specific Mapper function.
* Function is registered in a file with the naming convention ext-mapper-xpath-functions-config.xml
 */
public class CheckDept {
   public static String checkDept(String input){
          if (input == null || input.length() == 0)
                  return "999";
          else return input;
    }
}

Mapper functions are defined in a configuration file named ext-mapper-xpath-functions-config.xml
The following lines define the function for use with JDeveloper and the SOA runtime Server


<soa-xpath-functions version="11.1.1" 
    xmlns=http://xmlns.oracle.com/soa/config/xpath
    xmlns:geo="http://www.oracle.com/XSL/Transform/java/com.example.reusable.asset.CheckDept">
   <function name="geo:checkDept">
  <className>com.example.reusable.asset.CheckDept</className>
 <return type="string"/>
<params>
<param name="value" type="string"/>
</params>
<desc/>
<detail>
<![CDATA[This function returns a department of 999 if a department is not set.]]>
</detail>
</function>
</soa-xpath-functions>

When complete the function can be used as a custom function displayed in the XLST editor.

Mapper

A Complex Expression Builder Function Example

The sortEmployees is a more complex example that sorts a list of employees in ascending order based on their department code.

package com.example.reusable.asset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.xml.xpath.XPathFunctionException;
import oracle.fabric.common.xml.xpath.IXPathContext;
import oracle.fabric.common.xml.xpath.IXPathFunction;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/** Mapper function that can be used in Express Builder of any SOA Suite component.
  *
 **/

    public class SortEmployees implements IXPathFunction {

         public Object call(IXPathContext context,  List args) {

                org.w3c.dom.Node employees = (Node) args.get(0);

                try {
                           sortNodes(employees, false);
                }
                catch (XPathFunctionException ex)
                {
                    System.err.println("Error: SortEmployees XPath function exception, skipped processing: " + ex.getMessage());
                    employees = (Node) args.get(0);
                }   

                return employees;
            }
        
         /**
          * Sorts the nodes in a node list
          * 
          * @param nodeList - list whose nodes will be sorted
          * @param descending - true for sorting in descending order
          */
         public static Node sortNodes(Node inputNode, boolean descending) throws XPathFunctionException {
                   
                   List nodes = new ArrayList();
                   if(inputNode==null) {
                        throw new XPathFunctionException("Input Node is null");
                   }
                   else {    
                       if(inputNode == null)
                           throw new XPathFunctionException("Expected input Node of type http://www.example.org : Employees " +
                                                             "received null document");
                       if(inputNode.getNamespaceURI() == null ||
                           !(inputNode.getNamespaceURI().equals("http://www.example.org")) ||
                           !inputNode.getLocalName().equals("Employees") )
                           throw new XPathFunctionException("Expected input Node of type http://www.example.org : Employees " +
                                                            "received " + inputNode.getNamespaceURI() + " : " + inputNode.getLocalName());
      
                       NodeList employees = inputNode.getChildNodes();                  
                       if (employees.getLength() > 0) {
                          for (int i = 0; i < employees.getLength(); i++) {
                               Node tNode = employees.item(i);
                               nodes.add(tNode);
                          }
                          
                          Comparator comp = new EmpDeptComparator();
                          
                          if (descending)
                          {
                           //if descending is true, get the reverse ordered comparator
                               Collections.sort(nodes, Collections.reverseOrder(comp));
                          } else {
                               Collections.sort(nodes, comp);
                          }

                         for (Iterator iter = nodes.iterator(); iter.hasNext();) {
                               Node element = (Node) iter.next();
                               inputNode.appendChild(element);
                         }
                       }
                   }
               return inputNode;
              
           }
           }

           class EmpDeptComparator implements Comparator {

           public int compare(Object arg0, Object arg1) {
                   return ((Node) arg0).getFirstChild().getTextContent().compareTo(
                                   ((Node) arg1).getLastChild().getTextContent());
           }
        
       }



 

The data types manipulated by the function are defined by the following employee schema.

<?xml version="1.0" encoding="windows-1252" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
            xmlns="http://www.example.org"
            targetNamespace="http://www.example.org"
            elementFormDefault="qualified">
   <xs:element name="Employees" type="EmpCollection"/>
   <xs:complexType name="EmpCollection">
      <xs:sequence>
         <xs:element name="Emp" type="Emp" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
   <xs:complexType name="Emp">
      <xs:sequence>
         <xs:element name="number" type="xs:int"/>
         <xs:element name="firstName" nillable="true">
            <xs:simpleType>
               <xs:restriction base="xs:string">
                  <xs:maxLength value="10"/>
               </xs:restriction>
            </xs:simpleType>
         </xs:element>
          <xs:element name="lastName" nillable="true">
            <xs:simpleType>
               <xs:restriction base="xs:string">
                  <xs:maxLength value="10"/>
               </xs:restriction>
            </xs:simpleType>
         </xs:element>
         <xs:element name="job" nillable="true">
            <xs:simpleType>
               <xs:restriction base="xs:string">
                  <xs:maxLength value="12"/>
               </xs:restriction>
            </xs:simpleType>
         </xs:element>
         <xs:element name="hiredate" type="xs:date" nillable="true"/>
         <xs:element name="dept" type="xs:int" nillable="true"/>
      </xs:sequence>
   </xs:complexType>
</xs:schema>

Expression Builder functions are placed in different files to restrict usage.

  • Place definitions in the ext-bpel-xpath-functions-config.xml for use in BPEL Components.
  • Place definitions in the ext-mediator-xpath-functions-config.xml for use in Mediator Components.
  • Place definitions in the ext-wf-xpath-functions-config.xml  for use in Human Workflow Components.
  • Place definitions in the ext-soa-xpath-functions-config.xml for use in any of the above Components.

The following lines define the sortEmployee function for use with JDeveloper and the SOA runtime Server .
They are placed in a file named ext-soa-xpath-functions-config.xml file.


<?xml version="1.0" encoding="UTF-8"?>
<soa-xpath-functions version="11.1.1" 
   xmlns=http://xmlns.oracle.com/soa/config/xpath
   xmlns:mf="http://www.oracle.com/XSL/Transform/java/com.example.reusable.asset">
    <function name="mf:sortEmployees">
    <className>com.example.reusable.asset.SortEmployees</className>
    <return type="node-set"/>
    <params>
      <param name="empList" type="node-set"/>
    </params>
    <desc/>
    <detail>
       <![CDATA[This function sorts a list of employees.]]>
    </detail>
   </function>
</soa-xpath-functions>

When complete the function can be used as a custom function in the expression builder.
The following image shows the sortEmployees function in a BPEL assign node.

Mapper

Customer functions and their configuration files must be packaged in a Jar file and registered with both JDeveloper and the SOA Runtime Server.

The complete list of development and runtime configuration steps is too long for a blog post.

For a complete list of steps to create these functions see the Full Post

Comments:

Thanks Bob for excellent posting, I have a similar requirement and building a XPath function to determine the management chain of a user.

Steps:
1. Calling getManagementChain() API, its returning me a list of User objects e.g. <user>jcooper</user><user>jstein</user>...
2. Converting the list to NodeSet using IdentityXPathFunctionUtil and returning.

Query:
How would I determine the remaining set of User information and build node set that can give me entire user object instead of just user name.

Posted by guest on January 15, 2013 at 04:53 PM EST #

Post a Comment:
Comments are closed for this entry.
About

Picture of Bob

I am an Oracle Architect specializing in Service Oriented Architecture and Business Process Management.


Any code presented is for educational purposes only, no warranty or support is implied.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today