Chapter 18. Embedding Jess in Java applications – Jess in Action: Rule-Based Systems in Java

Chapter 18. Embedding Jess in Java applications

In this chapter you’ll...

  • Learn about the jess.Rete class
  • Create and manipulate facts from Java
  • Work with Jess exceptions
  • Learn to reroute Jess’s I/O channels

Every application you’ve written so far has been based on the command-line interface tool jess.Main. The Tax Forms Advisor and PC Repair Assistant each consisted entirely of a single Jess script, whereas the HVAC Controller also included some Java classes. Each of these three programs could be launched by starting jess.Main and telling it the filename of a script from the command line.

It might look as though jess.Main is Jess’s central class. In fact, jess.Main is just a command-line wrapper around the Jess library. With the library, you can create any number of individual Jess inference engines. You can define rules for them, add data to their working memories, run them in separate threads, and collect generated results—all from Java code, without using jess.Main.

The Jess library is obviously what you need to use to deploy rule-based web applications. Jess must be embedded inside some larger application: a web server, an application server, or even a browser. In this chapter, you’ll learn general techniques for embedding Jess in Java software. In the next chapter, you’ll put these techniques into practice and create a servlet-based web application around the Recommendations Agent rules you developed in chapter 17.

18.1. Getting started with the Jess library

Let’s begin at the beginning. The core of the Jess library is the jess.Rete class (see section 13.4.1). An instance of jess.Rete is, in a sense, an instance of Jess. Every jess.Rete object has its own independent working memory, its own list of rules, and its own set of functions. The Rete class exports methods for adding, finding, and removing facts, rules, functions, and other constructs. The Rete class is a facade[1] for the Jess library: Although there are many other classes in the library, jess.Rete provides a convenient central access point.

1 E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Reading, MA: Addison-Wesley, 1995).

It’s easy to create a jess.Rete object. There is a default constructor (one that accepts no arguments):

import jess.*;
    ...
    Rete engine = new Rete();

The most commonly used constructor, however, accepts a java.lang.Object argument. The Rete object stores this Object, and whenever it needs to load a Java class (for instance, because of a call to Jess’s new function), that Object’s class loader is used to find it. In some Java applications—especially in application server environments—there can be multiple class loaders, and using the correct one is important. By supplying an object from your application as an argument to the Rete constructor, you tell Jess how to find the other classes in your application. This will be important when you build a web application in the next chapter.

18.1.1. The executeCommand method

Once your Java program creates a Rete object, what’s next? One of the simplest and most powerful ways to manipulate Jess from Java is to use the executeCommand method. executeCommand accepts a String argument and returns a jess.Value (see section 4.1.3 and chapter 15 to learn about this class). The String is interpreted as an expression in the Jess language, and the return value is the result of evaluating the expression. So, for example, to add a fact to Jess’s working memory and get access to the jess.Fact object, you might do the following:

import jess.*;
...
    Rete engine = new Rete();
    Value v = engine.executeCommand("(assert (color red))");
    Fact f = v.factValue(engine.getGlobalContext());

Section 15.2.2 describes value resolution—how to get information out of a jess.Value object. To resolve a jess.Value, you need a jess.Context object. The method getGlobalContext in jess.Rete is a convenient way to obtain one. Commands executed via executeCommand may refer to Jess variables; they are interpreted in this same global context.

Using executeCommand to interact with Jess has one other advantage: It is thread-safe. Only a single call to executeCommand can be simultaneously executing on a given instance of jess.Rete. The lock object is internal to the Rete instance, so executeCommand is synchronized independently of any other concurrency controls. This means that if your web application uses executeCommand to call the get-new-order-number function (see section 17.7.1), it needn’t worry about interference between multiple threads.

18.1.2. Exchanging Java objects

Probably the most frequently asked question on the Jess email discussion list is some variation on, “How can I send my Java variable to my Jess program?” This section provides one easy answer to this important question. We’ll discuss the following functions:

  • Rete.store(String, Object) Stores any object in a special hash table, from Java
  • Rete.store(String, Value) Stores a jess.Value in the table, from Java
  • Rete.fetch(String) Retrieves a jess.Value, from Java
  • (store symbol value) Stores a value in a hash table, from Jess
  • (fetch symbol) Retrieves a value from the hash table, from Jess

