Skip to content

SWRLBuiltInBridge

Martin O'Connor edited this page Jun 9, 2017 · 36 revisions

The SWRL Built-in Bridge is a component of the SWRLAPI that provides mechanisms to define Java implementations of SWRL built-ins, to dynamically load these implementations, and to invoke them from a rule engine.

Note that the approach described here applies to the 3.0.0 or later release of the SWRLAPI.

Table of Contents

SWRL Built-Ins

SWRL provides a very powerful extension mechanism that allows user-defined methods to be used in rules. These methods are called built-ins and are predicates that accept one or more arguments. A number of core built-ins are defined in the SWRL Submission. This core set includes basic mathematical operators and built-ins for string and date manipulations.

An example use of a core SWRL mathematical built-in called swrlb:greaterThanOrEqual to indicate that a person with an age of 18 or greater is an adult is:

 Person(?p) ^ hasAge(?p, ?age) ^ swrlb:greaterThanOrEqual(?age, 18) -> Adult(?p) 

When executed, this rule would classify individuals of class Person with an hasAge property value of 18 or greater as members of the class Adult. The swrlb qualifier before the built-in name indicates the alias of the namespace containing the built-in definition. In this case, it indicates that the built-in comes from the core SWRL built-in ontology.

Users can also define their own built-in libraries. Example libraries could include built-ins for currency conversion, and statistical, temporal or spatial operations. Again, these user-defined built-ins can be used directly in SWRL rules.

Defining Built-Ins in the SWRLAPI

The built-in bridge provides a mechanism for defining and dynamically loading built-in implementation written in Java. Users wishing to provide implementations for a library of built-in methods must first define a Java class that contains definitions for all the built-ins in the library.

The bridge is expecting this built-in implementation class to be called SWRLBuiltInLibraryImpl. This class must extend the abstract base class AbstractSWRLBuiltInLibrary. This base class expects its subclasses to define a zero argument constructor that passes a library name to its superclass constructor, and also to define the method reset. The reset method does not take any arguments. This method is called when the library is first loaded and each time an associated rule engine bridge is reset. If a library does not maintain any state, it can leave its reset body implementation empty.

The package name of the SWRLBuiltInLibraryImpl class should be the namespace qualifier of the built-in library appended to the Java package name org.swrlapi.builtins. For example, the core SWRL built-in string:stringsEqual should be defined as a method called stringsEqual in the class SWRLBuiltInLibraryImpl which should be located in the Java package org.swrlapi.builtins.strings.

This implementation class for a library named strings could look something like the following:

  package org.swrlapi.builtins.strings;
  
  import org.swrlapi.builtins.AbstractSWRLBuiltInLibrary;
  import org.swrlapi.exceptions.SWRLBuiltInException;
  
  private static final String PREFIX = "strings";
  
  private static final String NAMESPACE = "http://swrl.stanford.edu/ontologies/built-ins/5.2.0/strings.owl#";
   
  private static final String[] BUILT_IN_NAMES = { "stringsEqual" };
  
  public class SWRLBuiltInLibraryImpl extends AbstractSWRLBuiltInLibrary  
  {
    public SWRLBuiltInLibraryImpl()
    {
      super(PREFIX, NAMESPACE, new HashSet<>(Arrays.asList(BUILT_IN_NAMES)));
    }
    
    @Override public void reset () { }
    ....
    // Built-in method definitions here
    ....
    }
  }

Each implementation of a specific built-in in a SWRLBuiltInLibraryImpl class should have a signature of the form:

 public boolean <name>(List<SWRLBuiltInArgument> arguments) throws SWRLBuiltInException 

The single arguments parameter is a list containing one or more SWRLBuiltInArgument objects. The most common types of arguments that can be passed to built-in methods are described by classes SWRLClassBuiltInArgument, SWRLNamedIndividualBuiltInArgument, SWRLObjectPropertyBuiltInArgument, SWRLDataPropertyBuiltInArgument, and SWRLLiteralBuiltInArgument.

