5 Using a RuleSession

This chapter describes how to use a RulesSession object.

The chapter includes the following sections:

5.1 RuleSession Constructor Properties

This section shows you the steps for creating a rule enabled application and describes using a RuleSession object. The package oracle.rules.rl contains the RuleSession object.

The RuleSession no argument constructor returns a RuleSession with the default locale and logging options set.

Table 5-1 describes the configuration parameters that can be set in a Map passed to the RuleSession constructor.

Table 5-1 Configuration Parameters for a RuleSession Constructor

Parameter Key Value

CFG_LOGGING

Boolean. True enables logging. False disables logging. The default is true.

CFG_LOCALE

The Locale instance for the desired locale. The default is the JVM default locale.

CFG_WATCH

The desired setting for the watch raw activity trace facility. The setting is restored when the session is reset.

WATCH_RULES: watch rules that fire.

WATCH_ACTIVATIONS: watch rule activations and deactivations.

WATCH_FACTS: watch fact operations assert, modify, retract.

WATCH_FOCUS: watch ruleset stack changes.

WATCH_COMPILATION: watch rule definintion.

WATCH_ALL: watch all of the above.

The default is that no trace settings are enabled.

CFG_DECISION_TRACE_LEVEL

Sets the decision trace level which controls the rule engine activity traced. This level is restored when the session is reset.

DECISION_TRACE_OFF: disables all decision tracing.

DECISION_TRACE_PRODUCTION: traces rules that fire.

DECISION_TRACE_DEVELOPMENT: detailed decision tracing. Equivalent to WATCH_ALL plus tracing of reset.

The default is DECISION_TRACE_OFF.

CFG_DECISION_TRACE_LIMIT

An integer that sets the limit on the number of trace entries that will be kept internally until the trace is retrieved.

The default decision trace limit is 10000.

5.2 RuleSession Methods

The outputWriter property determines where println, watch, and show output goes.

The rulesetName property sets the ruleset when RL statements are executed without an explicit named ruleset. The default rulesetName is main.

The executeRuleset methods parse and execute the given ruleset text (given as a String or a java.io.Reader).

The callFunction method invokes the named RL function (which must either be a built-in RL function or must have been previously defined with no parameters using one of the executeRuleset methods) and returns its result. Functions with a single argument can be invoked with the callFunctionWithArgument method. Functions taking any number of arguments can be called using the callFunctionWithArgumentList or callFunctionWithArgumentArray methods. The argument List or array must contain a Java Object for each RL function parameter.

5.3 RL to Java Type Conversion

The following table describes how Java Object types are be converted to RL types for passing arguments to RL functions.

Table Table 5-2 describes how Java Object types are be converted to RL types for passing arguments to RL functions, and conversely how RL types are converted to Java types for passing the RL function return value to Java.

Table 5-2 RL to Java Object Conversion

Java Class RL Type

java.lang.Integer

int

java.lang.Character

char

java.lang.Byte

byte

java.lang.Short

short

java.lang.Long

long

java.lang.Double

double

java.lang.Float

float

java.lang.Boolean

boolean

Object

Object

int[]

int[]

char[]

char[]

byte[]

byte[]

short[]

short[]

long[]

long[]

double[]

double[]

float[]

float[]

boolean[]

boolean[]

Object[]

Object[]

5.4 Error Handling

RuleSession method invocations that throw a ParseException or TypeCheckException do not affect the state of the RuleSession. A Java application, for example, an interactive command-line, can catch these exceptions and continue using the RuleSession.

RuleSession method invocations that throw a RLRuntimeException may have affected the state of the RuleSession and the RuleSession may not be in a usable state for the application to proceed. Robust applications should attempt to catch and recover from RLRuntimeExceptions in RL at a point near where the exception is thrown.

Other exceptions likely indicate a serious problem that the application cannot handle.

5.5 RL Class Reflection

You can use an RL class like a Java class in an RL program. The new, instanceof, and cast operators work on both kinds of class.

However, when an instance of an RL class is passed to a Java program, it is actually an instance of oracle.rules.rl.RLObject. A Java program can use the following classes: RLClass, RLProperty, and RLArray to examine the RLObject in a manner similar to using the java.lang.Class, java.lang.reflect.Field, and java.lang.Array classes to reflect a java.lang.Object. The package oracle.rules.rl contains RLCLass, RLProperty, and RLArray.

5.6 Obtaining Results from a Rule Enabled Program

