Working with External Functions

Implementing XmlExternalFunction
Implementing XmlResolver
Calling External Functions from XQuery

BDB XML allows you to define your own functions that you can access from your XQueries. To do this, you must provide an implementation of XmlExternalFunction, and you must implement a XmlResolver class that resolves which external function to call.

Implementing XmlExternalFunction

XmlExternalFunction implementations only require you to implement the execute() method with your function code. You must also implement a close() method that cleans up after whatever activities your execute() method calls.

The execute() method offers three parameters:

  • XmlTransaction

    This is the transaction in use, if any, at the time the external function was called.

  • XmlManager

    The XmlManager instance in use at the time the function was called.

  • XmlArguments

    An array of XmlResults objects which hold the argument values needed by this function.

For example, suppose you wanted to write an external function that takes two numbers and returns the first number to the power of the second number. It would look like this:

package misc;

import java.io.*;
import java.lang.Math.*;

import com.sleepycat.dbxml.*;

/* External function pow() implementation */
class MyExternalFunctionPow extends XmlExternalFunction {

    public XmlResults execute(XmlTransaction txn,
                              XmlManager mgr,
                              XmlArguments args)
        throws XmlException {

        // Retrieve argument as XmlValue
        XmlResults argResult1 = args.getArgument(0);
        XmlResults argResult2 = args.getArgument(1);

        XmlValue arg1 = argResult1.next();
        XmlValue arg2 = argResult2.next();

        // Call pow()
        double result = Math.pow(arg1.asNumber(), arg2.asNumber());

        // Create an XmlResults for return
        XmlResults results = mgr.createResults();
        XmlValue va = new XmlValue(result);
        results.add(va);

        return results;
    }

    // The base class's close routine will call delete() by default.
    // In order to reuse the object it's necessary to override that with
    // a no-op version of close().
    public void close() {
    }
} 

Implementing XmlResolver

The XmlResolver class is used to provide a handle to the appropriate external function, when a given XQuery statement requires an external function. For this reason, your XmlResolver implementation must have knowledge of every external function you have implemented.

The resolver is responsible for instantiating an instance of the required external function. It is also responsible for destroying that instance, either once the query has been process or when the resolver instance itself is being destroyed. Which is the correct option for your application is an implementation detail that is up to you.

It is possible for your code to have multiple instances of an XmlResolver class, each instance of which can potentially be responsible for different collections of external functions. For this reason, you uniquely identify each resolver class with a URI.

In order to call a specific external function, your XQueries must provide a URI as identification, as well as a function name. You can decide which external function to return based on the URI, the function name, and/or the number of arguments provided in the XQuery. Which of these are necessary for you to match the correct external function is driven by how many external functions you have implemented, how many resolver classes you have implemented, and how many variations on functions with the same name you have implemented. In theory, a very simple implementation could return an external function instance based only on the function name. Other implementation may need to match based on all possible criteria.

For the absolute most correct and safest implementation, you should match on all three criteria: URI, function name, and number of arguments.

For example, suppose you had two external functions: SmallFunction and BigFunction. SmallFunction is a small function that requires few resources to instantiate and is called infrequently. BigFunction is a larger function that opens containers, obtains lots of memory and from a performance perspective is something that is best instantiated once and then not destroyed until program shutdown. Further, SmallFunction takes two arguments while BigFunction takes five.

And XmlResolver implementation for this example would be as follows:

class MyFunResolver extends XmlResolver
{
    private String uri_ = "my://my.fun.resolver";
    XmlExternalFunction bigFunc = null;

    /*
     * Returns a new instance of either SmallFunction or
     * BigFunction if the URI, function name, and number of
     * arguments match.
     */
    public XmlExternalFunction resolveExternalFunction(XmlTransaction txn,
                                                       XmlManager mgr,
                                                       String uri,
                                                       String name,
                                                       int numberOfArgs)
        throws XmlException {

        XmlExternalFunction fun = null;
        if (uri.equals(uri_) && name.equals("bfunc") && 
                                    (numberOfArgs == 2)) {
            // bfunc is reusable.
            if(bigFunc == null)
                bigFunc = new BigFunction();
            return bigFunc;
        } else if (uri.equals(uri_) && name.equals("sfunc") && 
                (numberOfArgs == 1)) {

            fun = new SmallFunction();

        }

        return fun;
    }

    public String getUri(){
        return uri_;
    }

    public void close(){
        if(bigFunc != null)
            bigFunc.delete();
    }
}; 

Calling External Functions from XQuery

In order to use your external functions, you must register the resolver that manages them. You do this with the XmlManager.registerResolver() method. You then set a URI prefix for the URI that you use to identify your resolver. For example:

        try {
        // Create an XmlManager
        XmlManager mgr = new XmlManager();

        // Create an function resolver
        MyFunResolver resolver = new MyFunResolver();

        // Register the function resolver to XmlManager
        mgr.registerResolver(resolver);

        XmlQueryContext context = mgr.createQueryContext();

        // Set the prefix URI
        context.setNamespace("myxfunc", resolver.getUri()); 

To use the external function, declare them in the preamble of your query, and then use them as you would any XQuery function (for a complete explanation of examining query results, see the next section). For example:

declare function myxfunc:sfunc($a as xs:double, $b as xs:double) \
    as xs:double external;
myxfunc:sfunc(2,3);
    

You run this query as if you were running any other query.

        String query1 = "declare function " +
            "myxfunc:sfunc($a as xs:double, $b as xs:double) as " +
            "xs:double external;\n" +
            "myxfunc:sfunc(2,3)";

        XmlResults results = mgr.query(query, context);

        // The first query returns the result of sfunc(2,3)
        while (results.hasNext()) {
                XmlValue va = results.next();
                String out = "2^3 = " + va.asNumber();
                System.out.println(out);
        }

        // If the resolver reuses ExternalFunction objects, it is 
        // responsible for eventually calling delete()
        resolver.close();

        } catch (XmlException xe) {
            String out = "XmlException: " + xe.getMessage() + "\n";
            System.out.println(out);
        }