In the previous section, you asserted a fact (color red) into Jess’s working memory from Java. What if instead of the symbol red, you wanted the fact to hold a java.awt.Color object representing a particular shade of red? It’s not possible to construct a String argument for executeCommand that directly contains the Color object. This general problem—how to move objects in both directions between Jess and Java—is solved by the store and fetch functions. To send a Java object to Jess:

  1. Choose a unique identifier.
  2. In Java, call Rete.store, passing the identifier and the object as arguments.
  3. In Jess, call (fetch), using the same identifier to retrieve the object.

The color example would look like this:

import jess.*;
import java.awt.Color;
...
    Rete engine = new Rete();
    Color pink = new Color(255, 200, 200);
    engine.store("PINK", pink);
    Value v =
        engine.executeCommand("(assert (color (fetch PINK)))");
    Fact f = v.factValue(engine.getGlobalContext());

The store method saves the Color object in a hash table, using the string "PINK" as the key. The fetch function retrieves the object from the same hash table. Note that the store method is overloaded to accept either a jess.Value or a generic java.lang.Object as its second argument. The second version (the one used here) automatically wraps the Object in a jess.Value of type RU.EXTERNAL_ADDRESS before storing it.

Getting results

If sending Java variables to Jess is the topic of the most frequently asked Jess question, the second most popular question surely concerns retrieving values from Jess back into Java. Store/fetch works in the other direction, too. It’s a handy way of returning results from Jess to Java. Here, a value computed on the right-hand side of a rule is retrieved from Java:

import jess.*;
...
    Rete engine = new Rete();
    engine.executeCommand("(assert (numbers 2 2))");
    String rule = "(defrule add-numbers" +
        "(numbers ?n1 ?n2)" +
        "=>" +
        "(store SUM (+ ?n1 ?n2)))";
    // Use executeCommand to define a rule
    engine.executeCommand(rule);
    // The rule fires and stores the result
    engine.executeCommand("(run)");
    Value sumValue = engine.fetch("SUM");
    int sum = sumValue.intValue(engine.getGlobalContext());

The Java variable sum is initialized to 4. This is a long-winded way of adding two numbers, of course, but the same technique works with more complex rules.

18.1.3. Beyond executeCommand

As you’ve seen, you can use the executeCommand method to do just about anything in Jess from Java. This approach has some problems, though:

  • It’s verbose. Beside typing the whole Jess command, you have to type executeCommand, and possibly some store and fetch calls.
  • It’s potentially inefficient, because Jess has to parse the argument. If the script passed to executeCommand is something that will take a while to run, this issue doesn’t matter; but for a single short command, it could be a lot of overhead.
  • It’s error-prone. If the Jess script contains a syntax error, the Java compiler won’t catch it—you won’t find the error until you run your program.

The executeCommand method is very convenient, then, but it has some important disadvantages. Whenever possible, you should consider using Jess’s direct Java APIs instead. Table 18.1 lists some Jess functions and the equivalent Rete methods.

Table 18.1. Some simple methods in the jess.Rete class

Rete method

Jess equivalent

reset() (reset)
run() (run)
run(int) (run number)
clear() (clear)

For example, in a code listing in the previous section, you had

engine.executeCommand("(run)");

Instead, you can write this code as follows:

engine.run();

The Rete class also includes direct Java equivalents of (clear), (reset), and many other important Jess functions. In the following sections, we’ll look at what you can do with some more of these Rete methods.

18.2. Working with Fact objects in Java

Rather than using executeCommand, store, and fetch to assemble and assert facts from Java, you can construct jess.Fact objects directly. If a single fact is going to hold objects in several slots, this method will be more convenient, and it will always be more efficient. Table 18.2 lists some of the Java methods that will help you work with Fact objects.

Table 18.2. Some jess.Rete methods for working with facts

Rete method

Jess equivalent

addDeftemplate(Deftemplate) (deftemplate)
assertFact(Fact) (assert fact)
findDeftemplate(String) None
findFactByFact(Fact) None
findFactById(int) (fact-id number)
retract(Fact) (retract fact-id)

The jess.Fact class is a subclass of ValueVector (you met ValueVector in section 15.2). All the entries in the ValueVector correspond to slots of the Fact; the data for the first slot is the item at index 0. The head or name of the fact is stored in a separate variable and is accessible via the getName method.