SWRLNamedIndividualBuiltInArgument objects contain the name of an OWL individual; SWRLLiteralBuiltInArgument objects contain OWL literals, such as integers or strings; and SWRLClassBuiltInArgument, SWRLObjectPropertyArgument and SWRLDataPropertyArgument objects contain OWL class and property names, respectively.

Each built-in class must declare the exception SWRLBuiltInException. This abstract class has concrete subclasses for the four possible exceptions that can be thrown by a built-in implementation:

  • InvalidSWRLBuiltInArgumentNumberException - used to indicate that an incorrect number of arguments have been passed to the built-in.
  • InvalidSWRLBuiltInArgumentException - used used to indicate that an argument of an incorrect type has been passed to the built-in.
  • SWRLLiteralException - used to indicate that literal argument is invalid.
  • SWRLBuiltInNotImplementedException - used to indicate that a built-in (or variants of it for a particular argument type) has not been implemented.

The SWRL built-in specification states that a built-in should evaluate to false if it is supplied with invalid arguments. So, strictly speaking, a built-in should not throw an exception if it is supplied with invalid arguments but should simply return false. However, debugging of rule bases could be difficult if this approach were adopted. Built-in implementors are free to not throw exceptions and adopt the more formally correct semantics by simply returning false on any error conditions.

Here is an example Java method defining a built-in called stringEqualIgnoreCase from the core SWRL built-in library:

 public boolean stringEqualIgnoreCase(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int argument1Number = 0, argument2Number = 1, numberOfArguments = 2;
 
  checkNumberOfArgumentsEqualTo(numberOfArguments, arguments.size());
 
  String argument1 = getArgumentAsAString(argument1Number, arguments);
  String argument2 = getArgumentAsAString(argument2Number, arguments);
 
  return argument1.equalsIgnoreCase(argument2);
 }

This method illustrates the use of a utility methods defined in AbstractSWRLBuiltInLibrary that can be used to process arguments and generate appropriate exceptions. This class is documented below.

Loading a Built-in Implementation Class at Runtime

To allow the SWRLAPI to find a built-in implementation class at run time, it must first be placed in a JAR file. Prefix. Place in a directory called swrl-builtins.

A repo containing an example built-in library can be found here: https://github.com/protegeproject/swrlapi-builtin-library-example

Argument Processing in Built-ins

Built-in implementations can operate directly on the objects that are passed in their argument lists. However, the AbstractSWRLBuiltInLibrary class provided an array of methods to facilitate easier built-in argument processing. It includes methods for checking the number and types of arguments and for getting the values of arguments as basic Java types. It will also generate appropriate exceptions that will automatically identify the offending argument and the names of the invoking rules and built-in. For example, the checkNumberOfArgumentsEqualTo method will generate the exception InvalidBuiltInArgumentNumberException if the specified number of arguments are not passed it.

A group of methods named getArgumentAsA''Type'' are provided to convert arguments to a basic Java types. For example, the getArgumentAsALong method can be used to extract a long value from a supplied argument object and will throw an InvalidBuiltInArgumentException if the argument can not be converted to a long. These methods are permissive and try to perform all valid type conversions. So, for example, if a short in passed as an argument, the getArgumentAsALong method will return it as a long. The getArgumentAsAString built-in is the most general and will return a string for any argument type. If an invalid conversion is attempted a SWRLLiteralException is thrown.

A group of methods named checkThatArgumentIsA''Type'' can be used to check that the supplied argument is exactly of the type specified and will throw exceptions if it is not. Related methods that perform a check without throwing an exception are named isArgumentA''Type''. There is also a group a method named checkThatAllArgumentsAre''Type''s that can be used to determine if all arguments are of a particular type.

Three additional useful methods are getArgumentAsAClassName, getArgumentAsAPropertyName and getArgumentAsAnIndivdualName, which can be used to process OWL class, property, and individual arguments. These methods also have associated check methods.

Assigning Values to Built-In Arguments