When you create a a rule enabled program with Oracle Business Rules, a common question is, "How do I get the results of the evaluation?"

This section one approaches to extracting or exposing results of rule evaluation from the rule engine.

This section covers the following:

See Also:

For more information, see Working with Rules in Standalone (Non SOA/BPM) Scenarios in the Designing Business Rules with Oracle Business Process Management

5.6.1 Overview of Results Examples

The examples in this section show a highway incident notification system. These examples show the different approaches to access the results of rule engine evaluation. The examples use two Java classes: traffic.TrafficIncident and traffic.IncidentSubscription.

Note:

The traffic.* sample classes are not included in the Oracle Business Rules distribution.

The TrafficIncident class represents information about an incident affecting traffic and contains the following properties:

  • Which highway

  • Which direction

  • Type of incident

  • Time incident occurred

  • Estimated delay in minutes

The IncidentSubscription class describes a subscription to notifications for incidents on a particular highway and contains the following properties:

  • Subscriber - the name of the subscriber

  • The highway

  • The direction

In the example using these classes, when an incident occurs that affects traffic on a highway, a TrafficIncident object is asserted and rule evaluation determines to whom notifications are sent.

In the examples, the sess object is a RuleSession and a number of incident subscriptions are asserted. As a simplification, it is assumed that the TrafficIncident objects are short lived. They are effectively an event that gets asserted and only those subscribers registered at that time are notified.

The classes in these examples are all Java classes. However, it is possible to manipulate instances of RL classes in Java using the RL class reflection.

See Also:

For documentation see the Javadoc for the RLClass, RLObject, RLProperty and RLArray classes in the oracle.rules.rl package. Thus, RL objects, or instances of RL classes, can be used to hold rule engine results as well as Java objects.

5.6.2 Using External Resources to Obtain Results

This approach is similar to asserting a container for results, except that instead of a container, the object is a means to affecting resources external to the rules engine. For example, this could involve queuing up or scheduling work to be done, updating a database, sending a message. Any Java method accessible in the action may be invoked to effect the results. As with the container use case, the objects used in this example to access the external resources are not re-asserted since their content is not being reasoned on.

The following example shows the IncidentDispatcher object that is asserted and then used to dispatch the notification.

    rule incidentAlert
    {
        if (fact TrafficIncident ti &&
            fact IncidentSubscription s &&
                 s.highway == ti.highway &&
                 s.direction == ti.direction &&
            fact IncidentDispatcher dispatcher)
        {
            dispatcher.dispatch(s.subscriber, ti);
        }
    }

Example 5-1 shows Java code that asserts an IncidentDispatcher and a TrafficIncident, and then invokes the rule engine. This could also be accomplished using an object that is being reasoned on, but this would require a test in the rule condition to avoid an infinite loop of rule firing.

Example 5-1 Sample Showing Results with External Resources

        sess.callFunctionWithArgument("assert", new IncidentDispatcher());
 
        // An accident has happened
        TrafficIncident ti = new TrafficIncident();
        ti.setHighway("I5");
        ti.setDirection("south");
        ti.setIncident("accident");
        ti.setWhen(new GregorianCalendar(2005, 1, 25, 5, 4));
        ti.setDelay(45);
 
        sess.callFunctionWithArgument("assert", ti);
        sess.callFunction("run");

5.7 Debugging an RL Stacktrace

The runtime provides detailed debugging information in an RL stacktrace. When possible, if there is an error, the runtime provides extra context that helps identify the location of a problem. This extra context is useful when working with Rules SDK and Rules Designer.

The stacktrace includes the extra context showing the information for rule conditions, rule actions, functions, variables, and RL class definitions. The XPath style format consists of an RL construct and, if named, followed by the name enclosed in parentheses. If a number, n, appears in brackets after a construct it indicates the nth item following the previous construct. In combination with Rules SDK, RL generation should significantly assist in identifying a location for an error in Rules Designer.

For example, consider the ruleset shown in Example 5-2. When this ruleset executes, it gives the following report:

RLNullPointerException: object cannot be null
    at line 12 column 13 in stackTraceContext /Rule(porsche)/Pattern(car)/Test[1]
    at line 17 column 5 in stackTraceContext

Example 5-2 Test Ruleset

ruleset stackTraceContext
{
    class Car
    {
        String make;
        String model;
    }

    rule porsche
    {
        if (fact Car car &&
            car.make.startsWith("Porsche"))
        {
            println(car.make + " " + car.model);
        }
    }

    assert(new Car());
}