Every jess.Fact has an associated jess.Deftemplate object that describes the slots the fact can have. All Facts with a given head should share the same Deftemplate. When you’re programming in Java, it’s up to you to enforce this requirement. If the Deftemplate you need has already been defined, you can use the findDeftemplate method in Rete to get a reference to it; otherwise, you’ll have to construct it yourself. Once you’ve built a Deftemplate, you can reuse it for all the other Facts of the same type that you create.

Listing 18.1 shows how to create a Deftemplate with two slots and then assert several facts that use it. Deftemplate’s first two constructor arguments are the name of the template and the documentation string for the template. The arguments to addSlot are a name for the slot, a default value for the slot, and the name of the data type of the slot, respectively. Jess doesn’t do anything with the data type argument—it’s reserved for future use.

Listing 18.1. Creating a Deftemplate and asserting Fact objects from Java
import jess.*;
public class CreateFacts {
    public static void main(String[] unused)
    throws JessException {
        Rete engine = new Rete();
        Deftemplate d =
            new Deftemplate("person", "A person", engine);
        d.addSlot("name", Funcall.NIL, "STRING");
        d.addSlot("address", Funcall.NIL, "STRING");
        engine.addDeftemplate(d);

        String[][] data = {
            {"Joe Smith", "123 Main Street"},
            {"Fred Jones", "333 Elm Circle"},
            {"Bob Weasley", "211 Planet Way"},
        };

        for (int i=0; i<data.length; ++i) {
            Fact f = new Fact("person", engine);
            f.setSlotValue("name",
                           new Value(data[i][0], RU.STRING));
            f.setSlotValue("address",
                           new Value(data[i][1], RU.STRING));
            engine.assertFact(f);
        }
        engine.executeCommand("(facts)");
    }
}

In listing 18.1, you specify a value for every slot in each Fact, but doing so is not required. If you don’t specify a value for a slot, the default value is used when the Fact is asserted.

Once you assert a Fact object, you no longer “own” it—it becomes part of the Rete object’s internal data structures. As such, you must not change the values of any of the Fact’s slots. If you retract the fact, the Fact object is released and you are free to alter it as you wish. You can retract your fact from the engine using the retract method.

18.2.1. Multislots

Jess facts can contain multislots—single slots that hold multiple data items. You can add a multislot to a Deftemplate using the addMultiSlot method. You can then set the value of that slot in a fact as usual, but the slot value must be a jess.Value of type RU.LIST. A LIST value contains a ValueVector; the elements of the ValueVector are then the contents of the multislot:

Rete engine = new Rete();
Deftemplate d =
    new Deftemplate("student", "A student", engine);
d.addSlot("name", Funcall.NIL, "STRING");
d.addMultiSlot("courses", Funcall.NILLIST);
engine.addDeftemplate(d);

Fact f = new Fact("student", engine);
f.setSlotValue("name", new Value("Fred Smith", RU.STRING));
ValueVector courses = new ValueVector();
courses.add(new Value("COMP 101", RU.STRING));
courses.add(new Value("HISTORY 202", RU.STRING));
f.setSlotValue("courses", new Value(courses, RU.LIST));

Note that even if a multislot contains only one value, you have to put it in a ValueVector. When you’re programming in Java, it’s up to you to follow this rule. The results of breaking it are undefined.

18.2.2. Ordered facts

As you saw in section 6.4, ordered facts like

(shopping-list bread milk jam)
(point 20 10)
(power off)

are represented as if they were unordered facts with a single multislot named __data, like this:

(shopping-list (__data bread milk jam))
(point (__data 20 10))
(power (__data off))

If you assert an ordered fact from the Jess language, the deftemplate is created automatically if it doesn’t already exist. The same thing happens if you create the fact from Java. The deftemplate is created during the call to the Fact object’s constructor:

Fact f = new Fact("shopping-list", engine);
f.setSlotValue("__data", new Value(new ValueVector().
    add(new Value("bread", RU.ATOM)).
    add(new Value("milk", RU.ATOM)).
    add(new Value("jam", RU.ATOM)), RU.LIST));
engine.assertFact(f);

This code uses an interesting shortcut: The add method in ValueVector returns the ValueVector you call it on. This means you can chain calls to this method, as we’ve done here. Some people like this technique, and some people hate it; I think it’s a fine thing to do when you’re building up a list of items.

18.2.3. Removing facts

The retract method in the Rete class lets you remove facts from working memory:

Fact f = ...
engine.retract(f);