Built-ins can also assign (or bind) values to arguments but only if those arguments are unbound. For example, the SWRL atom swrlb:add(?x, 2, 3) uses the core SWRL built-in add method to add two integer literals. When this built-in is invoked by a rule engine and ?x is unbound, the rule engine is expecting that the built-in will assign a value to ?x. The bridge indicates to built-ins that an argument is unbound and that they are expected to assign a value to it by passing null as that argument value.

The built-in method is responsible for assigning values to unbound argument. It should construct an object of the appropriate type with the bound value and set the appropriate bound argument position in its argument list. The assigned value will be passed back to the bridge and ultimately sent back to the rule engine. The four main types of objects that can be returned from a built-in are SWRLClassBuiltInArgument, SWRLObjectPropertyBuiltInArgument, SWRLDataPropertyBuiltInArgument, SWRLNamedIndividualBuiltInArgument, and SWRLLiteralBuiltInArgument, all of which are subinterfaces of SWRLBuiltInArgument. These classes have factory methods defined in theAbstractSWRLBuiltInLibrary class.

Of course, not all built-ins will assign values to parameters or may only assign values to some of their arguments. If a built-in is incorrectly passed an unbound argument when it is not expecting any or is passed an unbound argument in an unexpected position it should throw an InvalidBuiltInArgumentException. Also, assignments to bound built-in arguments are ignored and do not change any corresponding value in the ontology.

Here is an example showing an implementation of the stringConcat method in the core SWRL built-in library. In this implementation the method deals with assignment of unbound first arguments only and will throw an exception if unbound arguments are passed in any other positions.

 public boolean stringConcat(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int resultArgNumber = 0, minimumNumberOfArguments = 2;
  String opResult = "";
 
  checkNumberOfArgumentsAtLeast(minimumNumberOfArguments, arguments.size());
  checkForUnboundNonFirstArguments(arguments);
 
  // Exception thrown by getArgumentAsAString if argument is not a string.
  for (int argNumber = 1; argNumber < arguments.size(); argNumber++) {
   opResult = opResult.concat(getArgumentAsAString(argNumber, arguments));
  }
 
  if (isUnboundArgument(resultArgNumber, arguments)) {
   // Bind the result to the first argument (which is unbound so must be a variable)
   SWRLVariableBuiltInArgument resultArgument = arguments.get(resultArgNumber).asVariable();
   resultArgument.setBuiltInResult(createLiteralBuiltInArgument(opResult));
   return true;
  } else {
   String argument1 = getArgumentAsAString(resultArgNumber, arguments);
   return argument1.equals(opResult);
  }
 }

A built-in method that successfully assigns a value to an argument should return true. If a method returns true yet has not assigned all unbound arguments passed to it the built-in bridge will generate an exception. If the method returns false no assignments are expected; any assignments it may have performed will effectively be ignored.

There is no type information for unbound arguments so attempts to use the accessor methods to retrieve an unbound argument as a particular type or to check that it is of that type will cause an InvalidSWRLBuiltInArgumentException to be generated.

Unbound arguments can potentially occur in any positions. For example, the SWRL atom swrlb:add(7, ?x, 1, 3) is satisfiable if the value 3 is bound to the variable x. An implementation of the add built-in can decide to assign a value to this argument if invoked. However, supporting arbitrary position argument binding for built-ins may prove cumbersome and implementors may decide to restrict unbound arguments positioning.

Assigning Multiple Values to Built-In Arguments

Unbound arguments may have more than one valid binding that satisfies the built-in. For example, the built-in invocation swrlb:abs(7, ?x) could be satisfied by both 7 and -7 as bindings for the variable x. A class called SWRLMultiValueVariableBuiltInArgument is provided to return arguments in this multiple-value situation. A multi-argument holds a list of SWRLBuiltInArgument objects. Arguments can be added to a multi-argument using the addArgument call.

