Build the instance

We’ll see here the various available solutions to obtain running instance of the rule engine.

Java API

It is possible to use exposed public MRules APIs to build or modify an instance.

3 main steps are necessary:

  • Instanciation of Objects structure.
  • Compilation context instanciation and compilation.
  • Execution context instanciation and execution.

An already existing and compiled instance may be altered, in which case the changes made will only be taken into account after a new compilation.

The simple code below shows an example of an instance printing in console “Good Morning!” up to 12, “Good Afternoon!” until 20PM and then “Good night!”.

package com.massa.mrules.demos.site;

import java.util.ArrayList;
import java.util.List;

import com.massa.mrules.accessor.MPropertyAccessor;
import com.massa.mrules.accessor.MValueAccessor;
import com.massa.mrules.action.MActionPrint;
import com.massa.mrules.condition.ICondition;
import com.massa.mrules.condition.MEvaluationCondition;
import com.massa.mrules.context.compile.MCompilationContext;
import com.massa.mrules.context.execute.MExecutionContext;
import com.massa.mrules.exception.MExecutionException;
import com.massa.mrules.exception.MRuleValidationException;
import com.massa.mrules.executable.IExecutable;
import com.massa.mrules.executable.MRule;
import com.massa.mrules.operator.evaluation.LT;
import com.massa.mrules.set.MRuleExecutionSet;

public class SimpleManualInstance {

    public static void main(final String[] args) throws MRuleValidationException, MExecutionException {
        SimpleManualInstance.buildAndExecute();
    }

    public static void buildAndExecute() throws MRuleValidationException, MExecutionException {
        //Main sequence of executables iterated by main Execution Set.
        final List mainExecutables = new ArrayList();

        //First Rule: if before 12, print Good Morning!
        ICondition condition = new MEvaluationCondition(
                new LT(), // Less Than Operator
                new MPropertyAccessor("#TIME"), // Property Accessor for Current Time Global Variable
                new MValueAccessor("12:00:00")); // Constant for 12
        List thens = new ArrayList();
        thens.add(new MActionPrint(new MValueAccessor("Good Morning!n"))); // Action to perform if true
        IExecutable e = new MRule(condition, thens); //Build rule instance. No else needed.
        mainExecutables.add(e);

        //Second Rule: if before 12, print Good Afternoon!
        condition = new MEvaluationCondition(
                new LT(), // Less Than Operator
                new MPropertyAccessor("#TIME"), // Property Accessor for Current Time Global Variable
                new MValueAccessor("20:00:00")); // Constant for 20
        thens = new ArrayList();
        thens.add(new MActionPrint(new MValueAccessor("Good Afternoon!n"))); // Action to perform if true
        e = new MRule(condition, thens); //Build rule instance. No else needed.
        mainExecutables.add(e);

        //Default action if nothing else is chosen before: Good Night!
        mainExecutables.add(new MActionPrint(new MValueAccessor("Good Night!n")));

        //Defining execution set, with stopAtFirstValidatedCondition to true
        //So that we exit as soon as something is printed.
        final MRuleExecutionSet set = new MRuleExecutionSet(mainExecutables, true);

        //Contexts. Compilation context needs a non null base, so provide a simple Object instance.
        final MCompilationContext<object> comp = new MCompilationContext<object>(set, new Object());
        final MExecutionContext<void> exec = new MExecutionContext<void>(set);

        //Compilation and execution.
        comp.compile();
        exec.execute();
    }
}

However, it is obvious that this method is very verbose. It has the main disadvantage of not being able to be modified without the intervention of a development team.

The use of the API may however be useful to create a GUI dedicated to the modification of an existing instance built by a Factory.

Factories

A Rule Engine Factory is a Java Class implementing the IRuleExecutionSetFactory interface. This interface defines the compilation parameters and methods for reading / writing.

In fact, the overall contract of a factory implementation is to manage both configuration reading (building the rule engine instance from a configuration) and writing (saving the configuration from the rule engine instance).

The factory will also perform the compilation phase when reading if it has enough information to do it :

  • A compilation context is provided to the factory, with necessary data filled (input / output data).
  • The ruleset can provide a context factory, which will allow to instantiate automatically required data.

The current version of MRules provides three implementations:

Light XML grammar

The Factory class is SimpleXMLFactory. It handles a simple grammar which does not take into account all the Addons or all the conditionning possibilities.

This syntax has the following principles:

  • Defining conditions / actions blocks (“rule”). For each of them :
  • Test one or more input bean property value (all operators are handled)
  • Set one or more output bean property.

