Red Hat
Mar 7, 2012
by devdude

JBoss Drools is more than Expert and Guvnor only, the suite of products also offers Fusion , Planner and Flow (now called jBPM). After getting my hands with dirty with Expert, I want to look at the Fusion part, which covers the event handling. Based on CEP (Complex Event Processing ) as an event-driven architecture which is an own science by itself, whole books written about nothing else than events (link ).

There is not much information on the web about Fusion other than the JBoss documentation , some chapters in Books (Packt Publishing: Drools 5 Developer Guide , Drools Cookbook ) and a handful of blog entries (link ), practically no working samples or end-to-end tutorials.

In this tutorial we will apply the necessary changes to our HelloDrools project from the previous tutorial :

  • The good thing: We dont need any other libraries than the ones already used.
  • Copy the previous project and give it a new name ‘HelloDroolsFusion’
  • In the last tutorial we had a simple message class and inserted a message as fact. A rule was triggered when the message type is equal to ‘Hello’
  • As refresher: The first rule

    import hellodrools.Message
    rule "Hello World"
    when
        message:Message (type=="Hello")
    then
        System.out.println("Hello World, Drools!");
    end
    

  • and the code to prepare the rule engine , insert a fact and trigger the rule execution

    ...
        private static KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        private static Collection<KnowledgePackage> pkgs;
        private static KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
        private static StatefulKnowledgeSession ksession;
    
    ...
            // this will parse and compile in one step
            // read from file
            kbuilder.add( ResourceFactory.newClassPathResource( "/hellodrools/testrules.drl",HelloDroolsNew.class),ResourceType.DRL );
    
            // Check the builder for errors
            if ( kbuilder.hasErrors() ) {
                System.out.println( kbuilder.getErrors().toString() );
                throw new RuntimeException( "Unable to compile drl\"." );
            }
    
            // get the compiled packages (which are serializable)
            pkgs = kbuilder.getKnowledgePackages();
    
            // add the packages to a knowledgebase (deploy the knowledge packages).
            kbase.addKnowledgePackages( pkgs );
    
            ksession = kbase.newStatefulKnowledgeSession();
    
            Message msg = new Message();
            msg.setType("Hello");
            ksession.insert(msg);
    
            ksession.fireAllRules();
    ...
    
  • First we adopt the rule engine setup.

    In the above sample we use the engine CLOUD mode which is the default setting, in this mode there is no notion of time, facts get inserted but the engine is not aware of the age of an event, even all facts have a timestamp.

    Change to STREAM mode

            ...
            KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
            config.setOption(EventProcessingOption.STREAM);
            kbase = KnowledgeBaseFactory.newKnowledgeBase(config);
            ...
    

    We also make use of the pseudoclock that comes with Drools to simulate our events (instead of waiting in real-time)

            ...
            KnowledgeSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
            conf.setOption(ClockTypeOption.get("pseudo"));
            ksession = kbase.newStatefulKnowledgeSession(conf,null);
            ...
    

    We declare one or more entrypoint to insert the facts later. Only the STREAM mode supports the entrypoint to allow the definition of different ‘sources’ of events.

    entryPoint1 = ksession.getWorkingMemoryEntryPoint("entryone");
    
  • The complete initialization code block:

        private static void initDrools() {
    
            kbuilder.add(ResourceFactory.newClassPathResource("/hellodrools/testrules.drl", HelloDroolsNew.class), ResourceType.DRL);
    
            if (kbuilder.hasErrors()) {
                System.out.println(kbuilder.getErrors().toString());
                throw new RuntimeException("Unable to compile drl\".");
            }
            pkgs = kbuilder.getKnowledgePackages();
    
            KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
            config.setOption(EventProcessingOption.STREAM);
            kbase = KnowledgeBaseFactory.newKnowledgeBase(config);
    
            kbase.addKnowledgePackages(pkgs);
    
            KnowledgeSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
            conf.setOption(ClockTypeOption.get("pseudo"));
            ksession = kbase.newStatefulKnowledgeSession(conf,null);
    
            entryPoint1 = ksession.getWorkingMemoryEntryPoint("entryone");
    
            new Thread() {
    
                @Override
                public void run() {
                    ksession.fireUntilHalt();
                }
            }.start();
        }
    

    Note: We use a separate thread to fire the rules until we stop the engine.

  • We want to create the following (simple) rules :

    1) Any message inserted 1 minute after the application was started.

    2) Any message inserted between 1 and 10 minutes after another message.
  • We create a simple class AppInfo to pass the application starttime to the rule engine as fact.

    (Alternatively we could use a global for this, but I dont favor globals)

    package hellodrools;
    
    import java.util.Date;
    
    public class AppInfo {
        private Date timestamp;
    
        public AppInfo() {
            this.timestamp = new Date();
        }
    
        @Override
        public String toString() {
            return "AppInfo{" + "startTime=" + timestamp + '}';
        }
    
        public Date getTimestamp() {
            return timestamp;
        }
    
        public void setTimestamp(Date timestamp) {
            this.timestamp = timestamp;
        }
    }
    
  • We insert the facts

    Create and advance the pseudoclock

            ...
            SessionPseudoClock clock = ksession.getSessionClock();
            ...
            clock.advanceTime(1, TimeUnit.MINUTES);
            ...
    

    Stopping and disposing the engine after a short artificial delay with ksession.halt() and ksession.dispose()

    The complete code:

        private static void initMessageObjects() {
    
            SessionPseudoClock clock = ksession.getSessionClock();
    
            AppInfo app = new AppInfo();
            entryPoint1.insert(app);
    
            Message msg = new Message();
            msg.setType("Hello");
            msg.setMsgtext("1st message");
            entryPoint1.insert(msg);
    
            clock.advanceTime(1, TimeUnit.MINUTES);
    
            Message msg2 = new Message();
            msg2.setType("Hello");
            msg2.setMsgtext("2nd message");
            entryPoint1.insert(msg2);
    
            try {
                System.err.println("[[ Sleeping ...]]");
                Thread.sleep(5000);
            } catch (final InterruptedException e) {
                e.printStackTrace();
            }
            System.err.println("[[ awake ...]]");
    
            ksession.halt();
            ksession.dispose();
        }
    
  • Now let’s adopt the rules

    import hellodrools.Message
    import hellodrools.AppInfo
    
    declare Message
    @role(event)
    end
    
    declare AppInfo
    @role(event)
    end
    
    rule "RULE 1"
    when
        $appinfo:AppInfo() from entry-point entryone
        $message:Message( this after[ 1m ] $appinfo ) from entry-point entryone
    then
        System.out.println("RULE 1 (message after 1min app start): " + $message.getMsgtext());
    end
    
    rule "RULE 2"
    when
        $message1:Message() from entry-point entryone
        $message2:Message( this after[ 1m, 12m ] $message1 ) from entry-point entryone
    then
        System.out.println("RULE 2 (message between 1 and 12 min to other message): " + $message1.getMsgtext() + " and " + $message2.getMsgtext());
    end
    

    Notes :

    1) Import the classes to make the rule engine aware of the objects

    2) Define facts as event (declare Message..), by default the event time is the build-in timestamp of the fact, otherwise declare with @timestamp()

    3) Use of entrypoints for our facts

  • Run the application

    Running the sample

  • This project should you get running with a very simple setup, but simple enought to understand the concept. I highly recommend playing around and looking into the documentation and the 2 Pack Publishing Books mentioned above.

    Lookout:

    - Use our own times instead of the creation timestamp,

    - Experiment with more facts and more complex rules. I suggest you look at the available temporal operators here .

    - Embed the rule engine into a container, run it on an application server instead of a simple java application only.