retract takes a Fact object as an argument. If you asserted the fact from Java, then it’s easy to use it as an argument to retract. If you didn’t, then you need to get hold of a Fact object somehow. There are two approaches: Either you can use the fact number of the Fact as an argument to the findFactById method in Rete, or you can construct a Fact just like the one you want to retract and use that as the argument to retract (it turns out that retract doesn’t need you to pass in a Fact object that’s really in working memory—it can instead be a Fact that’s identical to one in working memory).

The working memory is stored as a hash table, with the Fact objects as the keys. That means finding out whether a Fact is in working memory already is a fast operation, but finding the Fact with a particular numeric identifier is slow (because Jess has to examine each Fact in working memory until it finds the right one). Therefore, there’s no cheap way to remove a fact from working memory if you don’t already have a reference to the Fact object: Either Jess has to do a slow lookup, or you have to do the work of constructing a duplicate Fact object. If you assert Facts from Java and intend to retract them later in your application, be sure to keep references to the Fact objects you create.

18.3. Working with JavaBeans

Adding JavaBeans to Jess’s working memory from Java is simple, and it works pretty much the same way as it does from the Jess language. Table 18.3 shows the correspondence between methods in the Rete class and the Jess functions you’ve already learned about.

Table 18.3. jess.Rete methods for working with JavaBeans

Rete method

Jess equivalent

defclass(tag, class-name, parent) (defclass tag class-name [parent])
definstance(tag, object, boolean) (definstance tag object[static|dynamic])
undefinstance(object) (undefinstance object)

You use the Rete.defclass method to register a class with Jess, and then you use Rete.definstance to add individual instances to working memory. You can remove the objects using Rete.undefinstance. As an example, you can add a java.awt.Button to Jess’s working memory like this:

Rete engine =
engine.defclass("button", "java.awt.Button", null);
Button b = new Button("OK");
engine.definstance("button", b, true);

You don’t want this defclass to extend another one, so you pass null as the last argument to the defclass function. Because java.awt.Button sends Property-ChangeEvents, you can pass true as the last argument to definstance, making this a dynamic instance.

18.4. Calling Jess functions from Java

As you’ve seen, the Rete class exposes many Jess functions as public Java methods—many, but not all. Some Jess functions show up as Java methods in other classes: For example, the Jess gensym* function is available as the static gensym function in the jess.RU class. The Userfunction class that implements gensym* is just a thin wrapper around this static Java method.

Other Jess functions, however, are implemented directly in the Userfunction class. To call them from Java, you have to use an instance of the appropriate Userfunction class. The helper class jess.Funcall can make this process a little simpler. As an example, there’s no specific Java API for the watch function. To turn on all the watch diagnostics from Java code (the equivalent of (watch all) in Jess), you can do this:

Rete engine = ...
Context context = engine.getGlobalContext();
Funcall f = new Funcall("watch", engine);
f.arg("all");
f.execute(context);

Once you’ve created a Funcall object, you can save it and call execute on it any number of times. Alternatively, you can use the compressed form:

new Funcall("watch", engine).arg("all").execute(context);

Some Jess functions can fail, due to either programmer error or a runtime condition. In the next section, you’ll learn how to deal with errors during execution.

18.5. Working with JessException

Jess’s Java API reports errors by throwing instances of jess.JessException. Therefore, whenever you work with Jess in Java, you need to catch this exception. Working with JessExceptions can be a little tricky, as exception classes go, but using them correctly can save you a lot of head scratching when you’re debugging your programs.

Here are some useful methods in jess.JessException:

  • getContext() Returns a Jess stack trace
  • getLineNumber() Returns the offending line number in Jess language code
  • getNextException() Returns a nested exception
  • getProgramText() Returns the offending Jess code itself
  • getRoutine() Returns the name of the Jess function or Java method

An instance of JessException can of course provide an error message and a Java stack trace, like all Java Throwables. If the error comes from executing code in the Jess language, the JessException can provide additional information about what went wrong. The getContext method, for example, returns a kind of Jess language stack trace, showing what functions and constructs were executing when an error occurred. The getRoutine method tells you what Jess function was executing, and the getLineNumber method points to a specific line in your script. getProgramText returns the actual snippet of Jess code that caused the error. The code in this example provokes a JessException by trying to multiply two symbols, and then demonstrates how to use some of these methods:

import jess.*;

