Creating Custom XPath Functions using Oracle JDeveloper

This posting provides an example of how to extend JDeveloper and SOA Suite 11.1.1.5 with custom XPATH Functions.
The example includes Custom functions for use with both Mapper and Expression Builder.

The first function is a Mapper function that can be used in the JDeveloper XLST Mapper.
The  “Check Department” function receives a XML String node representing a department number and
returns a default department value of 999 if no value is present.

The second function is an example of an Expression Builder function that can be used in the JDeveloper in a BPEL,
 Mediator or Worklist Component.  The function receives a list of Employees and sorts them based on their department code.
The SOA Suite Developer's Guide does a good 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 a more detailed example using SOA Suite 11.1.1.5
Implementation consists of the following Development and Run time activities:

Development Time Tasks
1) Implement a Java function for each custom function.
2) Add a definition for each custom function to the appropriate xml configuration file
3) Package the class files and configuration files in a jar
4) Register the functions jar with JDeveloper so that the functions appear in the JDeveloper tooling
5) Create a new Composite project and use the functions in the JDeveloper Expression Editor and XSLT Mapper

Run Time Tasks

6) Register the functions with the SOA Server
7) Test the Composite using EM

 

Step 1: Implement Java Functions

Start by creating a new Application in JDeveloper and adding a Java Project.
From the Jdeveloper main menu choose File->New
Then select Applications from the General Category and select the “Generic Application” item”
Press the OK Button
In Step 1 of the wizard provide a name a name for the Application and press the next button.
In Step 2 provide a name for the Java Project  and select Java from Project Technologies tab and
press the arrow to move it to the Selected column.  Then press the next button.
In Step 3 provide a default Java package for example:  “com.example.reusable.asset” then press the Finish button.

The newly created Java Project needs to include the oracle.soa.fabric.jar in its classpath to support custom functions.
Right click on the new Java Project in the Application Explorer and choose “Libraries and Classpath”
Click the “Add Jar” button and navigate to the oracle.soa.fabic.jar in the
  ${FMWHome}/Oracle_SOA1/soa/modules/oracle.soa.fabric_11.1.1/oracle.soa.fabric.jar

Project
          Properties Dialog

Next we will add the two java classes that implement or functions.

Right click on the Java Project in Application Explorer and select add a New.
Expand the General Category and select Java, then select Java Class from the list of items.

New Java Class
          Dialog

Call the class CheckDept and verify the Package field contains com.example.reuse.asset

New Java Class
          Dialog


Replace the contents of the file with the following code which implements a simple XLST Mapper 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;

    }

}




Repeat the process to implement the second XPATH function to be used in the JDeveloper Expression Builder.

Create a second Java Class named  SortEmployees.java in the package com.example.reusable.asset


New Java Class
          Dialog


Replace the contents of the generated java file with the following:

    
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<Node> nodes = new ArrayList<Node>();

                  
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());

           }

       

       }





 




Step 2: Add a definition for each custom function to the appropriate xml configuration file

All user-defined functions are defined in a configuration file based on the same schema.
The name of the configuration file determines where the functions appear in the tooling.

The first function example is a XLST Mapper Function.
All XSLT Mapper function definitions are placed in a single file named ext-mapper-xpath-functions-config.xml
Add an XML file to the Java Project named ext-mapper-xpath-functions-config.xml:

Mapper Config
          Dialog


Replace the file contents with the following text

<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>


The second function is an example of an Expression builder function.
Functions of this type can be 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.

Add an XML file to the Java Project named ext-soa-xpath-functions-config.xml:

SOA Config
          Dialog


Replace the file contents with the following text

<?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>

At this point the Java Project should successfully build.

 

Step 3) Package the class files and configuration files in a jar

Both JDeveloper and the WLS SOA runtime server require the function class files and their
configuration files to be packaged in a jar file.

Configuration files must be located in a folder named META-INF

Jar assembly can be done manually or using a JDeveloper feature called deployment profiles
which can automate the packaging.

For information of the deployment profile approach, see my earlier blog post  at the link below

http://blogs.oracle.com/bwb/entry/creating_jar_deployments_using_jdeveloper


Step 4)  Register the functions jar with JDeveloper so that the functions appear in the JDeveloper tooling


Before the new functions can be used they must be registered with JDeveloper.
From the main menu choose the Tools Menu item and then select SOA from the list on the left.
Press the Add button and navigate to the jar file created in step 3.
Press OK to save the SOA settings.
JDeveloper must be restarted to recognize the changes.

Project Tools
            Settings Dialog


Step 5) Create a new Composite project and use the functions in the JDeveloper Expression Editor and XSLT Mapper

