Creating and Deploying Custom Jena Built-ins

Last revised 01/31/2014 by A. W. Crapo

Introduction

Jena rules can use built-ins to perform various tasks in procedural code. These built-ins have methods called by the Jena rule engine. A built-in can be called from the rule body (premises), from the rule head (conclusions), or from either. Adding custom built-ins to use in your rules is a powerful way to extend the capability of an application. This document describes how to build and how to deploy a set of custom Jena built-ins. For more details, see http://jena.apache.org/documentation/inference/#RULEextensions.

Building the Custom Built-in

A custom Jena built-in must extend the Java class com.hp.hpl.jena.reasoner.rulesys.builtins.BaseBuiltin. Since the deployment is to an Eclipse environment, it is assumed in this discussion that the building of the custom built-ins also takes place in an Eclipse Java development environment. Building a custom built-in consists of the following steps. The Pow built-in is deployed with the SADL-IDE, and will serve as an example.

Step 1: Create an Eclipse Plug-in Fragment Project

To begin, create a new Eclipse Fragment Project (File -> New -> Project..., Plug-in Development -> Fragment Project) to contain the source code for your built-ins. For example, PowBuiltinFragment. Set the Host Plug-in to "com.ge.research.sadl" with some appropriate minimum version (the version you are using of that your users will be using) or leave blank.

Step 2: Create the Source Code for the Built-in

In the fragment project, create the package for your built-in(s) under the source code folder, "src", e.g., com.ge.research.sadl.jena.reasoner.builtin. Then add the built-in class(es) to the package, e.g., Pow.java. Make the Jena jars available on your classpath.

If your built-in is to be used in rule premises it must either return true when it "matches" (false when it doesn't) or bind a value to a variable. In the latter case, the built-in will have one more argument than its inputs, which is the variable to which the output will be bound. For example, the pow built-in provides the capability to take a first argument to the power of the second argument and return the value as the third argument so it takes three arguments. Note that in SADL rules, a built-in that returns a value appears to have 1 fewer arguments than it actually has because the output appears on the left-hand-side of an assignment. The translator takes this into account. For example,

y is pow(2,3)     // assign 2 raised to the power 3 to the variable y

Here is the relevant source code for the Pow class, as an example.

package com.ge.research.sadl.jena.reasoner.builtin;

import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.reasoner.rulesys.BindingEnvironment;
import com.hp.hpl.jena.reasoner.rulesys.RuleContext;
import com.hp.hpl.jena.reasoner.rulesys.Util;
import com.hp.hpl.jena.reasoner.rulesys.builtins.BaseBuiltin;

public class Pow extends BaseBuiltin {
    /**
    * Return a name for this builtin, normally this will be the name of the
    * functor that will be used to invoke it.
    */
    public String getName() {
        return "pow";
    }

    /**
    * Return the expected number of arguments for this functor or 0 if the number is flexible.
    */
    public int getArgLength() {
        return 3;
    }

    /**
    * This method is invoked when the builtin is called in a rule body.
    * @param args the array of argument values for the builtin, this is an array
    * of Nodes, some of which may be Node_RuleVariables.
    * @param length the length of the argument list, may be less than the length of the args array
    * for some rule engines
    * @param context an execution context giving access to other relevant data
    * @return return true if the buildin predicate is deemed to have succeeded in
    * the current environment
    */
    public boolean bodyCall(Node[] args, int length, RuleContext context) {
        checkArgs(length, context);
        BindingEnvironment env = context.getEnv();
        Node n1 = getArg(0, args, context);
        Node n2 = getArg(1, args, context);
        if (n1.isLiteral() && n2.isLiteral()) {
            Object v1 = n1.getLiteralValue();
            Object v2 = n2.getLiteralValue();
            Node pow = null;
            if (v1 instanceof Number && v2 instanceof Number) {
                Number nv1 = (Number)v1;
                Number nv2 = (Number)v2;
                if (v1 instanceof Float || v1 instanceof Double
                    || v2 instanceof Float || v2 instanceof Double) {
                    double pwd = Math.pow(nv1.doubleValue(), nv2.doubleValue());
                    pow = Util.makeDoubleNode(pwd);
                } else {
                    long pwd = (long) Math.pow(nv1.longValue(),nv2.longValue());
                    pow = Util.makeLongNode(pwd);
                }
                return env.bind(args[2], pow);
            }
        }
        // Doesn't (yet) handle partially bound cases
        return false;
    }
}

Step 3: Organize the Manifest File

Organize the fragment using the Organize Manifests Wizard (Overview tab, META-INF -> MANIFEST.MF editor). In particular, on the "Runtime" tab add the package(s) containing your built-in(s) t othe "Exported Packages" and add the "bin" (assuming that's where compiled class files are placed) and the "META-INF" folders to the "ClassPath" so that the contents of these folders will be part of the fragment runtime. On the Overview tab you can increment the version number of your fragment as you make new releases.

Under the Plug-in Fragment project's META-INF folder (which contains the MANFEST.MF file), create a folder named "services". In this folder create a file with the same name as the base class, "com.hp.hpl.jena.reasoner.rulesys.Builtin". In this file, add one line for each custom built-in in the project. For example, the entry for the example built-in above might be as follows.

    com.ge.research.sadl.myapp.jena.builtin.Pow

These names and packages must match exactly what is in your source code. The built-ins are then discovered in the jar file by a service loader and made available to the application. Use of a built-in within the SADL-IDE will cause the definition of that built-in to be added to the configuration.rdf file in the OwlModels folder, which will be used by SadlServer to load built-ins needed for a particular knowledge base.

Step 4: Create a Feature Project

Create a new Feature Project (File -> New -> Project..., Plug-in Development -> Feature Project), e.g., PowBuiltinFeature. On the "Plug-ins" tab of the feature.xml editor, add your new fragment. Add explanation, copyright, and licensing information to the "Information" tab.

Step 5: Create an Update Project

Create a new Update Site Project ((File -> New -> Project..., Plug-in Development -> Update Site Project), e.g., PowBuiltinUpdate.  Open "site.xml" in the default editor and add your feature to the "Site Map" tab. It is a good idea to create a New Category and add your feature under the category. Click on Build All to build the update site. For subsequent releases, you may add new feature versions to the category. If you wish previous version to be available, leave them in the site.xml file. If not, drop old versions by selecting and deleting (delete key).

Deploying the Custom Built-in

To deploy a set of custom Jena built-ins to the SADL-IDE, create an update site to allow users to easily add your custom built-ins to their Eclipse environment. This will normally be a Web site to which they have access with appropriate access control. Copy the contents of your Update Site Project to this update site. The following files and directories with content should be copied to the site:

  1. site.xml
  2. content.jar
  3. artifacts.jar
  4. features
  5. plugins

Users can then use the Eclipse Install New Software function to add the fragment containing your built-ins to their SADL-IDE plug-ins. This will place the new plug-ins on the classpath of the IDE and allow them to be used in your models' Jena rules.

Installing Custom Built-ins in SADL-IDE

The SADL-IDE is a set of plug-ins for Eclipse that implement the SADL language editor and testing environment. The update site for the customer built-ins created by following the instructions above will create an update fragment requiring that the SADL-IDE be installed before the custom built-ins. To install the custom built-ins, select from the Eclipse menu Help -> Install New Software.... Click the "Add..." button and enter a name for the built-ins and the location of the update site created in the step above. Follow the instructions to complete installation of the fragment extending the SADL-IDE with the new built-ins.