Extending XPath

Extending XPath

Of late I have run come across the need to extend the built in functionality of XPath.  Occasionally you come across transformations or calculations that can't be done in XPath.  For example I have found it tricky to calculate the time interval between two dates.  This is very useful when you have a deadline to meet and want to set a time interval for a given time period from now.  The problem is you only know the time interval from the deadline, not to the dealine.  So lets look at how we would extend XPath functionality to provide some useful additional functions.

Adding a Duration Calculator

The functionality we want is very simple.  Given two XML datetimes we want to be able to calculate the XML time duration between them.  In Java this is very simple.  The following Java method does exactly this.
    public String calculateDifference(Date earlier, Date later) {
        Duration duration = null;
        try {
            long diff = later.getTime() - earlier.getTime();
            duration = DatatypeFactory.newInstance().newDuration(diff);
        } catch (DatatypeConfigurationException e) {
            return e.toString();
        }
        return duration.toString();
    }
So how do we wrap this code up to make it available to BPEL via XPath.  Obviously we could turn it into a web service but it would be more convenient to have this available as an XPath function, avoiding the need for an extra partner link.
To create an XPath function version of this code we follow the instructions in the manual.  Clemens has also blogged about this, but I plan on covering a bit more detail.

Wrapping the Code

To be made available as an XPath function we need to first implement the IXPathFunction interface.  This has a single method that it requires us to implement:
    public Object call(IXPathContext iXPathContext, List list) throws XPathFunctionException
The IXPathContext interface provides access to the environment, principally BPEL variables, and personally I don't feel it should be used much if we are creating true functions.  The List interface is a standard java.util.List that provides access to the parameters, if any.  Here is the implementation of the method.
    public Object call(IXPathContext iXPathContext,
                       List list) throws XPathFunctionException {
        try {
            // Declarations
            DatatypeFactory factory = DatatypeFactory.newInstance();
            XMLGregorianCalendar start = null;
            XMLGregorianCalendar end = null;
            // Verify got two parameters

            if (list.size() != 2)
                throw new XPathFunctionException("calculateDuration(startdate, enddate) needs 2 arguments");
            // Get value of input parameters

            Object p1 = list.get(0);
            Object p2 = list.get(1);
            String sp1 = getParamValue(p1);
            String sp2 = getParamValue(p2);
            // Convert to Date objects and pass in to function

            start = factory.newXMLGregorianCalendar(sp1);
            end = factory.newXMLGregorianCalendar(sp2);
            return calculateDifference(start.toGregorianCalendar().getTime(),
                                       end.toGregorianCalendar().getTime());
        } catch (DatatypeConfigurationException e) {
            throw new XPathFunctionException("Failed to initialise DatatypeFactory",
                                             e);
        }
    }
This implementation verifies that it received two parameters and then extracted the value of those parameters using the following method.
    private String getParamValue(Object param) {
        String value;
        if (param instanceof Element) {
            Node n = (Node)param;
            value = n.getFirstChild().getNodeValue();
        } else if (param instanceof Text) {
            Node n = (Node)param;
            value = n.getNodeValue();
        } else {
            value = String.valueOf(param);
        }
        return value;
    }
This method takes an Element or a String literal and their value.  It assumes that the element has a String value associated with it.
Now that we have implemented the Java code for the XPath function we can deploy it by dropping the classes into the $ORACLE_HOME/bpel/system/classes directory of the BPEL PM server.

Telling BPEL about the New Function

Dr. Strangelove: Of course, the whole point of a Doomsday Machine is lost, if you *keep* it a *secret*! Why didn't you tell the world, EH?

So how do we tell the world, or at least the BPEL PM about our new function?  We add it to the $ORACLE_HOME/bpel/system/config/xpath-functions.xml file.  The appropriate entry is shown below.
    <function id="getTimeDifference" arity="2">
        <classname>ajr.TimeDifference</classname>
        <comment>
            <![CDATA[The signature of this function is <i>ajr:getTimeDifference(time1, time2)</i>.
      The arguments to the function:
      <ol type="1">
          <li>time1 - String or element containing the datetime of the start of the interval</li>
          <li>time2 - String or element containing the datetime of the end of the interval</li>
      </ol>
      Returns: an XML duration formatted string]]>
        </comment>
        <property id="namespace-uri">
            <value>http://ajr/utilities/xpath</value>
            <comment>Namespace URI for this function</comment>
        </property>
        <property id="namespace-prefix">
            <value>ajr</value>
            <comment>Namespace prefix for this function</comment>
        </property>
    </function>
