Since the beginning of the work on the r3 ontology and the r3 prototype, it became clear that there was a considerable amount of code that would be shared by r3 main rule engines and the different r3 expression sub-engines (hopefully to be developed for an unlimited set of languages).
In some cases, this sharing of code, may be solved by an adequate distributed component-based architecture. This is the case for the requirement to evaluate expression arguments (which is not trivial) that is common to all algebraic languages and shared by the main engines (given precisely the algebraic nature of those languages). This requirement is naturally solved/shared by the introduction of broker engines to be used both by the the main rule engines and any algebraic engines. But actually invoking a broker engine and other (not so minor) details (like dealing with variables and unification, and joining substitution sets) are hardly solved by distributed architectures.
Eventually, all fully compliant r3 expression engines will share an enormous amount of code that is already part of an r3 main engine. In order to minimize this issue and ease, as much as possible, the process of implementing and testing new expression engines, r3 facilitates a Java library that abstracts away matters like communication (HTTP, SOAP) protocols, binding variables and generating alternative solutions, or even the r3 ontology itself and dealing with the Jena RDF models.
The final goal of the r3 library is to allow developers of expression engines to focus on the specificities of the languages to implement; freeing them from r3 details which are to be abstracted by tailored evaluation context components (under development at the time of this writing, e.g. Context), ContextArg) and ArgEval). Still, a note of caution, r3 is subject to ongoing work. We will do our best to support anyone using r3, and we will consider carefully any change that may have an impact on work based on r3. Don't forget to drop us a line if you are using r3 or the r3 library!
For your convenience, and in order to provide some guidance for new comers, below, we have tried to anticipate the answers to a few questions. Additionally, an example of an r3 engine that implements some dummy language, based on the r3 development library, is provided as a WAR file (r3xra.war). If you use Eclipse as your Java IDE you may easily create a Web Dynamic Project by simply importing this WAR file (viz. File/Import...) into your own Eclipse workspace. The main r3 WAR file (r3.war), that you can import just as easily into Eclipse, has also been packaged with full source code and includes additional examples of implementation of r3 component/expression languages.
You will find a complete list of the languages currently packaged together with r3 in our languages page. The implementation of those languages is included in r3.jar (or as a separate library -r3eval.jar- requiring r3lib.jar), with the exception of the evolp and protune languages which are available as separate Web applications (as explained below). Some of these languages/libraries require additional supporting packages (all included in the r3.war package). All these libraries and packages are available in the installation page.
A Bioinformatics domain broker (B-Domain) is under development by Tiago Franco as part of his Master Thesis. This work includes both a domain language (engine prototype) and an application language (engine prototype). A SPARQL language (engine prototype) is also under consideration. It is available as a separate Web application, you will find a snapshot of the current version here and additional examples in the languages and prototype pages.
The EVOLP language was implemented by Martin Slota as part of his Master Thesis and is currently being expanded with variable support and exposed as an r3 language (engine prototype). It is available as a separate Web application, you will find a snapshot of the current version here and additional examples in the languages and prototype pages.
The Protune language developed by Rewerse WGI2 is also exposed as an r3 language (engine prototype). It is available as a separate Web application, you will find a snapshot of the current version here and additional examples in the languages and prototype pages.
An evaluator engine for (a demonstrative subset of) the SNOOP event algebra also exists. This language was implemented by Gastón Tagni using a previous r3 version (v0.02). Details about this language, and its implementation, are provided in Implementation of a Complex Event Engine for the Web (©IEEE, EDA-PS 2006, PDF) and in An Approach to Complex Event Detection in the Web (MSc Thesis 2007, PDF).
Finally, it is worth mentioning that besides the component languages listed here, the MARS prototype also exposes several component languages.
If a parameter MUST have a value, for a construct to be understood/evaluated, then it is an input parameter (either functional or opaque), otherwise it is a logical parameter (or loosely speaking an output parameter). In the latter case if it is bound to a value, upon input, it restricts the solutions returned.
Functional parameters MUST have atomic/literal values which MUST NOT contain any variable references. Like any input parameter, if they have a default value they may be ommited.
Most of the times, opaque parameters may be recognized by the fact that their values may contain ad-hoc references to variables; some times they do not allow such explicit references, but are instead extended with all the implicitly known variables; other times ad-hoc variables are not involved, but they use some sub-language that partially specifies the actual semantics of the construct (e.g. a query string where logical operators may be used).
First you need to define your language, using this definition you may then proceed to create a web application using the r3 library. To achieve this you may use Eclipse and create a Web Dynamic Project by importing the WAR file r3xra.war (which contains an example of a dummy component engine) and modify it to suit your needs. This example war file contains not only all the libraries required, but also a suggestion of a package structure that you may want to follow.
The main entry point for your r3 web application is an r3 Servlet (e.g. mypkg.r3.Servlet) mapped to an URL of your choice (e.g. http://myserver.nop/r3/*) in your server configuration:
...
<servlet>
<servlet-name>r3Servlet</servlet-name>
<display-name>r3 Servlet</display-name>
<servlet-class>mypkg.r3.Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>r3Servlet</servlet-name>
<url-pattern>/r3/*</url-pattern>
</servlet-mapping>
...In your r3 Servlet you must register all the services that expose your own component engines:
package mypkg.r3;
public class Servlet extends net.rewerse.i5.r3.Servlet {
protected void initServices() {
// register r3 default services if present
super.initServices();
// expose some engine at http://myserver.nop/r3/some-endpoint
registerService("mypkg.r3.mylang.Server", "some-endpoint");
// expose some other engine at http://myserver.nop/r3/other-endpoint
registerService("mypkg.r3.myotherlang.Server", "other-endpoint");
}
}To register a service you neeed to provide the full name of its r3 Server class and the relative endpoint where you want to expose it. Your Server class is a simple class that is responsible for creating the one and only instance of your component/expression Evaluator class that actually implements your component language:
package mypkg.r3.mylang;
import net.rewerse.i5.r3.eval.ExprEvaluator;
public class Server extends net.rewerse.i5.r3.eval.Server {
protected ExprEvaluator createEvaluator(String url) throws Exception {
// 'url' is the actual URL where your service is available
return new mypkg.r3.mylang.Evaluator(url);
}
}All that is missing now is the implementation of the singleton r3 Evaluator of your own language where you should include a method to retrieve your language definition and another method to actually evaluate expressions written using your language. We strongly suggest that you keep your Server, Evaluator and a copy of your ontology (e.g. mylang.owl) in the same Java package (e.g. mypkg.r3.mylang):
package mypkg.r3.mylang;
import net.rewerse.i5.r3.juice.ontology.Language;
// ...
public class Evaluator extends net.rewerse.i5.r3.eval.ExprEvaluator {
public static final LANG =
r3lang(Evaluator.class,
"mylang.owl", // the name of the file containing a copy of your ontology
r3url("http://dummy.nop/mylang.owl"), // the xml:base (and URI) of your ontology
"mylang"); // the rdf:ID of your language
protected Language getLanguage() {
return LANG;
}
protected void evaluate(Context ctx) throws Exception {
String opn = ctx.opname();
if (opn.equals("insert")) {
// ...
} else if (opn.equals("delete")) {
// ...
} else if (opn.equals("get")) {
// ...
} else if (opn.equals("search")) {
// ...
} else if (opn.equals("sequence")) {
// ...
} else {
// only valid constructs of your language are returned
// all expressions are validated against your language
// definition prior to calling this method
}
}
}
You simply install your web application at an appropriate server (like Tomcat) and you're good to go. With your web application in-place you may retrieve the RDF model containing all the registered component engines (using your servlet mapping, e.g. http://myserver.nop/r3/), or the RDF model for a single engine (using, e.g. http://myserver.nop/r3/some-endpoint), or evaluate any expression by posting the adequate evaluation request.
To test your engine as a Java application you need to make your Servlet an r3 Tester and include in it a main method:
package mypkg.r3;
public class Servlet extends net.rewerse.i5.r3.test.Tester {
public static void main(String[] args) throws Exception {
String templates = parseArgs(args);
if (templates == null) templates = "tests.xml";
testTemplates(templates, new Servlet());
}
protected void initServices() {
// ...
}
}Additionally you have to include in the same Java package (e.g. mypkg.r3) your template files (e.g. tests.xml). The application will emulate the submission of all the requests contained in the selected file and output all the results for each of them. You will find examples of template files here or here. Additional Tester parameters may be set using the 'tester-*' attributes of the document element of the template files.
An asynchronous expression to be evaluated requires the client submitting it to supply a notifyTo endpoint. Asynchronous responses will be simply posted to this endpoint. If you do not have an endpoint to supply for this purpose you may use an r3 dumpster endpoint. To make such an endpoint available in your Servlet you just have to register the default r3 services as shown here, and you will get one for free (e.g. http://myserver.nop/r3/dumpster).
The 'evaluate' method of an Evaluator has a single parameter, an evaluation Context, that provides all the information about the current expression under evaluation and its input substitutions. Additionally this context allows you to build the results to return (i.e. literal returned values, each with a set of associated output substitutions). From this evaluation Context you may retrieve the main construct of the current expression:
protected void evaluate(Context ctx) throws Exception {
String opn = ctx.opname();
// ...
}
Given a request to evaluate an expression, the 'evaluate' method of an Evaluator may be called more than once. It is invoked once for every distinct combination of the input parameters (functional or opaque) of the top construct of the expression.
Like the main construct, the value of any parameter is retrieved from the current evaluation Context (ctx):
if (ctx.opname().equals("index-of")) {
String pattern = ctx.text("pattern"), text = ctx.text("text");
// ...
}The 'text' method is appropriate if the value of the parameter contains only text-like DOM nodes since it concatenates all the text-like (e.g. CDATA) nodes and further replaces all occurrences of XML escaped characters (e.g. '<' is replaced by '<'). The 'literal' method returns the untouched XML serialization of the XML fragment that constitutes the value of the parameter:
if (ctx.opname().equals("xpath-select")) {
String path = ctx.text("path"), fragment = ctx.literal("fragment");
// ...
}Both 'text' and 'literal' return 'null' if a parameter has no value (which, by definition, is impossible for input parameters).
An evaluation is considered to be successful only if your 'evaluate' method "returns" at least one result:
if (ctx.opname().equals("square")) {
Float x = Float.valueOf(ctx.text("number"));
ctx.addResult(""+(x*x));
}The value provided to 'addResult' is expected to be an XML serialization of an XML fragment. Which means that the following would be a safer version (although uneeded, in this case) of the previous code:
if (ctx.opname().equals("square")) {
Float x = Float.valueOf(ctx.text("number"));
ctx.addResult(net.rewerse.i5.r3.R3Utils.asString(
xmlBuilder().newDocument().createTextNode(""+(x*x))
));
}If the value provided to 'addResult' is 'null' it means that although the evaluation succeeds (in the logical sense) it returns no functional value. Notice that you may return as many alternate values as you like by invoking 'addResult' several times.
Unlike an input parameter, a logical parameter may have several alternate values for a single invocation of the 'evaluate' method. If the logical parameter is bound to a logical variable its values (if there are any) will depend on the input substitutions matching the current set of values of the input parameters. As such, the value of a logical parameter, in general, is known only for a particular input substitution. To iterate the input substitutions (or tuples) Context provides the 'getUsing' method:
import net.rewerse.i5.r3.juice.ontology.Tuple;
// ...
if (ctx.opname().equals("count-bound")) {
int cnt = 0;
for (Tuple t: ctx.getUsing()) {
ctx.startResult(t); // start a result with some substitution
if (ctx.literal("logical-value") != null) cnt++;
ctx.cancelResult(); // rollback the started result
}
// instead of: ctx.addResult(""+cnt);
ctx.startResult((Tuple)null); // start a result with an empty substitution
ctx.finishResult(""+cnt); // commit by providing the functional result
}
Most of the times, instead of accessing the specific value bound to a logical parameter, it is more important to know if a logical parameter can be bound (i.e. unifies) to a specific value. For the latter purpose an evaluation Context provides an 'output' method:
import net.rewerse.i5.r3.juice.ontology.Tuple;
// ...
if (ctx.opname().equals("get-student")) {
String id = ctx.text("student-id"); // a functional parameter
String name = students.get(id);
if (name == null) {
// no results, logical failure
} else {
name = net.rewerse.i5.r3.R3Utils.asString(
xmlBuilder().newDocument().createTextNode(name));
for (Tuple t: ctx.getUsing()) {
ctx.startResult(t); // start a result with specific input substitution
if (ctx.output("student-name", name)) // bind/unify logical parameter
ctx.finishResult(null); // logical success, no functional result
else
ctx.cancelResult(); // abort started result
}
}
}Notice that the 'output' value is an XML serialization of an XML fragment (pretty much like for 'addResult' and 'finishResult').
When implementing opaque constructs (having one or more opaque parameters) you will have to deal with logical variables explicitly. For this purpose, several methods are provided by evaluation Context objects:
An asynchronous evaluation, in general, is one for which you do not know the complete set of results upon evaluation request. You may know/return several results immediately, but then you will need to suspend the evaluation until further results become available.
To suspend an asynchronous evaluation, in your 'evaluate' method you have to declare the current evaluation as 'incomplete' instead of adding the specific results:
... String iid = ctx.incomplete(); // launch asynch evaluation associated with the returned incomplete ID ...
Later, when some results become available for a particular incomplete ID, you will have to retrieve the evaluation Context (ctx) associated with that ID (iid), build the results to return (using, e.g., 'addResult' and 'output') and signal the evaluation Context to return the new results (that may be the last ones or not):
... Context ctx = incompleteEvaluation(iid); ... // use ctx to add all the available results boolean islast = false; // Are this the last results? ctx.notifyResults(islast); // Notify issuer of current results ...
If your incomplete evaluations are not stateless you further have to extend the cleanup method in order to account for externally forced termination of those evaluations:
...
protected void freeId(Context ctx, String iid) throws Exception {
// ... terminate evaluation associated with 'iid' (if possible)
}
...
The support for implementing algebraic operators, including the recursive evaluation of operator arguments, is still partial at the time of this writing. Nevertheless some methods are already available for this purpose (e.g. 'launch'):
import net.rewerse.i5.r3.juice.ontology.Result;
// ...
protected void evaluate(Context ctx) throws Exception {
// ...
if (ctx.opname().equals("sequence")) {
ctx.launch("first", new EvalNext());
}
// ...
}
private class EvalNext extends ArgEval.Nop {
public void evalOne(ContextArg ctx, Result r) throws Exception {
if (ctx.argname().equals("first")) {
ctx.launch("next", r.getUsing(), null, this);
}
}
public void evalSome(ContextArg ctx, Iterable<Result> lr) throws Exception {
if (ctx.argname().equals("next")) ctx.addResult(lr);
}
}
Currently you can only define native rule languages. Native rule languages have only opaque (i.e. native) rule constructs. To implement an evaluator that is capable of dealing with native rules (besides expressions), instead of extending ExprEvaluator, you have to extend NativeEvaluator and provide your own implementation of the abstract methods 'createNative' and 'freeId':
package mypkg.r3.mylang;
// ...
public class Evaluator extends net.rewerse.i5.r3.eval.NativeEvaluator {
// ...
protected void createNative(Context ctx, String id) throws Exception {
String opn = ctx.opname();
if (opn.equals("native")) {
String rule = ctx.literal("source");
// ... load 'rule' associated with 'id'
}
}
protected void freeId(Context ctx, String id) throws Exception {
// ... unload rule associated with 'id' (if possible)
}
}
Last modified: |