ruleset stackTraceContext
{
class Car
{
String make;
String model;
}
rule porsche
{
if (fact Car car &&
car.make.startsWith("Porsche"))
{
 
println(car.make + " " + car.model);
}
}
assert(new Car());

5.8 Using RuleSession Pooling

A typical application that uses rules evaluates the same rules multiple times, with different facts corresponding to separate requests. Initializing a RuleSession typically takes a few seconds depending on the number of rules involved.

In contrast, the time to execute the rules is typically much less. Therefore, better performance can be achieved by initializing a RuleSession one time and reusing it for each new request. Using RuleSession pooling, you can create a pool of RuleSession instances that supports improved performance and scalability of applications that use rules.

A RuleSession pool shares common definitions for rules, types, and functions across all the sessions in the pool which can significantly reduce memory consumption as compared to the same number of independent sessions. This sharing also greatly reduces the cost of creating a new session since it does not need to be created from the original rule language text.

5.8.1 How to Create a RuleSession Pool

In order for performance to scale up with increasing load, more than one RuleSession is required. A pool of RuleSession instances supports improved performance and scalability of applications that use rules. A pool is instantiated with a list of the RL code that is used to initialize each RuleSession created by the pool. The RL code is executed in the order in which it appears in the list. The number of RuleSession instances to create initially may be specified. In general, this should be a small value and usually the default should be sufficient.

If the rules in use by an application are updated, the application may need to load the new rules so that subsequent rule executions use the new rules. This is supported by the pool by invoking the refreshPool method passing it a list of the new RL. After the pool has been refreshed, RuleSessions returned by getPoolableRuleSession will have been initialized with the new RL code. When RuleSessions that were obtained before the refresh are returned using returnPoolableRuleSession, they are not placed back in the pool. The refreshed pool will only contain RuleSessions initialized with the new RL code.

Typically, the RL code is generated from a RuleDictionary created with the Rules SDK. Example 5-3 demonstrates creating and using a RuleSessionPool with RL code from a RuleDictionary.

Example 5-3 Creating a RuleSession Pool

     RuleDictionary rd;
     // Code to load rule dictionary not shown
     List rlList = new ArrayList();
     rlList.add(rd.dataModelRL());
     List rulesetAliases = rd.getRuleSetAliases(true);
     for (String alias : rulesetAliases)
     {
         rlList.add(rd.ruleSetRL(alias));
     }
 
     RuleSessionPool pool = new RuleSessionPool(rlList);

5.8.2 How to Use a RuleSession Pool

To execute rules using a RuleSession, you obtain a RuleSession from the pool and then return it after execution is complete. A poolable RuleSession is acquired by invoking the getPoolableRuleSession method. The pool creates new RuleSessions as required. An invocation of getPoolableRuleSession will not block waiting for a free RuleSession.

A RuleSessionPool can be created with a Map of RuleSession configuration parameters and all sessions in the pool will be configured as specified.

When rule execution has been completed, the poolable RuleSession is returned to the pool by invoking the returnPoolableRuleSession method. When a RuleSession is returned to the pool it is reset by the pool by invoking the built-in RL function, reset(). This removes all facts from working memory to prepare the RuleSession for the next execution. Every RuleSession that is retrieved from the pool should be returned to the pool. If an error has occurred during rule execution that results in the RuleSession being unfit for further use, the pool detects this and discards it.

Besides clearing working memory, the reset() function re-executes the initializers of all non-final global variables. The initializer of a non-final global variable can be used to perform other initialization at reset if this is required.

The following code example demonstrates using a RuleSession from the pool:

     PoolableObject po = pool.getPoolableRuleSession();
     RuleSession engine = po.getPooledObject();
     // use the RuleSession to execute rules as required here
     pool.returnPoolableRuleSession(po);

A soft upper bound on the size of the pool can be specified. This allows the pool to respond to temporary increases in demand by growing the pool while allowing the pool to shrink down to this soft upper bound when demand subsides.

Using the RuleSession pooling implementation, you create RuleSession instances when the getPoolableRuleSession method is invoked and the pool is empty. If the load is heavy enough, this will result in an instance count that is greater than the soft limit.

As the load subsides, the number of RuleSession instances in the pool will automatically be decreased to the soft limit.

5.9 Using RuleSession Options

The RL runtime with a RuleSession supports following mentioned options.

It supports the following options:

  • RuleSession.CFG_LOGGING

  • RuleSession.CFG_DECISION_TRACE_LEVEL

  • RuleSession.CFG_DECISION_TRACE_LIMIT

5.9.1 Using the CFG_LOGGING System Property

RL Language runtime looks for CFG_LOGGING as a system property as well as a Boolean in the config Map passed to the RuleSession constructor. A value in the Map overrides the system property value.

5.9.2 Using the CFG_DECISION_TRACE_LEVEL Option

You can configure the trace level in a RuleSession or in a RuleSessionPool by including the RuleSession.CFG_DECISION_TRACE_LEVEL initialization parameter and specifying a level in the configuration Map passed to the RuleSession or RuleSessionPool constructor. This sets the decision trace level at the time a RuleSession is created; invoking reset() guarantees that the level after the reset() is returned to the configured value, in case it had been changed during rule execution. For more information, see Using Rule Engine Level Decision Tracing.

5.9.3 Using the CFG_DECISION_TRACE_LIMIT Option

The size of a trace is limited by limiting the number of entries in a decision trace. This necessary to avoid infinite rule fire loops, due to a possible bug in the rules, from creating a trace that consumes all available heap in the JVM. Set the trace limit with the setDecisionTraceLimit function. The limit may also be configured in a RuleSession (or RuleSessionPool) by including the RuleSession.CFG_DECISION_TRACE_LIMIT initialization parameter with the desired limit in the configuration Map passed to the RuleSession or RuleSessionPool constructor. For more information, see Using Rule Engine Level Decision Tracing.

5.10 Using Decision Tracing

Using Oracle Business Rules, a decision trace is a trace of rule engine execution that includes information on the state of the rule engine, including the state of facts when rule fire.

The Oracle Business Rules rule engine constructs and returns a decision trace using JAXB generated Java classes generated from the decision trace XML schema.

5.10.1 Introduction to Rule Engine Level Decision Tracing

To provide a business analyst friendly presentation of a decision trace requires that the associated rule dictionary is available. Using the rule dictionary associated with a trace allows for a more flexible and efficient approach, as the trace output does not need to include all of the related dictionary content information in the trace.

The XML schema is in the file decisiontrace.xsd and it is part of the Jar file rl.jar as: oracle/rules/rl/trace/decisiontrace.xsd. The packages of interest are oracle.rules.rl.trace, oracle.rules.rl.extensions.trace, and oracle.rules.sdk2.decisiontrace. The Java classes packages generated from the decisiontrace XML schema are in the package oracle.rules.rl.trace and are included in the Javadoc. For more information, see Oracle Business Rules Java API Reference.

5.10.2 Using Rule Engine Level Decision Tracing

A decision trace is a set of XML elements showing rule engine events that occur during rule evaluation. The types of events that are added to a decision trace depend on the trace level selected, and can include:

  • Fact operations (assert, retract, modify)

  • Rules fired

  • Rule activations added or removed from the agenda

  • Ruleset stack changes

  • Rule compilation

  • Reset (which is needed for maintaining state for decision trace analysis)

Each trace contains information about a particular event. For example, a fact operation event entry consists of:

  • The operation type (assert, modify, retract)

  • The ID of the fact in working memory

  • Fact type name (fact classed in RL)

  • Runtime object type name

  • The fact object data, including the property name and value for zero or more fact properties

  • Name of rule, RL name, if the operation was the result of a rule action

  • Millisecond timestamp

In a fact operation event trace, the fact object content reflects the structure of the object as a Java Bean. If the bean properties are references to other beans the related bean content is included in the trace. The value of a bean property can be one of the following alternatives.

  • A string representation of the property. This is the case for primitive types and classes in the java.* and javax.* packages.

  • A nested bean object with its property values.

  • A fact ID. This occurs when the property value is an object which has itself been asserted as a fact. The data for the fact at the time of the trace can be retrieved from the RuleEngineState using the fact ID when analyzing the trace.

  • A collection of values accessed as a List in the trace.

  • An array of values accessed as a List in the trace.

At runtime, to determine which alternative is included in the trace you can test for null; only the correct alternative has a non-null value.

Table 5-3 shows the RL functions that provide control over decision tracing in the rule engine and provide access to a decision trace.

Table 5-3 RL Decision Trace Functions

Function Description

getDecisionTrace

Returns the current trace and starts a new trace.

getDecisionTraceLevel

Gets the current decision trace level.

getDecisionTraceLimit

Returns the current limit on the number of events in a trace.

setDecisionTraceLevel

Sets the decision trace level to the specified level.

setDecisionTraceLimit

Sets the limit on the number of events in a trace.

Default limit value is 10000.

5.10.2.1 Setting Decision Trace Level

The decision trace level may be set by invoking the setDecisionTraceLevel function. You can also configure the initial trace level in a RuleSession or in a RuleSessionPool by including the RuleSession.CFG_DECISION_TRACE_LEVEL initialization parameter and specifying a level in the configuration Map passed to the RuleSession or RuleSessionPool constructor. This sets the decision trace level at the time a RuleSession is created.

You can invoke the setDecisionTraceLevel function on a RuleSession or a RuleSessionPool object after initialization. When you invoke reset(), this function returns the decision trace level to the configured value (if the level was changed during rule execution). Thus, the reset() function resets the decision trace limit to the value set during initialization of a RuleSession or a RuleSessionPool. In these cases, reset() restores the values established using the initialization parameters.

Note:

These reset() semantics for a RuleSession are only valid for a RuleSession initialized with either or both of the CFG_DECISION_TRACE_LIMIT and the CFG_DECISION_TRACE_LEVEL initialization parameters (or that is obtained from a RuleSessionPool when the pool is created with either or both of the CFG_DECISION_TRACE_LIMIT and the CFG_DECISION_TRACE_LEVEL initialization parameters.

The size of a trace is limited by limiting the number of entries in a decision trace. This necessary to avoid infinite rule fire loops, due to a possible bug in the rules, from creating a trace that consumes all available heap in the JVM. Set the trace limit with the setDecisionTraceLimit function. The limit may also be configured in a RuleSession (or RuleSessionPool) by including the RuleSession.CFG_DECISION_TRACE_LIMIT initialization parameter with the desired limit in the configuration Map passed to the RuleSession or RuleSessionPool constructor.

For rules applications that use runUntilHalt, it is the responsibility of the application to invoke getDecisionTrace before the trace limit is hit.

The decision trace provides structure to the trace data so that it can be manipulated programmatically. However, the trace by itself can be cumbersome to analyze. A trace analysis class (oracle.rules.rl.extensions.trace.TraceAnalysis) analyzes a decision trace and facilitates exploration of the trace. Use this class to construct the state of working memory, the agenda, and the ruleset stack from the trace.

The TraceAnalysis API supports the following:

  • Obtain a list of fact types that appear in the trace.

  • Obtain a list of names of the rules that fired in the trace.

  • Obtain a list of the last fact traces for each instance of a specific fact type.

  • Obtain the last fact trace for a specific fact identified by its fact ID.

  • Obtain all of the fact traces for a fact identified by its fact ID.

  • For a fact trace, if the fact trace was created by a rule action, get the rule trace that rule firing in which the action executed.

  • For a rule trace, get the list of fact traces for each fact that matched and resulted in the rule firing.

  • Get the next or previous trace. Exploration of the trace is typically not an iteration over the trace. For example, obtaining a rule trace from a fact trace is essentially jumping to that rule trace. The traces near the rule trace can be explored directly.

  • Obtain a list of rule traces for a rule identified by its name.

  • Obtain the rule engine state for a trace entry. The rule engine state reflects the state of the rule engine after the activity that generated the trace. This API enables inspecting the rule engine state at the time of each trace. This API is most useful with development level tracing. With production level tracing, only the facts in working memory can be tracked and they will not include any fact contents.

Example 5-4 shows a code sample that uses the decision trace analysis API.

Example 5-4 Decision Trace Analysis API Usage

DecisionTrace trace;
...
TraceAnalysis ta = new TraceAnalysis(trace);
// Get all of the last fact traces for a fact type.
List<FactTrace> factTraces = ta.getLastFactTraces("com.example.MyFactType");
// From a particular fact trace, how it was arrived at may be explored, first by
// obtaining the rule that asserted or modified the fact.
// From the FactRecord, the rule that resulted in the record can be obtained.
FactTrace factTrace = factTraces.get(0);  // assumes there is one
RuleTrace ruleTrace = ta.whichRule(factTrace);
  // The ruleTrace will be null if the fact operation was not part of a rule action.
System.out.print("Fact " + factTrace.getFactId() + ", a " + factTrace.getFactType() + " " + factRecord.getFactOp());
if (ruleTrace != null)
    System.out.println(" by rule " + ruleTrace.getRuleName());
else
    System.out.println("");
// The analysis can continue by obtaining the list of FactRecords that matched the rule and
// proceeding to analyze the trace back in time.
List<FactTrace> matchingFacts = ta.getRuleMatchedFacts(ruleTrace);

5.10.3 Decision Trace Samples for Production and Development Level Tracing

Example 5-5 shows a sample production level trace document.

Example 5-5 Sample Production Level Decision Trace

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<decision-trace xmlns="http://xmlns.oracle.com/rules/decisiontrace">
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975549890</timestamp>
        <rule-name>OrderDiscount.goldCustomer</rule-name>
        <token-time>0</token-time>
        <sequence-number>1</sequence-number>
    </trace-entries>
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975549893</timestamp>
        <rule-name>OrderDiscount.goldCustomerDiscount</rule-name>
        <token-time>0</token-time>
        <sequence-number>2</sequence-number>
    </trace-entries>
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975549894</timestamp>
        <rule-name>OrderDiscount.applyDiscount</rule-name>
        <token-time>0</token-time>
        <sequence-number>3</sequence-number>
    </trace-entries>
</decision-trace>

5.10.4 Sample Development Level DecisonTrace

Example 5-6 shows a sample development level decision trace document.

Example 5-6 Sample Development Level DecisionTrace

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<decision-trace xmlns="http://xmlns.oracle.com/rules/decisiontrace">
    <trace-entries xsi:type="fact-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491008</timestamp>
        <fact-id>1</fact-id>
        <operation>assert</operation>
        <fact-type>com.example.Customer</fact-type>
        <object-type>com.example.Customer</object-type>
        <fact-object>
            <properties>
                <name>YTDOrderAmount</name>
                <value>
                    <string>2000.0</string>
                </value>
            </properties>
            <properties>
                <name>level</name>
                <value>
                    <string>null</string>
                </value>
            </properties>
            <properties>
                <name>name</name>
                <value>
                    <string>OneLtd</string>
                </value>
            </properties>
            <properties>
                <name>pastDue</name>
                <value>
                    <string>false</string>
                </value>
            </properties>
        </fact-object>
    </trace-entries>
    <trace-entries xsi:type="activation-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491024</timestamp>
        <rule-name>OrderDiscount.goldCustomer</rule-name>
        <token-time>2</token-time>
        <fact-ids>1</fact-ids>
        <operation>add</operation>
    </trace-entries>
    <trace-entries xsi:type="fact-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491025</timestamp>
        <fact-id>2</fact-id>
        <operation>assert</operation>
        <fact-type>com.example.Order</fact-type>
        <object-type>com.example.Order</object-type>
        <fact-object>
            <properties>
                <name>customerName</name>
                <value>
                    <string>OneLtd</string>
                </value>
            </properties>
            <properties>
                <name>discount</name>
                <value>
                    <string>0.0</string>
                </value>
            </properties>
            <properties>
                <name>grossAmount</name>
                <value>
                    <string>400.0</string>
                </value>
            </properties>
            <properties>
                <name>netAmount</name>
                <value>
                    <string>0.0</string>
                </value>
            </properties>
            <properties>
                <name>number</name>
                <value>
                    <string>1001</string>
                </value>
            </properties>
        </fact-object>
    </trace-entries>
    <trace-entries xsi:type="activation-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491035</timestamp>
        <rule-name>OrderDiscount.goldCustomerDiscount</rule-name>
        <token-time>5</token-time>
        <fact-ids>2</fact-ids>
        <fact-ids>1</fact-ids>
        <operation>add</operation>
    </trace-entries>
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491036</timestamp>
        <rule-name>OrderDiscount.goldCustomerDiscount</rule-name>
        <token-time>5</token-time>
        <fact-ids>2</fact-ids>
        <fact-ids>1</fact-ids>
        <sequence-number>2</sequence-number>
    </trace-entries>
...
    <trace-entries xsi:type="rule-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491036</timestamp>
        <rule-name>OrderDiscount.applyDiscount</rule-name>
        <token-time>7</token-time>
        <fact-ids>2</fact-ids>
        <sequence-number>3</sequence-number>
    </trace-entries>
...
    <trace-entries xsi:type="ruleset-stack-trace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <timestamp>1248975491037</timestamp>
        <operation>pop</operation>
        <ruleset-name>OrderDiscount</ruleset-name>
    </trace-entries>
</decision-trace>