This maps the XPath function ajr:getTimeDifference onto the Java class ajr.TimeDifference.  In addition it tells BPEL that at least 2 parameters (arity="2") are expected.
After these changes and deploying the classes I bounced the container running the BPEL PM to get it to pick up the changes.
We have now implemented the new XPath function and told BPEL about it, so it is time to test it.

Testing the Duration Calculator

To test it I created a BPEL process that accepted as input a duration and then added it to the current time to give a future date.  I then calculated the duration between the two dates using the getTimeDifference XPath funciton that we just registered.  Within the BPEL process I performed 4 copy operations
  1. Copy xp20:current-dateTime() to start element.  This gives a starting date.
  2. Copy the input duration to duration element.  Note that this should be in the format P[xxY][xxM][xxD][T[xxH][mmM][ssS]. For example 'PT10M' indicates 10 minutes whilst 'P1DT2H5S' represents 1 day, 2 hours and 5 seconds.
  3. Add the duration element to the start element using xp20:add-dayTimeDuration-to-dateTime and copy the result to the startPlusDuration element.
  4. Calculate the difference in time between the start element and the startPlusDuration element using the ajr:getTimeDifference function and copy the result to the calculatedDuration element.
The result of a test run is given below:
<start>2007-07-18T17:44:45+00:00</start>
<duration>P1DT2H5M</duration>
<startPlusDuration>2007-07-19T19:49:45+00:00</startPlusDuration>
<calculatedDuration>P1DT2H5M0.000S</calculatedDuration>
Note that the calculated duration has a slightly different textual value but the same semantic value as the original duration, indicating that the funciton is working as expected.

Gotchas Along the Way

The biggest gotcha I have come across is getting the right types passed into XPath functions.  Basically you have three different types that can be passed across
  • Element types
  • Node Lists
  • Strings
If we get the value of an element then it will return null and we actually need to get the value of the first child node which should be a text node if the element has a value.  The XPath function ora:getNodeValue does this for us or it can be done in the XPath functions Java as shown in the getParamValue method.
Node lists are as the name implies lists of elements and we need to iterate over them within our XPath function using Java code to get each individual item.
Finally Strings are exactly what you thouught you where passing in the first place!

Downloads


I have uploaded the XPath code in a JDeveloper project called {manilaSuite.gems.includeGem (64)}.  In addition to the ajr:getTimeDifference function it also has a function called ajr:inspectParameters that will return an XML document listing the structure of all the parameters passed to it.  Useful for exploring all the possible ways in which an XPath extension function can receive data.  To try it out just create a BPEL process with an anyType output element and assign the output of the function to that element.  It is easier to view the return result in the visual flow.
The sample BPEL project to test the time difference function is uploaded as {manilaSuite.gems.includeGem (65)}.

Comments:

Hi Antony, This can be achieved easily using XPath 2.0. See my blog post [Subtracting 2 dateTime values into a duration using XPath 2.0 ] at http://blogs.oracle.com/rammenon/2007/07/18/ for more information. Best! Ram

Posted by Ramkumar Menon on July 18, 2007 at 09:03 AM MDT #

Another simple and interesting way to do this would be through XSLT. See the blog Entry [Subtracting 2 dateTime values into a duration using XPath 2.0] at http://blogs.oracle.com/rammenon/2007/07/18/

Posted by Ramkumar Menon on July 18, 2007 at 09:22 AM MDT #

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

Musings on Fusion Middleware and SOA Picture of Antony Antony works with customers across the US and Canada in implementing SOA and other Fusion Middleware solutions. Antony is the co-author of the SOA Suite 11g Developers Cookbook, the SOA Suite 11g Developers Guide and the SOA Suite Developers Guide.

Search

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