public class CatchJessException {
    public static void main(String[] argv) {
        try {
            Rete r = new Rete();
            r.executeCommand("(* 1 2)\n(* 3 4)\n(* a b)");
        } catch (JessException je) {
            System.out.print("An error occurred at line " +
                             je.getLineNumber());
            System.out.println(" which looks like " +
                               je.getProgramText ());
            System.out.println("Message: " + je.getMessage());
        }
    }
}

Running this program produces the following:

An error occurred at line 3 which looks like ( * a b )
Message: Not a number: "a"

The most important thing to remember about working with JessExceptions is never to ignore them. A truly remarkable number of people write code like this:

try {
    Rete r = new Rete();
    r.executeCommand("(* 1 2)\n(* 3 4)\n(* a b)");
} catch (JessException je) {
    /* NOTHING */
}

and then wonder why their program doesn’t work. Don’t be one of them! Always at least print the exception object itself to System.out or to a log file. JessException’s toString method produces the by-now-familiar exception display you see from the Jess command prompt. Printing the object je would give the following:

Jess reported an error in routine Value.numericValue
    while executing (+ a b).
    Message: Not a number: "a" (type = ATOM).
    Program text: ( + a b ) at line 3.

This is more information than the end user of your application probably needs to see, but it’s better than no information at all.

18.5.1. Nested exceptions

The Jess function call can invoke any Java method. If the invoked Java method throws an exception, call creates a JessException object as a wrapper for the real exception and throws the wrapper instead. Therefore, when you catch a Jess-Exception, you may need to check it for a nested exception object using the get-Cause method. If getCause returns non-null, the returned exception is almost always more interesting than the JessException itself:

import jess.*;

public class CheckNextException {
    public static void main(String[] argv) {
        try {
            Rete r = new Rete();
            r.executeCommand("(new java.net.URL foo://bar)");
        } catch (JessException je) {
            System.out.println(je.getMessage());
            System.out.println(je.getCause().getMessage());
        }
    }
}

This code prints the following:

Constructor threw an exception
unknown protocol: foo

Note that the toString method of JessException does not display nested exceptions, so you need to check for and report them explicitly.

18.5.2. Rolling your own

The call method of the Userfunction interface can throw JessException, so if you’re writing your own Jess commands, you may want to create your own Jess-Exception objects to report errors. The good news is that Jess automatically takes care of setting the line number, program text, and Jess stack trace information after your JessException is thrown; all you need to worry about are the routine name, error message, and possibly a nested exception.

JessException has three constructors, which differ only in their last argument. The first two arguments are always a routine name and a message. The routine name indicates where the exception was created. It can be either the name of a Java class and method, or the name of a Jess language function; use your best judgment. The routine name you use is displayed on the first line of the message returned by JessException.toString().

The second argument to each JessException constructor is the error message—a short description of what went wrong, perhaps five words at most. For two of the three constructors, the third argument is simply more data to be appended to the message, with a space in between. One of the constructors accepts an int, which is handy for reporting number-related errors, and the other accepts a second String. For the third constructor, the last argument is a nested exception.

This code snippet is from an imaginary Userfunction that implements a google function in Jess. It tries to connect to the Google web site and reports any failure via a JessException:

try {
    Socket s = new Socket("www.google.com", 80);
    // use the socket ...
} catch (Exception ex) {
    throw new JessException("google", "Network error", ex);

}

Any exceptions thrown by the Socket constructor result in a JessException being thrown; the original exception is available via getNextException.

18.6. Input and output

The function printout was the first Jess function you learned. Until now, the first argument to printout has always been t, but I’ve never explained why. The time has come to break this silence and explain the topic of I/O routers. The following methods in the Rete class deal with these beasts:

  • addInputRouter(String name, Reader r, boolean mode) Defines or redefines an input router
  • getInputRouter(String name) Returns the named input router
  • removeInputRouter(String name) Undefines an input router
  • getInputMode(String name) Determines whether the named input router is console-like
  • addOutputRouter(String name, Writer w) Defines or redefines an output router
  • getOutputRouter(String name) Returns the named output router
  • removeOutputRouter(String name) Undefines an output router
  • getOutStream() Returns the standard output router
  • getErrStream() Returns the standard error router

You can use the readline and printout functions to collect input and display output from Jess:

(printout t "Enter 'y' or 'n': ")
(bind ?response (readline t))