The following is an example of an integer implementation of the swrlb:abs built-in that returns a multi-argument.

 public boolean abs(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int argument1Number = 0, argument2Number = 1, numberOfArguments = 2;
  boolean result, isFirstArgumentUnbound, isSecondArgumentUnbound;
  int firstArgument, secondArgument;
   
  checkNumberOfArgumentsEqualTo(numberOfArguments, arguments.size());
  isFirstArgumentUnbound = isUnboundArgument(argument1Number, arguments);
  isSecondArgumentUnbound = isUnboundArgument(argument2Number, arguments);
  
  if (!isFirstArgumentUnbound && isSecondArgumentUnbound) {
   SWRLMultiValueVariableBuiltInArgument multiArgument 
     = createSWRLMultiValueVariableBuiltInArgument();
  
   firstArgument = getArgumentAsAnInteger(argument1Number, arguments);
  
   multiArgument.addArgument(createLiteralBuiltInArgument(firstArgument));
   multiArgument.addArgument(createLiteralBuiltInArgument(-firstArgument));
  
   arguments.get(argument2Number).setResult(multiArgument);
    
   result = !multiArgument.hasNoArguments();
  } else ... // Deal with other cases
   return result;
 }
 

An exception will be thrown if an empty multi-argument is passed back from a built-in that returns true.

Some combinations of unbound arguments may have an infinite number of possible bindings. For example, in the built-in invocation swrlb:add(15, 4, 5, ?x, ?y) with x and y unbound, there are an infinite number of bindings pairs that will satisfy this predicate (e.g., (1, 5), (0, 6), (-1, 7)). A built-in can signal its detection of this situation by throwing the SWRLInfiniteBindingBuiltInException.

Some built-in implementation should not allow any unbound arguments because these infinite binding possibilities occur in all or most unbound argument cases. For example, the built-in swrlb:greaterThan has an infinite number of possible bindings for any unbound argument. Supporting binding in this built-in would make little sense.

Standardized Processing of Unbound Arguments

Many built-in repeat the same pattern when processing potentially unbound arguments. They must first check if the relevant argument is unbound and if it is they will process the other bound arguments to calculate a result for that argument and then assign it. If the argument is bound, the built-in will process the other bound arguments and test the result for equality with the bound relevant argument.

For example, here is a built-in that takes two arguments, converts the second to a lower case, and (1) if the first argument is unbound, binds the result to the first argument, and returns true, or (2) if the first argument is bound, determines if the second argument is equal to this argument, and returns true if it is, and false otherwise.

 public boolean toLowerCase(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int resultArgNumber = 0, sourceArgNumber = 1;
  String opResult = getArgumentAsAString(sourceArgNumber, arguments).toLowerCase();
 
  if (isUnboundArgument(resultArgNumber, arguments)) {
   arguments.get(resultArgumentNumber).setResult(createLiteralBuiltInArgument(opResult));
   return true;
  } else {
   String argument1 = getArgumentAsAString(resultArgNumber, arguments);
   return argument1.equals(opResult);
  }
 }

The AbstractSWRLBuiltInLibrary provides a set of convenience methods to handle this pattern. These methods are named processResultArgument and take a variety of result argument types. Each method takes three arguments.

The first the the argument list passed to the built-in, the second is the number of the result argument, and the final argument is a result argument of a particular type. Standard types such as strings, integers, floats, and so on are supported. These methods examine the result argument and (1) bind it to the result argument if it is unbound, or (2) if it is bound, compare its value to the passed result value.

Here, for example, is the above toLowerCase built-in implemented using the string result variant of these methods:

 public boolean toLowerCase(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int resultArgNumber = 0, sourceArgNumber = 1;
  String opResult = getArgumentAsAString(sourceArgNumber, arguments).toLowerCase();
  
  return processResultArgument(arguments, resultArgNumber, opResult);
 }

Accessing the Names of Argument Variables in Built-Ins

Some built-ins may wish to be able to access the names of variables passed as arguments. An example invocation of a built-in of this type could look something like eval(?r, "x + y", ?x, ?y), where the built-in takes the values of variables x and y, inserts them into the mathematical formula, and then binds the result the the variable r. In this case the built-in implementation must be able to retrieve both the name and value of these variable arguments. The SWRLBuiltInArgument class has a method called isVariable to determine if an argument corresponds to a variable value; if it does, a method called getVariableName can be used to retrieve the variable name.

