Wednesday, July 16, 2008

Custom XPath function in Oracle BPEL PM

Hi,

I'll present you today the way to do custom xpath function in Oracle BPEL Process Manager 10.1.3.x. This stuff is very usefull for basic things (as an isNumber() method, ID generator, ...) or more complex things (as XML operations, business stuff).

I did many projects with Oracle BPM 10, and I've got used to use lots of those functions.


Step 1 - Make a class which implements the IXPathFunction interface

The next class is a little tool which tests if a string is a number.
Class IsNumberFunction
:
package com.bpelsoa.xpath;

import java.util.List;

import org.w3c.dom.Node;

import com.collaxa.cube.xml.dom.DOMUtil;
import com.oracle.bpel.xml.xpath.IXPathContext;
import com.oracle.bpel.xml.xpath.IXPathFunction;
import com.oracle.bpel.xml.xpath.XPathFunctionException;

/**
* This implements a small tool which tests if a String is a Number.
*
* @author Raphaël
*
*/
public class IsNumberFunction implements IXPathFunction {

  /** Number of args for this XPathFunction. */
  private static final int NB_ARGS = 1;

  /** Index in the List of our argument. */
  private static final int INDEX_ARG = 0;

  /**
   * Is called by the BPEL engine.
   *
   * @see com.oracle.bpel.xml.xpath.IXPathFunction#call(com.oracle.bpel.xml.xpath
   * .IXPathContext, java.util.List)
   */
  @SuppressWarnings("unchecked")
  public Object call(IXPathContext context, List args)
      throws XPathFunctionException {

    // test if we have the right argument number
    if (args.size() != NB_ARGS) {
      throw new XPathFunctionException(
          "This function requires one argument.");
    }

    // extract the String of the argument from the BPEL process
    Object o = args.get(INDEX_ARG);
    String str = getValue(o);

    // call the business method
    return isNumber(str);
  }

  /**
   * Extract the value of our String.
   *
   * @param o object
   * @return a string which contains our value
   * @throws XPathFunctionException if error occurs
   */
  private String getValue(Object o) throws XPathFunctionException {
    if (o instanceof String) {
      return ((String) o);
    } else if (o instanceof Node) {
      return DOMUtil.getTextValue((Node) o);
    } else {
      throw new XPathFunctionException("Unknown argument type.");
    }
  }

  /**
   * Test if a String is a Number.
   *
   * @param str
   * the String to be tested
   * @return a boolean
   */
  private boolean isNumber(String str) {
    for (int i = 0; i < str.length(); i++) {
      if (!Character.isDigit(str.charAt(i))) {
        return Boolean.FALSE;
      }
    }
    return Boolean.TRUE;
  }
}

Step 2 - Register the xpath function to your BPEL domain
Edit the xpath-functions.xml file in the $BPEL_HOME/domains/bpelsoa/config directory.
Add these following lines into the bpel-xpath-functions tags.
<function id="isNumber">
  <classname>com.bpelsoa.xpath.IsNumberFunction</classname>
  <comment><![CDATA[This implements a small tool which tests if a String is a Number. The signature of this function is bpelsoa:isNumber(String || Node).]]></comment>
  <property id="namespace-uri">
    <value>http://bpelsoa.blogspot.com</value>
    <comment>Namespace URI for this function</comment>
  </property>
  <property id="namespace-prefix">
    <value>bpelsoa</value>
    <comment>Namespace prefix for this function</comment>
  </property>
</function>

Step 3 - Add your classfiles to the bpel classpath
Create a new directory in your domain home ($BPEL_HOME/domains/bpelsoa) called classes and add your classfiles into it or add your classfiles to your application directory (/product/your_app/classes). Don't forget to add the packages hierarchy.

Edit the shared library of your application server
  • OC4J : $ORACLE_HOME/j2ee/oc4j_soa/config/server.xml
-> locate the shared library called oracle.bpel.common
-> add a tag code-source with your classpath as the others code-source tags.
  • WebSphere 6.1 : In the administration console, go to shared library, select the oracleBPELServer scope, and edit the orabpel_sl lib
-> when editing the shared library, simply add your classpath with the others.


Step 4 - Using your new custom xpath function in a BPEL process
A custom xpath function can be used as any xpath functions. It has to be in an expression field from an assign activity (copy, append, insert after/before, etc.).
<assign name="xpathCall">
  <copy>
    <from expression="bpelsoa:isNumber('123')"/>
    <to variable="myBoolean"/>
  </copy>
</assign>


Enjoy your new xpath.

That's all.

2 comments:

Luiz Eduardo Guida Valmont said...

Is there an easier way to do this on a per application basis? Maybe a couple of application specific files that'll enable certain custom xpath functions only to the app they're part of. Tweaking server.xml and xpath-functions.xml seems a bit intrusive when I think of functions meant to check application specific outputs, use case outcomes and so on.

Raph said...

Unfortunatly, you have no way to define xpath functions for specific process or application.

The cause is simple : the xpath-functions.xml is a "bpel domain scope" descriptor file.

For each domain, you can define a new xpath-functions.xml. That's the best you can do.

Regards.