The first argument to printout or readline is a router, a symbol that tells Jess where to send the output. Several routers are built into Jess, and they’re all initially connected to standard input and output: text sent to t, WSTDOUT or WSTDERR all goes to System.out by default, and data read from t or WSTDIN comes from System.in. The W* routers are used internally by Jess. WSTDOUT is where Jess sends the Jess> prompt and the result of evaluating an expression you type at the prompt. WSTDERR is used for internal error messages. WSTDIN exists only for symmetry; Jess doesn’t use it for anything.

A router is really just a symbolic name for a java.io.Reader (for input) or a java.io.Writer (for output). The defaults for the built-in routers are wrappers around System.in and System.out. Each jess.Rete object keeps its own table of routers (there are separate tables for input and output routers, so an input/output pair with the same name, like t, are separate, unconnected objects).

You can retrieve routers from this table by name with the getInputRouter and getOutputRouter methods. If you’re writing a Userfunction or other Java code that wants to intersperse its output with Jess’s other output, you can use getOutputRouter("WSTDOUT") to get an appropriate Writer object. The method getOutStream() is a special shortcut for this common operation.

Note that Jess’s routers are not general I/O channels. Jess always interprets data from an input router as a sequence of Jess language tokens (symbols, numbers, and strings). As a result, you can’t do binary I/O through a router. If you need to do binary I/O, you can use Jess’s reflection capabilities to call read and write directly on appropriate Java streams.

18.6.1. Using custom routers

In the command-line client jess.Main, the default values for the built-in routers are fine; but if Jess is embedded in software with a GUI, it’s likely that printing to standard output is inappropriate. If you want to be able to use readline, printout, and friends from an application that won’t be used from the command line, you may need to set the standard routers to refer to Reader and Writer objects that are more appropriate for the situation. For example, if Jess was to be embedded in a web application, printout wouldn’t be useful unless a new router were defined that sent its output to someplace other than System.out. You might define a new router using a java.io.StringWriter; printout could then send information to a string, which could then be retrieved when appropriate by the application. To define such a router named out, you could do the following:

StringWriter sw = new StringWriter();
Engine.addOutputRouter("out", sw);

If you then executed the Jess statement (printout out 12345 crlf), a subsequent call to sw.toString() would return the string "12345\n".

Sometimes, coming up with appropriate Readers and Writers is tricky. In the jess.Console graphical console application, for example, the output from (printout t) goes to one graphical text component in the GUI, and (readline t) takes input from a second text component. To make this work, Jess includes two adapter classes named jess.awt.TextAreaWriter and jess.awt.TextReader. TextAreaWriter is a subclass of Writer that takes a TextArea as a constructor argument. The abstract write method is implemented to call append on the java.awt.TextArea, accumulating text as it arrives. The jess.Console application installs a single instance of this class as the routers t, WSTDOUT, and WSTDERR. As a result, all of Jess’s regular output goes to the TextArea (see figure 18.1).

Figure 18.1. Usage diagram for TextAreaWriter and TextReader

The TextReader class is similarly used to collect user input from the GUI. Text-Reader is a subclass of Reader with read methods that return data from an internal buffer. TextReader also has a method appendText that lets you add text to this internal buffer. jess.Console connects a java.awt.TextField in its GUI to an instance of TextReader using an event handler: Whenever the user presses Enter in the TextField, the event handler calls appendText on the TextReader, making the text entered in the TextField available to be read from the Text-Reader’s internal buffer. The connected TextReader object is installed as the t and WSTDIN input routers.

18.7. Summary

Jess is designed as a library that can be embedded into many different kinds of software. It has an extensive Java API to help you accomplish this functionality, and we have covered only some of it in this chapter.

The jess.Rete class plays a central role. Its methods let you define and retrieve constructs like rules and templates. You can use store and fetch to pass values between Jess and Java code. jess.Rete also manages a set of I/O routers that control how Jess prints output and reads input.

You can work with jess.Fact objects from Java whenever you need more control over the creation or manipulation of facts than you can get from the Jess language. Working with facts often involves manipulating the jess.Deftemplate objects that define the structure of the facts. You can also add JavaBeans to Jess’s working memory from Java code using the defclass and definstance methods of the Rete class.

Jess uses the jess.JessException class to report errors, and you can use it to report errors when you write Userfunctions. A JessException often carries a wealth of useful information about the error it represents.

Jess’s I/O functions read input from, and write input to, objects called routers. You can install your own routers, redirect the predefined routers, and even create your own router types.

In the next chapter, you’ll use what you’ve just learned to deploy the Recommendations Agent you developed in chapter 17 as a web application.