Now it’s time to use the new functions.  From the JDeveloper main menu select File->New
In the New Gallery dialog, select the Current Technologies Tab and select the SOA Category
Finally Choose the SOA Project item and click the OK Button.

New Project
            Gallery Dialog

The New Project Wizard will appear.
In Step 1 of the wizard, choose a name for the new project or accept the default.
In Step 2 of the wizard, select the “Empty Composite” template and press the finish button.
A composite.xml file should open in the editor.
 

Before working with the composite, a Schema file is needed to define the data used to test the custom functions.

Right click on the new SOA project in the Application Explorer and select New.
In the New Gallery Dialog, select the “All Technologies Tab”, expand the “General” node,
select the “XML”  Category, then select “XML Schema” from the list of items.


New XML Schema
            File Dialog




Name the new file   FileEmployee.
Once the file is created it will open in the editor.  
Select the Source tab at the bottom of the FileEmployee.xsd window and replace the
contents of the file with the following text:

<?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>


Save the file contents.
Return to the open composite.xml window.

Drag a BPEL Process Icon from the component palette to the center “Components” section of the composite.xml window.
A Create BPEL Process dialog should appear.
In the dialog choose a name for BPEL process or accept the default.
Select a Template of “Synchronous BPEL Process”

Press the Magnifying glass next to the input Field, in the Type Chooser dialog that appears
expand “Project Schema Files” and Select the “Employees” node inside the FileEmployees.xsd

Repeat the same process for the output field, selecting Employees for the output type.
Note that Employees should be the type for both the in the input and output.

New BPEL
            Process Dialog

Press the OK Button to Create the Process.

The Composite window should look like the image below.


Composte with
          Single Bpel component


Double click the BPEL Process icon in the composite window.

Add an Transform node that utilizes a custom Function

Drag and drop a “Transform” icon from the Component Palette under the Oracle Extensions section,
to the BPEL diagram after the receive input Node.

Double click the transform node, and Transform Properties Dialog will appear.

Transform input
          Dialog

Press the Green Plus icon and press the OK button and accept the value inputVariable as the input to the Transform.
Click the Dropdown box under the Target Variable and select  “inputVariable.
Note: Ensure in the last step,  both the Source and Target are set to inputVariable.
Press the OK button
          
The XSLT Mapper editor should appear
Drag the Employees Node from the Left Source Side to the Employees node on the Right side.
Press the OK button on the “Auto Map Preferences” dialog and the OK button on the confirm dialog.

The new checkDept() function will set the dept node, so delete the existing mapping for dept by
right clicking on the solid wire between the dept nodes and choose delete.
After the delete, a dash lined may be visible between departments.

 

Now it’s finally time to use the new function.
Click the dropdown at the top of the Component Palette and choose the “User Defined” category.
The checkDept and sortEmployees functions should be present.

Component
          Functions Dialog

Drag and drop the checkDept function onto the center of Mapper window.
Click on the bump on the left side of the f(..)  icon and drag it to the dept node on the left.
Then drag the bump on the right side of the function to the dept on the right.
The final mapping should appear as follows:

JDeveloper XSLT
          Mapper

Add an Assign node that utilizes a custom Function
Return to the BPEL editor window.
Drag an Assign node from the Component Palette under the BPEL Constructs section to the BPEL Diagram,
placing it after the Transform1 node.

JDeveloper BPEL
          Editor

Double Click the new Assign node in the diagram, the Edit Assign dialog should open.

Edit Assign
          Dialog

Expand the input Variable tree on the RIGHT side of the Edit Expression dialog so that
 the ns1:Employees : EmpCollection node is visible.
In the upper right corner of the dialog, Drag the Fx icon (left of the Green puzzle piece)
and drop it onto the EmpCollection on the right.

The Expression builder window should open.
Select the Dropdown under the Functions section and choose “User Defined Extension Fuctions”
The sortEmployees() function should be in the list.

Press the “insert Into Expression” button in the center of the screen.
The text “mf:sortEmployees()” should appear in the Expression field.
Use the mouse to set the cursor between the parenthesis in  mf:sortEmployees( )”
Expand the inputVariables tree under in the BPEL Variables section so that the ns1:Employees : EmpCollection node is visible.
Select the ns1:Employees node and press the Press the “insert Into Expression” button in the center of the screen.

The final Expression should appear as follows

Expression
          Builder with Completed Expression

Press the OK Button
 

The final Edit Assign node should appear as follows

Completed Edit
          Assign Dialog



This completes the Composite BPEL Process

Build the project by Right clicking on the project in the application explorer and choose  Make projectName


 

Step 6 Register the functions with the SOA Server

The jar file containing the functions must be deployed to the WLS Servers hosting the SOA runtime.

Copy the .jar file containing the functions to the Oracle_SOA1/soa/modules/oracle.soa.ext_11.1.1/ 
folder under the Fusion Middleware Home.