The following example shows an implementation of a built-in that uses this mechanism. It returns true if the first argument is equal to the mathematical expression specified in the second argument, which may use the values and variable names specified by the optional subsequent arguments. If the first argument is unbound, it binds it to the result of the expression. It uses the Java Math Expression Parser library to perform the calculation.

 public boolean eval(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int resultArgNumber = 0, expressionArgNumber = 1, minumumNumberOfArguments = 2;
  double opResult;
 
  checkNumberOfArgumentsAtLeast(minimumNumberOfArguments, arguments.size());
 
  String expression = getArgumentAsAString(expressionArgNumber, arguments); 
 
  if (arguments.size() > minumumNumberOfArguments) { // We have variable arguments
   List<SWRLBuiltInArgument> variableArguments 
     = arguments.subList(minumumNumberOfArguments, arguments.size());
 
   checkForUnboundArguments(variableArguments, "unexpected unbound expression argument");
   checkForNonVariableArguments(variableArguments, "unexpected non variable argument");
 
   for (SWRLBuiltInArgument argument : variableArguments) { 
    String variableName = argument.getVariableName(); 
    double variableValue = getArgumentAsADouble(argument);
    getJEP().addVariable(variableName, variableValue);
   }
  }
 
  // Perform the computation
 
  getJEP().parseExpression(expression);
  if (getJEP().hasError()) 
   throw new SWRLBuiltInException("exception parsing expression '" + expression +
                                  "': " + getJEP().getErrorInfo());
  opResult = getJEP().getValue();
  if (getJEP().hasError()) 
   throw new SWRLBuiltInException("exception parsing expression '" + expression + 
                                  "': " + getJEP().getErrorInfo());
  
  // Evaluate the result and assign result to first argument if necessary
 
   if (isUnboundArgument(resultArgNumber, arguments)) {
    arguments.get(resultArgNumber).setResult(createLiteralBuiltInArgument(opResult));
    return true;
   } else {
    return (opResult == getArgumentAsADouble(resultArgNumber, arguments));
  }
 }

Exception Handling in Built-Ins

Java runtime exceptions thrown by a built-in are automatically caught and rethrown by the built-in bridge. In addition to providing the offending exception type information and explanatory text, the rethrown exception will automatically identify the name of the built-in that generated the exception and the containing rule name.

As shown above, most exceptions relating to built-in arguments are generated automatically when using the argument processing methods in the AbstractSWRLBuiltInLibrary class. However, if the implementor wishes to supply method-specific text for exceptions relating to an individual argument, they should throw the InvalidSWRLBuiltInArgumentException exception and supply it with that text.

The SWRLBuiltInException class can be used for any other exceptions thrown by a method's implementor.

The following built-in shows the use of the implementor-thrown exceptions SWRLBuiltInMethodException and InvalidSWRLBuiltInArgumentException. The method is expecting a single string argument containing the name of a car and determines if it is manufactured in Japan.

 public boolean isJapaneseCar(List<SWRLBuiltInArgument> arguments) 
   throws SWRLBuiltInException
 {
  final int carArgNumber = 0, numberOfArguments = 1;
  boolean result;
 
  checkNumberOfArgumentsEqualTo(numberOfArguments, arguments.size());
 
  // Throws InvalidBuiltInArgumentException for unbound or non string argument.
  String carName = getArgumentAsAString(carArgNumber, arguments);
   
  CarServer server = new CarServer();
 
  try {
   server.connect();
   // Example of implementor-thrown InvalidBuiltInArgumentException
   if (!server.isValidCarModel(carName))
    throw new InvalidBuiltInArgumentException(carArgNumber, 
              "Car name " + carName + " is invalid.");
 
    result = server.isCarOrigin(carName, "Japan");
  } catch (CarServerException e) {
   // Example of implementor-thrown SWRLBuiltInException
   throw new SWRLBuiltInException("Error connecting to car server: " +
                                  e.getMessage());
  } finally {
    server.disconnect();
  }
  return result;
 }

