Function Binder
The function binder utility makes it easy to expose Java methods as SPARQL functions.
Limitations
- Multi-methods are not yet supported - i.e. it is not possible to define multiple Java methods (with varying parameter type lists) as implementations of the same SPARQL function IRI.
Basic Usage
The code below shows how to register a custom “reverse string” function with Jena’s FunctionRegistry
.
/* A class with methods annotated with IRIs via @Iri and/or @IriNs */
public class SparqlFnLibString {
/**
* Definition of the "string-reverse" SPARQL function for use as in:
*
* SELECT * { BIND(<http://www.example.org/reverse>("hello") AS ?x) }
*/
@IriNs("http://www.example.org/")
public static String reverse(String str) {
return new StringBuilder(str).reverse().toString()
}
}
/**
* The entry point of the custom Jena plugin.
* The fully qualified class name must be placed into the file
* src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
*/
public class InitMyJenaPlugin
implements JenaSubsystemLifecycle
{
public void start() {
FunctionBinder binder = JenaExtensionUtil.createFunctionBinder(FunctionRegistry.get());
binder.registerAll(SparqlFnLib.class);
}
}
The registerAll(Class<?>)
method registers all appropriately annotated static methods with the configured function registry. Use the overload registerAll(Class<?>, Object)
to register all appropriately annotated methods of an object. The object must be an instance of the given class.
Default Values
The @DefaultValue("lexicalValue")
annotation can be used to annotate parameters. Once a parameter is annotated with a default value then all remaining parameters must have default value annotations as well.
/**
* A function that increments a value by a given amount. The amount defaults to 1.
*
* SELECT * {
* BIND(eg:inc(1) AS ?x) // uses default value of 1
* BIND(eg:inc(1, 2) AS ?y)
* }
*/
@IriNs("http://www.example.org/")
public static long inc(long value, @DefaultValue("1") int amount) {
return value + amount;
}
Registering Custom Type Conversions
By default, Jena’s TypeMapper
is consulted for mapping between RDF literal types and Java classes. In addition, it is possible to access a FunctionBinder’s underlying converter registry in order to define custom two-way conversions between the TypeMapper’s Java classes and parameter types. For example, Jena’s GeoSPARQL extension uses the GeometryWrapper
Java class to capture geometry RDF literals. However, GeometryWrapper
internally also wraps Java Topology Suite (JTS) Geometry
. In order to allow GeometryWrapper
instances to be used as arguments for Geometry
parameters the following snippet can be used to register custom coercions:
FunctionBinder binder = JenaExtensionUtil.createFunctionBinder(FunctionRegistry.get());
FunctionGenerator generator = binder.getFunctionGenerator();
// Define two-way Geometry - GeometryWrapper coercions
generator.getConverterRegistry()
.register(Geometry.class, GeometryWrapper.class,
geometry -> new GeometryWrapper(geometry, WKTDatatype.URI),
GeometryWrapper::getParsingGeometry)
;
binder.registerAll(SparqlFnLibGeo.class);
class SparqlFnLibGeo {
@IriNs(GeoSPARQL_URI.GEOF_URI)
public static Geometry simplifyDp(
Geometry geom, // Because of the coercion we can use `Geometry` here instead of `GeometryWrapper`
@DefaultValue("0") double tolerance,
@DefaultValue("true") boolean ensureValid) {
DouglasPeuckerSimplifier simplifier = new DouglasPeuckerSimplifier(geom);
simplifier.setDistanceTolerance(tolerance);
simplifier.setEnsureValid(ensureValid);
Geometry result = simplifier.getResultGeometry();
return result;
}
}
VarArgs
Variable argument lists are supported. Thereby, the type Node
accepts any RDF type. The following example shows an excerpt of array
function implementations.
public class SparqlLibArrayFn {
/** SELECT * { BIND(array:of(1, 'string', true) AS ?arr) }*/
@IriNs(JenaExtensionArray.NS)
public static NodeList of(Node... nodes) {
return new NodeListImpl(Arrays.asList(nodes));
}
@IriNs(JenaExtensionArray.NS)
public static Node get(NodeList nodes, int index) {
return nodes.get(index);
}
}