For example:
      cp functions.jar    /home/oracle/Oracle/Middleware/Oracle_SOA1/soa/modules/oracle.soa.ext_11.1.1/

After copying, the final step is to rebuild the registry classpath entries so the SOA runtime is aware of the new functions.

Open a command prompt and change the current directory to the oracle.soa.ext_11.1.1 directory ,
then execute the build.xml file in the oracle.soa.ext_11.1.1 folder using Ant.

This task must be repeated only if the jar is renamed in the future.

Restart the WLS Server that is hosting the SOA Suite Partition so that the new classes are loaded.


Note:
               If Ant is unavailable, the configuration can be done manually as follows:
               Extract the META-INF/MANIFEST.MF file from the oracle.soa.ext.jar file.
               Update the classpath entry in the MANIFEST.MF file to include the name of the new function jar
               For example    Class-Path: myFunctions.jar classes/
               Update  the jar file with the modified META-INF/MANIFEST.MF file.


 

Step 7)  Test the Composite using EM

Deploy the SOA Composite to the SOA Server using JDeveloper.

Load the EM console at http://localhost:7001/em
Substitute your WLS Admin server name for localhost and your server port for 7001

Navigate to the composite and choose the Test Button.
Select the Request Tab and paste the following test message into the XML Input field.

Press the test button, the response should be identical to the response shown below.

Sample SOAP Input Test Message for Enterprise Manager Test Page

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
                <soap:Body xmlns:ns1="http://www.example.org">
<ns1:Employees>
  <ns1:Emp>
    <ns1:number>2315</ns1:number>
    <ns1:firstName>Bob</ns1:firstName>
    <ns1:lastName>Jones</ns1:lastName>
    <ns1:job>Accountant</ns1:job>
    <ns1:hiredate></ns1:hiredate>
    <ns1:dept>103</ns1:dept>
 </ns1:Emp>
  <ns1:Emp>
    <ns1:number>2300</ns1:number>
    <ns1:firstName>David</ns1:firstName>
    <ns1:lastName>Last</ns1:lastName>
    <ns1:job>President</ns1:job>
    <ns1:hiredate></ns1:hiredate>
    <ns1:dept></ns1:dept>
  </ns1:Emp>
  <ns1:Emp>
    <ns1:number>1300</ns1:number>
    <ns1:firstName>Betty</ns1:firstName>
    <ns1:lastName>Black</ns1:lastName>
    <ns1:job>Sales Rep</ns1:job>
    <ns1:hiredate></ns1:hiredate>
    <ns1:dept>101</ns1:dept>
  </ns1:Emp>
</ns1:Employees>
    </soap:Body>
</soap:Envelope>


Sample Response received by Enterprise Manager Test Harness

Success is indicated by a response showing that the empty department has been replaced by dept 999
and that all employees are sorted by their department number in ascending order.

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <env:Header>
                                <wsa:MessageID>urn:9B28DF60905211E0AF01B333A49C5543</wsa:MessageID>
                                <wsa:ReplyTo>
                                                <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
                                </wsa:ReplyTo>
                </env:Header>
                <env:Body>
                                <Employees xmlns:tns=http://oracle.com/sca/soapservice/XPathFunctionExample/Project1/bpelprocess1_client_ep
                                                      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:inp2=http://xmlns.oracle.com/singleString
                                                      xmlns:ns0="http://www.example.org" xmlns="http://www.example.org">
                                                <ns0:Emp>
                                                                <ns0:number>1300</ns0:number>
                                                                <ns0:firstName>Betty</ns0:firstName>
                                                                <ns0:lastName>Black</ns0:lastName>
                                                                <ns0:job>Sales Rep</ns0:job>
                                                                <ns0:hiredate/>
                                                                <ns0:dept>101</ns0:dept>
                                                </ns0:Emp>
                                                <ns0:Emp>
                                                                <ns0:number>2315</ns0:number>
                                                                <ns0:firstName>Bob</ns0:firstName>
                                                                <ns0:lastName>Jones</ns0:lastName>
                                                                <ns0:job>Accountant</ns0:job>
                                                                <ns0:hiredate/>
                                                                <ns0:dept>103</ns0:dept>
                                                </ns0:Emp>
                                                <ns0:Emp>
                                                                <ns0:number>2300</ns0:number>
                                                                <ns0:firstName>David</ns0:firstName>
                                                                <ns0:lastName>Last</ns0:lastName>
                                                                <ns0:job>President</ns0:job>
                                                                <ns0:hiredate/>
                                                                <ns0:dept>999</ns0:dept>
                                                </ns0:Emp>
                                </Employees>
                </env:Body>
</env:Envelope>


Download the complete example as a zip from GitHub Here