Accessing the Ontology associated with the invoking Bridge from Built-in Methods

An method called getInvokingBridge is provided by the base SWRLBuiltInLibrary class, which is subclassed by all built-in libraries. This bridge has a method called getOWLDataFactory that can be used to retrieve an OWL data factory associated with the bridge.

Invoking a Built-in Method from a Rule Engine

A built-in method can be invoked by a rule engine through the SWRLRuleEngineBridge class. This class has a method called invokeSWRLBuiltIn that takes the name of a built-in and a list of SWRLBuiltInArgument objects for that built-in. It returns a boolean value that holds the result of the built-in invocation. The built-name passed to the invoke method must be of the form builtInLibraryAlias:builtInName. For example, the add method in the core SWRL built-in library would be referred to as swrlb:add.

The invoke method itself can directly throw three possible exceptions: (1) InvalidSWRLBuiltInNameException, which is used to indicate that a supplied built-in name is not a valid name for a SWRL built-in, i.e., no OWL individual of class BuiltIn with the name of the built-in exists in the OWL model supplied to the bridge; (2) SWRLUnresolvedBuiltInException, which indicates that no Java implementation method for the built-in could be found in any of the dynamically loaded built-in libraries; and (3) InvalidSWRLBuiltInLibraryImplementationClass, which indicates that the Java implementation class found for the built-in did not correctly implement the interface SWRLBuiltInLibrary.

If a correct implementation is found for the supplied built-in, it is invoked and is supplied with the argument list passed to the invoke method. As discussed above, a built-in implementation can throws four possible exceptions (which are also subclasses of the SWRLBuiltInException class). If an exception is throw, it is passed directly back to the caller. If the method executes successfully, its (boolean) return value is passed back from the invoke method.

It is worth noting that there may be considerable overhead to invoking built-in implementations from inside a rule engine. In addition to marshalling and unmarshalling built-in arguments, the process of invoking external methods from inside a rule engine during rule evaluation can dramatically slow its execution. This overhead may not be large if the rule engine is Java-based, but for non Java-based engines it may be significant. In general, if a particular built-in is expected to be used a lot, native rule engine implementations of them should be developed.

Sharing Built-in Libraries

Multiple bridges can share the same library within a single process. Hence, a built-in implementation should not assume that all calls are coming from the same bridge. The bridge does ensure that only one thread is executing a built-in in a particular library at any time. Multiple invocations can take place in different libraries simultaneously, however.

The base SWRLBuiltInLibrary class provides the method getInvokingBridge that can be used to determine the invoking bridge. It also provides the methods getInvokingRuleName and getInvokingBuiltInIndex, which provide the name of the invoking rule and a unique-pre-rule index of the invoking built-in.

The bridge also guarantees that the library reset method is not called when a built-in is being executed in that library. The getInvokingBridge method can be called within the reset method to determine which bridge invoked it. If a library maintains per-bridge state, it can use this method to partition that state appropriately so that a reset from a single bridge does not affect all bridges.

Built-In Invocation during Rule Processing

During rule evaluation there is no absolute guarantee that antecedent built-ins are invoked only once for each unique argument pattern. In some cases, an antecedent built-in may be invoked multiple times with identical arguments. This repeated invocation may create inefficiencies: if a built-in performs a time-consuming calculation, it may repeat those calculations unnecessarily. The method createInvocationPattern in the AbstractSWRLBuiltInLibrary class can be used to help keep track of such invocations. This method uses the information returned by the SWRLBuiltInLibrary calls getInvokingBridge, getInvokingRuleName and getInvokingBuiltInIndex in addition to the arguments passed to the built-in to generate a unique key for every built-in invocation. These strings can be stored by the associated library and used to keep track of repeated invocations.

This problem does not arise with consequent built-ins where invocation is controlled by rule firing.

Clone this wiki locally