Example :

<?xml version="1.0" encoding="UTF-8"?>

<!-- 
This example shows possibilities offered by the simple XML grammar.
 -->
<rules name="RuleSetName" stopAtFirstValidatedCondition="true">
    <rule name="rule 1">
        <if>
            <!-- Logical operator is AND -->
            <!-- Property types are detected automatically and values are cast to be compared -->
            <!-- Default evaluation operator is EQ, but might be modified using the dedicated attribute -->
            <inStringProperty>In String Value</inStringProperty>
            <inIntProperty2>1</inIntProperty2>
            <inDateProperty operator="GTE">2016-01-01</inDateProperty>
        </if>
        <then>
            <!-- Nested beans are handled -->
            <outProperty1.subDoubleProperty>1.234</outProperty1.subDoubleProperty>
            <outProperty1.subBooleanProperty>true</outProperty1.subBooleanProperty>
            <outProperty2.subStringProperty>Out String Value</outProperty2.subStringProperty>
        </then>        
    </rule>
    <rule name="rule 2">
        <if>
            <!-- In this particular case, the IN operator is implicitly used. -->
            <inStringProperty>String Value 1</inStringProperty>
            <inStringProperty>String Value 2</inStringProperty>
            <inStringProperty>String Value 3</inStringProperty>
            <inStringProperty>String Value 4</inStringProperty>
        </if>
        <then>
            <outProperty2.subStringProperty>Out String Value</outProperty2.subStringProperty>
        </then>        
    </rule>
    <rule name="rule 2 bis">
        <if>
            <!-- Other way to build the previous condition. -->
            <inStringProperty operator="IN" separator=";">String Value 1;String Value 2;String Value 3;String Value 4</inStringProperty>
        </if>
        <then>
            <outProperty2.subStringProperty>Out String Value</outProperty2.subStringProperty>
        </then>        
    </rule>
    <rule name="rule 3 bis">
        <if>
            <!-- List "inListProperty" must contain at least all 4 values. -->
            <inListProperty operator="COLINCLUDES" separator=";">String 1;String 2;String 3;String 4</inListProperty>
        </if>
        <then>
            <outProperty2.subStringProperty>Out String Value</outProperty2.subStringProperty>
        </then>        
    </rule>
    <rule name="default rule">
        <!-- No if node, so always true -->
        <then>
            <outProperty2.subStringProperty>Default out String</outProperty2.subStringProperty>
            <outProperty1.subBooleanProperty>false</outProperty1.subBooleanProperty>
        </then>        
    </rule>
</rules>

However, this grammar might be quite enough in many cases.

Full XML grammar

The Factory class is RichXMLFactory. It allows usage of all Addons, including specific implementations. They must declare the configuration properties to save through the PersisantProperties et PersistantProperty annotations.

The root node declares the IMRuleExecutionSet to use and its configuration properties (Persistant Property). The “implem” attribute is requiered for each Addon declaration , providing its unique implementation ID.
Each attribute on nodes represents a configuration parameter for the declared Addon. Note the syntax “PROPERTY:#TIME”: it allows to avoid declaring a child node to declare a sub-addon, by providing as argument it’s default property value. Not all Addons declare a default property: they are described on the Addons Properties page. It’s also possible to chain Addons, for example: “NULLIFEMPTY:PROPERTY:myProperty.mySubProperty”.
Each child node also represent a configuration parameter for the parent Addon, which cannot be represented directly as an attribute.

The following example reproduces the demo using the Java API:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Complete XML syntax example. 
All addons nodes declare the "implem" attribute, containing the unique ID of the Addon to invoke.
- Other attributes are Persistant Properties. Note the specific syntax "PROPERTY:#TIME" :
  it saves from declaring a subnode for an Addon, by allowing to provide as an argument the
  default Addon property. Not all Addons declare a Default Property: they are described in the Addons properties page.
- Sub Nodes are Persistant Properties which can not be represented as an Attribute.
-->
 
<!-- Root node declares the IRuleExecutionSet implementation to use and its parameters. -->
<rules implem="RULEEXECUTIONSET" name="Hello" stopAtFirstValidatedCondition="true">
    <!-- Each "executable" nodes declare one step to execute. -->

    <!-- First Rule: if before 12, prints Good Morning! -->
    <executable implem="RULE">
        <condition implem="EVAL" source="PROPERTY:#TIME" operator="LT" reference="VALUE:12:00:00"/>
        <then implem="PRINT" source="VALUE:Good Morning!&#10;" /> 
    </executable>

    <!-- Second Rule: if before 12, prints Good Afternoon! -->
    <executable implem="RULE">
        <condition implem="EVAL" source="PROPERTY:#TIME" operator="LT" reference="VALUE:20:00:00"/>
        <then implem="PRINT" source="VALUE:Good Afternoon!&#10;" /> 
    </executable>
    
    <!-- Default: Prints Good Night! -->
    <executable implem="PRINT" source="VALUE:Good Night!&#10;" /> 
