Before being used, the rule engine needs to be configured.
To achieve this, several options are offered. Exposed Java API can be used, but this solution is not adapted to keep the advantage of avoiding a development cycle when rules change. It’s recommended to use rule engine factories. How to build an instance is detailed here.
Utilities centralize instances handling in order to limit the number of configuration parsings and compilations to the strictly necessary.
Note that the factories support reading the configuration, but also writing it. It is thus possible to create a GUI to modify the configuration using the Java API then use the factory to save it.
It is also simple change configuration format: just read with the Factory accepting the original format, then save with the Factory handling the target format.
Input data is provided to rule engine by a context.
- Compilation context will be provided for compilation step.
- Execution context will be provided for Runtime.
- Context might be automatically instanciated with a context factory.
- Context is not Thread Safe, unlike the rule engine instance.
- A context instance is linked to a rule engine instance, the opposite is not true.
The context mainly provides :
- Reading and writing bases. Accesses to input / output beans specified in the engine will always be done from these bases, depending on the access type.
- The variables repository.
MRules provides several context types:
- Standalone context: simplest implementation, with which no input bean is provided to the rule engine. Rules might be based on other data, like current time, etc …
Corresponding Java Classes are MStandaloneCompilationContext for compilation and MStandaloneExecutionContext pour l’exécution.
- Standard context: simple implementation, allowing to provide to the rule engine one Java bean, bringing input and output values.
Corresponding Java Classes are MCompilationContext for compilation and MExecutionContext for execution.
- IN / OUT context: A slightly more complete implementation, allowing to provide to the rule engine twoJava bean. one brings input values and the other output values.
Corresponding Java Classes are MInOutCompilationContext for compilation and MInOutExecutionContext for execution.
- List context: for compatibility with use cases defined by JSR94. It accepts an Objects List, which will be grouped by types (Classes) and on which will be iterated. Each type must be defined in rule engine configuration.
Corresponding Java Classes are MListCompilationContext for compilation and MListExecutionContext for execution.
- Finally, we can also notice the presence of other contexts, for example to turn a compilation context into an execution context (or vice versa) or to encapsulate a context to change a specific behavior. These contexts are for internal use or intended for specific addons development.
The context can be created manually (new instance) or via the context factory.
The three life cycle phases
The life cycle of a rule set instance is composed by three phases, detailled above.
After the rule engine is built, compilation is a mandatory step.
It allows to operate several pre-calculations:
- Determine necessary object types and type conversions which have to be performed.
- Detect potential errors, which won’t occur at Runtime.
Two compilation levels are available:
- Standard level:
- Needs to know input objects types.
- Will detect more errors and make most pre-calculations.
- Light level:
- No needs to know input objects types.
- Allows more flexibility at Runtime.
The compilation stage in the STANDARD level will check if properties are available in input / output beans, using reflection. For example, if the configuration tries to use the property “person.agge” instead of “person.age”, then the related getter / setter will not be found and an error will be raised.
It also allows to perform internal cast only once at compule time instead of doing it at each execution . For example, if the property “person.age” is an Integer and is compared to “21”, the value “21” will be converted into an Integer.
Only the signature of methods are used: data is not actually read. So you may provide an empty bean with no data, it does not matter. No need to fill it with dummy values, they will not be used. In some cases, the provided bean might be modified (nested beans created, etc..).
If the bean implementation is not available, the LIGHT compilation level can be used. It does not perfom property availability checks. It tries to cast internal data at best (for example the String “true” will be cast into a boolean, “21.0” into a double).
Without compilation, execution will result to an Exception with error code “MRE_COMPILATION_MISSING”. RuleSet Factories can perform this step seamlessly.
Otherwise, this simple line of code launches compilation:
After compilation, a second phase is the optimization. This optional step will try to optimise performances during execution and minimize memory footprint. Optimization is automatically launched at the end of the compilation, by the line of code above, except if explicitly deactivated.
Optimization deactivation is made via the compilation context (setOptimizationEnabled), or via the CDI or JNDI configuration.
Optimization is made of two sub-steps:
- First, a global optimizer chain is executed. Included optimizers are:
- Finding expressions for which evaluation result is constant, and transform them into constants (example : “A OR true” equals “true”).
- Finding logical expressions which may be simplified (example : “A OR A” equals “A”).
- Memory foot print optimization, by mutualizing similar objects.
- Second, unitary optimization . The “optimize” method is invoked for each Addon.
Each step of the global optimizer chain is deactivable, via the rule set generic properties. (see each optimizer’s javadoc to find out the property name).
After building, compilation and when input and output beans are valued and positionned, execution is only a formality, launched by a single line of code: