Creating and Deploying Custom Jena Built-ins

Last revised 3/31/2021. Contact us.
  1. Creating and Deploying Custom Jena Built-ins
    1. Introduction
    2. Building the Custom Built-in
      1. Step 2: Create the Source Code for the Built-in
      2. Step 3: Organize the Manifest File
      3. Step 4: Create a Feature Project
      4. Step 5: Create an Update Project
    3. Deploying the Custom Built-in
    4. Installing Custom Built-ins in SADL-IDE
    5. Enabling Type Checking of Jena Built-in Functions

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 designed to 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 org.apache.jena.reasoner.rulesys.builtins.BaseBuiltin. If desired, the custom built-in can extend the Java class com.ge.research.sadl.jena.reasoner.builtin.CancellableBuiltin, which is a subclass of org.apache.jena.reasoner.rulesys.builtins.BaseBuiltin, in order to provide a hook for making inference in SADL cancelable. The custom built-in may also extend com.ge.research.sadl.jena.reasoner.builtin.TypedBaseBuiltin in order to support type checking of function arguments. For details see section below. Since the deployment is to an Eclipse environment, it is assumed in this discussion that the building of 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, which 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 or 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 you are extending the CancellableBuiltin or TypedBaseBuiltins you will need to also add the com.ge.research.sadl.jena-wrapper-for-sadl project's jar file, found in the target folder.

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

translates to pow(2,3,y) (loosely speaking, this isn't the actual Jena Rule syntax).

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

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

import org.apache.jena.graph.Node;
import org.apache.jena.reasoner.rulesys.BindingEnvironment;
import org.apache.jena.reasoner.rulesys.RuleContext;
import org.apache.jena.reasoner.rulesys.Util;
import org.apache.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) to the "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, "org.apache.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 would be as follows.

    com.ge.research.sadl.jena.reasoner.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 versions 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, place the zip file in the Update Site project in a location that will allow users to easily add download and add your custom built-ins to their Eclipse environment. Or if you are deploying to a Web site to which user will 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

Either from the downloaded zip file or from the Web site, 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 runtime 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 zip file or 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.

Enabling Type Checking of Jena Built-in Functions

To enable type checking of the arguments and returned value of a custom built-in, have them built-in class extend com.ge.research.sadl.jena.reasoner.builtin.TypedBaseBuiltin. This abstract class adds the additional method

abstract public String getFunctionSignatureString();

which provides the reasoner/translator pair with the signature of the function, allowing the arguments provided, as well as the use of the value returned, to be check for type consistency. The method should return the expected signature of the built-in function as a string in the following syntax.

functionName(ar1type, arg2type, ....)returnType

For example, the custom built-in com.ge.research.sadl.jena.reasoner.builtin.Abs (absolute value) has this method.

@Override
public String getFunctionSignatureString() {
   return "abs(decimal)decimal";
}

This signature indicates that the built-in name to be used is "abs", it takes a single argument of type decimal, and it returns a single value of type decimal. (All Jena built-ins are expected to return at most one value.)

Type checking will check for both the correct number of arguments and for the correct type of arguments. Sometimes built-in functions can take a variable number or arguments, or can take arguments of various types. Also, sometimes it is not possible, at least currently, to identify the argument type as is the case for function arguments of type SADL typed list. (There is not a corresponding sadllist:List class in the Xtext model. If a function takes a specific subclass of sadllist:List then that type can be specified for an argument.) To handle these special cases use the following special argument types.

  1. "..." indicates that the number of arguments is not known.
  2. "--" indicates that the type of a particular argument is not specified and so cannot be type-checked.

For example, the com.ge.research.sadl.jena.reasoner.builtin.Print has the following signature method.

@Override
public String getFunctionSignatureString() {
   return "print(--,...)--";
}

This indicates that the first argument can be of any type, and there can be any number of arguments, which will not be type checked. It also indicates that the return value should not be type checked.