</rules>

The following Java laucher may then be used:

package com.massa.mrules.demos.site;

import java.io.InputStream;

import com.massa.mrules.context.compile.CompilationLevel;
import com.massa.mrules.context.compile.MCompilationContext;
import com.massa.mrules.context.execute.MExecutionContext;
import com.massa.mrules.exception.MConfigurationException;
import com.massa.mrules.exception.MExecutionException;
import com.massa.mrules.exception.MRuleValidationException;
import com.massa.mrules.factory.xml.RichXMLFactory;
import com.massa.mrules.set.IMruleExecutionSet;

public class SimpleRichXmlInstance {

    public static void main(final String[] args) throws MRuleValidationException, MExecutionException, MConfigurationException {
        SimpleRichXmlInstance.buildAndExecute();
    }

    public static void buildAndExecute() throws MRuleValidationException, MExecutionException, MConfigurationException {
        //Compilation context.
        final MCompilationContext<Object> comp = new MCompilationContext<Object>(CompilationLevel.STANDARD, new Object());
        
        //Build instance with factory
        final RichXMLFactory factory = new RichXMLFactory(comp);
        final InputStream xml = ClassLoader.getSystemResourceAsStream("RichExample.xml");
        final IMruleExecutionSet set = factory.read(xml);

        //Execution context.
        final MExecutionContext<Void> exec = new MExecutionContext<Void>(set);
        exec.execute();
    }
}

Note that the above syntax might be much more compact in terms of XML representation, by setting sub-objects properties values directly in parents nodes. It is perfectly functional at reading but is not yet managed for writing:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Root node declares the IRuleExecutionSet implementation to use and its parameters. -->
<rules implem="RULEEXECUTIONSET" name="Hello" stopAtFirstValidatedCondition="true">
    <!-- Each "executable" nodes declare one step to execute. -->

    <!-- First Rule: if before 12, prints Good Morning! -->
    <executable implem="RULE" then="PRINT:VALUE:Good Morning!&#10;" condition="EVAL" condition.source="PROPERTY:#TIME" condition.operator="LT" condition.reference="VALUE:08:00:00" />

    <!-- Second Rule: if before 12, prints Good Afternoon! -->
    <executable implem="RULE" then="PRINT:VALUE:Good Afternoon!&#10;" condition="EVAL" condition.source="PROPERTY:#TIME" condition.operator="LT" condition.reference="VALUE:20:00:00" />
    
    <!-- Default: Prints Good Night! -->
    <executable implem="PRINT" source="VALUE:Good Night!&#10;" /> 
</rules>

To be noted, in this example, the factory performs the compilation, but a context has to be instantiated and provided by launcher. By specifying a Context Factory in the rule engine configuration, all data required for compilation will be automatically instantiated. The feature is very interesting if instances are meant to be centralized using the Builder (cf. Accessing the instances).

A little change in the XML configuration is enough to achieve this:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Complete XML syntax example. 
All addons nodes declare the "implem" attribute, containing the unique ID of the Addon to invoke.
- Other attributes are Persistant Properties. Note the specific syntax "PROPERTY:#TIME" :
  it saves from declaring a subnode for an Addon, by allowing to provide as an argument the
  default Addon property. Not all Addons declare a Default Property: they are described in the Addons properties page.
- Sub Nodes are Persistant Properties which can not be represented as an Attribute.
-->
 
<!-- Root node declares the IMRuleExecutionSet implementation to use and its parameters. -->
<rules implem="RULEEXECUTIONSET" name="Hello" stopAtFirstValidatedCondition="true">
    <!-- This first line allows the compilation phase to be performed automatically by the factory.
    No input are needed, so the generic Object type might be used.-->
    <contextFactory implem="CONTEXT" classIn="Object"/>

    <!-- Each "executable" nodes declare one step to execute. -->

    <!-- First Rule: if before 12, prints Good Morning! -->
    <executable implem="RULE">
        <condition implem="EVAL" source="PROPERTY:#TIME" operator="LT" reference="VALUE:12:00:00"/>
        <then implem="PRINT" source="VALUE:Good Morning!&#10;" /> 
    </executable>

    <!-- Second Rule: if before 20, prints Good Afternoon! -->
    <executable implem="RULE">
        <condition implem="EVAL" source="PROPERTY:#TIME" operator="LT" reference="VALUE:20:00:00"/>
        <then implem="PRINT" source="VALUE:Good Afternoon!&#10;" /> 
    </executable>
    
    <!-- Default: Prints Good Night! -->
    <executable implem="PRINT" source="VALUE:Good Night!&#10;" /> 
