In this recipe, we will create a custom XPath function in java that can be used by any composite component in an assign or transform. This enables us to add complex logic into our XPath expressions without making an explicit call to Java. We will use the example of calculating the mean and standard deviation of a set of numbers.
Our XPath function will have a signature, as shown in the following code snippet:
double getStdDev(values as node-set)
It will take the value of each node in the input node set, calculate it, and return the standard deviation.
In JDeveloper create a new Java project. From the new projects Project Properties, choose the Libraries and Classpath tab and choose Add Library to add the SOA Runtime library and the Oracle XML Parser v2 library to the project. Click on OK until the Project Properties dialog closes.
oracle.xml.parser.v2.XMLNodeList
as the input parameter (this is the input to our function) and returns a java.lang.Double
(the output of our function). The method name is the name of our XPath function.This is the method that we will use to provide the implementation of our custom XPath function. It is also the method we will register with the XSLT engine.
A sample class is shown as follows:
package soa.cookbook.xpath;
import oracle.xml.parser.v2.XMLNodeList;
public class StdDev {
public static double getStdDev(XMLNodeList nodes)
{
return null;
}
}
The input parameter to our XPath function is a list of nodes that we can iterate over through each node:
for (int i = 0; i < nodes.getLength(); i++) { try { Node node = nodes.item(i);
We can then check that the node is of the expected type; in our case we expect an element:
if (node.getNodeType() == Node.ELEMENT_NODE) {
We can then access the value of the element as a string and parse it into the double we expect:
double value = Double.parseDouble(node.getTextContent());
We wrap the parameter processing in a try-catch block so that we can ignore any unexpected data types.
} } catch (Exception e) { ; // Ignore non-numeric values }
Our implementation makes use of the Apache Commons Math library, and so we need to add that library to our Project Properties by going to the Libraries and Classpath tab and choosing Add Library. We can then click the New button to add a new library to JDeveloper.
On the Create Library dialog, we can specify a name for the library and then choose the Class Path and click Add Entry to bring up the Select Path Entry dialog, which will allow us to choose the JAR file or classpath that we need to add to find the classes in the library.
We can then specify the Source Path and Doc Path if those are also available for the library. Finally, we add the library by clicking on OK and then select the library from the Add Library dialog to include it in our project.
To implement the function logic, we begin by executing the initialization code before we start iterating over the nodes in the XMLNodeList
.
DescriptiveStatistics stats = new DescriptiveStatistics();
for (int i = 0; i < nodes.getLength(); i++) {
XMLNodeList nodes = (XMLNodeList)list.get(0);
Within the iteration of XMLNodeList
, we then add the value of the element to the statistics we are gathering.
double value = Double.parseDouble(node.getTextContent());
stats.addValue(value);
Finally, we return the standard deviation as the result of the XPath function by passing it out as the return value of the method.
return stats.getStandardDeviation();
BPEL and Mediator components can use custom XPath functions, but they have a slightly different interface than custom XSLT XPath functions, so we will now implement that interface—oracle.fabric.common.xml.xpath.IXPathFunction
.
We modify our class to implement the interface by right-clicking on the class name and choosing Source | Implement Interface. We then use the Hierarchy browser in the Implement Interface dialog to select the IXPathFunction
interface.
This creates a new method in our class:
public Object call(IXPathContext iXPathContext, List list) throws XPathFunctionException { return null; }
The call method takes an IXPathContext
and a List
as the input parameters and returns an Object
. The method should declare that it throws an XPathFunctionException
.
The input parameters to the XPath function are available in the list parameter of our method.
We have only one parameter, so we can access it by getting the first item in the list and casting it to the expected type, an XMLNodeList
:
NodeList nodes = (NodeList)list.get(0);
The iXPathContext
provides access to the calling component (Mediator and BPEL) and to any variables declared in that component.
We need to pass the function input parameters to the static method we previously implemented, so we'll just call that method from the new method:
return getStdDev(nodes);
To tell both JDeveloper and SOA Suite about our custom XPath functions, we must create their description in an XML file. The file is called ext-soa-xpath-functions-config.xml
and must be created in the project's src/META-INF
directory.
This file must have the following content:
<?xml version="1.0" encoding="UTF-8"?> <soa-xpath-functions xmlns="http://xmlns.oracle.com/soa/config/xpath" xmlns:stat= "http://www.oracle.com/XSL/Transform/java/soa.cookbook.xpath.StdDev" >
The soa-xpath-functions
element is in the http://xmlns.oracle.com/soa/config/xpath
namespace and must specify a target namespace prefix (with a name of our choosing) that references a namespace made up of two parts. The first part of the namespace must be http://www.oracle.com/XSL/Transform/java/
and the final part must be the canonical class name of the class implementing the static XSLT function, called soa.cookbook.xpath.StdDev
.
The function name
element is used to register the name of the function. The function name is used by XSLT to identify the static method in the class that was previously identified.
<function name="stat:getStdDev">
The className
element identifies the class that implements the BPEL and Mediator call method.
<className>soa.cookbook.xpath.StdDev</className>
The return
element identifies the return type of the function.
<return type="number"/>
The params
element lists the parameters of the function, identifying their names and types.
<params> <param name="data" type="node-set"/> </params>
The desc
element provides the function summary that will appear in the brief description in JDeveloper.
<desc>Returns the Standard Deviation of the values of the input node-set</desc>
The detail
element provides the detailed description that appears in JDeveloper.
<detail>Returns the Standard Deviation of the values of the top level elements in the node-set passed as a parameter. </detail> </function> </soa-xpath-functions>
Having created the descriptor file, we now package the XPath function into a JAR file by going to Project Properties and selecting the Deployment tab. Here, we create a New deployment that will be used to package the XPath function we have created.
In the Create Deployment Profile dialog, we give the profile a name and choose it to be of the type JAR File, and then click on OK.
In the Edit JAR Deployment Profile Properties dialog, we go to the Contributors section of the Project Output section in File Properties and click on the Add button to add a new contributor. Here, in the Add Contributor dialog, we enter the path for any libraries that need to be included in our custom XPath library; in our case we add the Apache Math library.
Having created the profile, we can now deploy it by right-clicking on the project and selecting Deploy to generate our JAR file.
To use the custom function in JDeveloper, we must go to Tools | Preferences and choose the SOA section.
Clicking on Add allows us to locate our newly created JAR file in the deploy directory of our project and register it with JDeveloper.
To use our custom XPath functions in an SOA Suite installation, we need to copy the generated JAR file to $ORA
LE_HOME/soa/modules/oracle.soa.ext_11.1.1
.
We can use our custom XPath function just like any other XPath function by choosing it from Component Palette. It will be listed under the User Defined section.
When we register our XPath JAR file with JDeveloper and SOA Suite, they look in the META-INF
directory for a configuration file that will tell them what functions are being registered and which classes implement those functions. The name of the configuration file varies according to which component types we want to allow to access our XPath function. The format of the file is the same for all the components; the name of the file and the component types they apply to are shown in the following table:
Filename |
Registered Component | |||
---|---|---|---|---|
XSLT |
BPEL |
Mediator |
Human Workflow | |
|
Yes |
Yes |
Yes |
Yes |
|
Yes |
No |
No |
No |
|
No |
Yes |
No |
No |
|
No |
No |
Yes |
No |
|
No |
No |
No |
Yes |
If we do not wish to register our function with the XSLT mapper, because for instance it made use of the name of the currently active composite or component, we would need to provide three identical files to register with BPEL (ext-bpel-xpath-functions-config.xml
), Mediator (ext-mediator-xpath-functions-config.xml
), and Human Workflow (ext-wf-xpath-functions-config.xml
).
When our function is called from XSLT, the parameters to the function map directly onto the parameters of our static method. The parameter mappings from the XSD type in our XPath function to the Java type in our static method are shown as follows:
XPath Function Parameter Type (XSD) |
Java Method Parameter Type |
---|---|
|
|
|
|
|
|
|
|
|
|
A node set will have multiple XML elements at the same level and is useful for when we want to operate across multiple elements; in our example, we used it to pass multiple values for statistical analysis. A tree has a single, top-level XML element that will usually have a number of nested XML elements.
When our function is called from BPEL, Mediator, or Human Workflow, the list of parameters are packaged up into a java.util.List
and passed as a single parameter to our registered class' call
method.
If we have more than one parameter, we can iterate over the list using a for
statement:
For (Object o : list) { ... }
The IXPathContext
parameter for the XPath functions registered with BPEL and Mediator is used to pass information about the calling component. In particular, it can be used to determine the type and name of the calling component, and provides access to any variables in that component.