</rules>

The following simplified Java laucher may then be used:

package com.massa.mrules.demos.site;

import java.io.InputStream;

import com.massa.mrules.context.compile.CompilationLevel;
import com.massa.mrules.context.compile.MCompilationContext;
import com.massa.mrules.context.execute.MExecutionContext;
import com.massa.mrules.exception.MConfigurationException;
import com.massa.mrules.exception.MExecutionException;
import com.massa.mrules.exception.MRuleValidationException;
import com.massa.mrules.factory.xml.RichXMLFactory;
import com.massa.mrules.set.IMruleExecutionSet;

public class SimpleRichXmlInstance {

    public static void main(final String[] args) throws MRuleValidationException, MExecutionException, MConfigurationException {
        SimpleRichXmlInstance.buildAndExecute();
    }

    public static void buildAndExecute() throws MRuleValidationException, MExecutionException, MConfigurationException {
        //Build instance with factory
        final RichXMLFactory factory = new RichXMLFactory();
        final InputStream xml = ClassLoader.getSystemResourceAsStream("RichExample.xml");
        final IMruleExecutionSet set = factory.read(xml);

        //Execution context.
        final MExecutionContext<Void> exec = new MExecutionContext<Void>(set);
        exec.execute();
    }
}

The complete grammar also allows to store  the generic properties of the rule set. These properties are accessible programmatically via the “setProperty” and “getProperty” methods. The syntax is very simple:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generic properties example. -->
 
<!-- Root node. -->
<rules implem="RULEEXECUTIONSET" name="Ruleset with properties">
    <properties>
        <property_name_1>value 1</property_name_1>
        <property_name_2>value 2</property_name_2>
    </properties>
    <!-- ... -->
</rules>

DSL Grammar

To be able to use this grammar and therefore the associated factory, you need the license for the Grammar Engine (“mrules-dsl”). You also need to import the “mrules-ext-dsl-bre” extension dependency .

The factory class is NativeLanguageFactory. It makes it easier to build a ruleset configured with the “Natural Language” grammar based on our grammar engine.

We invite you to consult the extension details page for more details on how to use the grammar and for more precise description of the syntax and editing tools.

The following example reproduces the demo by using this grammar:

// Begining rule set
ruleset

  // Declares global rule set configuration (name, context factory, ...)
  name is "Ruleset with properties"
  context factory is standalone
  stop at first validated condition is true

  // Declares rules
  
  // First Rule: if before 12, prints Good Morning!
  if #TIME is lesser than "12:00:00" then
    print "Good Morning!\n"
  end of if
  
  // Second Rule: if before 12, prints Good Afternoon!
  // Note the sign "<" which is equivalent to "is lesser than"
  if #TIME < "20:00:00" then
    print "Good Afternoon!\n"
  end of if
  
  //Default: Prints Good Night!
  print "Good Night!\n"

// Ending rule set
end of ruleset

The following Java laucher may then be used:

package com.massa.mrules.demos.site;

import java.io.InputStreamReader;

import com.massa.mrules.context.execute.MExecutionContext;
import com.massa.mrules.exception.MConfigurationException;
import com.massa.mrules.exception.MExecutionException;
import com.massa.mrules.extensions.dsl.factory.NativeLanguageFactory;
import com.massa.mrules.set.IMruleExecutionSet;

public class SimpleDslInstance {

    public static void main(final String[] args) throws MConfigurationException, MExecutionException {
        SimpleDslInstance.buildAndExecute("DslExample.mrl");
    }

    public static void buildAndExecute(final String mrlFile) throws MConfigurationException, MExecutionException {
        //Read
        final NativeLanguageFactory factory = new NativeLanguageFactory();
        final IMruleExecutionSet set = factory.read(new InputStreamReader(ClassLoader.getSystemResourceAsStream(mrlFile)));

        //Execute
        final MExecutionContext exec = new MExecutionContext(set);
        exec.execute();
    }
}

Serialisation

Another implementation can back up an instance using the Java binary serialization. The stream can optionally be encoded in Base 64.