Drools Grid (version 2) – #3 Drools Grid Remote Services

Now it’s time to jump to a more interesting environment, the Remote Environment.
During this post I will try to show the components that will interact in a remote environment. We will continue using the Drools Grid Services APIs but we will change the underlying implementation from LocalProviders to RemoteProviders.

Remote Providers

We will continue using the previous examples (Local Providers from the previous post) and we will only change the GridTopology configuration.

GridTopologyConfiguration gridTopologyConfiguration =
                                       new GridTopologyConfiguration("MyTopology");
gridTopologyConfiguration.addExecutionEnvironment(
                             new ExecutionEnvironmentConfiguration(
                                 "MyMinaEnv",
                                  new MinaProvider("127.0.0.1",9123)));
gridTopologyConfiguration.addDirectoryInstance(
                             new DirectoryInstanceConfiguration(
                                  "MyMinaDir",
                                  new MinaProvider("127.0.0.1",9124)));
return gridTopologyConfiguration;

As you can see the only thing that change are the providers for both the ExecutionEnvironment and the DirectoryInstance.

MinaProvider is a Remote Provider that uses Apache Mina for transport. This Remote Provider gives us the possibility to create, host and execute our knowledge in remote locations.

Characteristics of Remote Executions

As you may know, when you are dealing with Remote Environments you need to have some considerations to be able to distribute your knowledge and some considerations to execute and interact with the remote sessions.

1) All your interactions will go to the wire in different ways depending the chosen underlying implementation. So you need to be careful with the information that you send over the wire. Basically you will need to implement “Serializable” or some mechanism to share information between your apps and the remote provider. You will see in the demo project that all the domain classes has been marked as Serializable.

2) Your knowledge will be executed remotely and your rules should be designed with that restriction in mind. If the rules call a service, this service must be also accessible from the RemoteProvider location.

3) You must distribute your domain model with your rules. This is pretty related with 2. The environment that will actually execute the rules will need to have all the classes loaded in the server classloader to be able to compile and run the rules/knowledge.

Following these considerations for every environment (Local/Remote/Distributed) will let you to switch from one to the other without any problem.

Setting Up the Environment

Once you mark the domain classes to be Serializable and check that your rules can be executed in a remote environment you need to set up the living topology.
As we describe in the GridTopologyConfiguration we have two servers one listening in 127.0.0.1:9123 (localhost) and the other in 127.0.0.1:9124.

For that reason we need to start up both servers. I can simulate the servers in a test scenario, but for practical reasons I prefer to show how to distribute and run each server instance.

The first step is to download both servers: The Execution Environment Server:  drools-grid-remote-mina-distro.zip and the Directory Instance server: drools-grid-remote-dir-mina-distro.zip.
You need to unzip the content from both files in different directories. Inside each of them you will find the start.sh script. Running this script (in unix machines, I’m working in bat files for window$) will start each of the servers.
The script contains the information about the host and the ports (I will work on customization for these parameters later).

When you uncompress these files you will see that there is a lib directory. Inside this lib directory you will find all the dependencies required to run the execution node.
One important thing to understand here is that you will probably need to include all the rules dependencies here in the lib directory. So as a second step, you will need to clean and build the drools-grid-mini-demo project and copy the jar to the Execution Environment server lib’s directory(drools-grid-remote-mina/lib/). You will need to have the domain classes (and all the required dependencies) that are being used inside the rules inside the Execution Environment lib directory in order to execute the rules/knowledge remotely.

The third step is to start both the Execution Environment and the Directory Instance Server. You can do that running the  ./start.sh script in the console. (take a look inside the script. If you want to change the host or the port where
the server is started).

Note: Usually you wont need to copy dependencies to the Directory Instance lib project.

Analyzing the Remote Topology Knowledge Execution

Drools Grid Remote Executions

As you can see in the previous figure, three steps are executed by the application. Using Drools Grid Services, the application queries the available directories to find a running execution environment. The Directory Instance return the registered Execution Environment (Remote) to the application to start working. Once we get an Execution Environment we can create a Knowledge Builder, a Knowledge Base and a Knowledge Session. After we have the session we can start inserting facts and firing all the activated rules.

From the application perspective will be transparent where the rules are executed, but in the background you can check that the current execution is happening in the remote server. The example project that you can download, uses a rule that only prints out the the console that the rule was activated. In this case and just to show that the execution happens in the remote server, you can see that the output is being printed in the server console. In real scenarios you will probably want to send a response or some results to the client application. We will see how to achieve that in following posts.

In Brief

During this post we have seen how we can move our application that was constructed using Local Providers to Remote Providers. You can download a new version of the drools-grid-mini-demo v2 here. You will find a new class to execute called: RemoteTopologyTest that contains the GridTopologyConfiguration that uses the RemoteProviders.

It would be nice to pick the old project from the previous post, download it and then try to migrate to use the Remote Providers. Try to extend the example and let me know if you have any problems.

The next post will be about Distributed Environment, and there is when this stuff begins to make really sense.

Advertisements

33 thoughts on “Drools Grid (version 2) – #3 Drools Grid Remote Services”

  1. I will attempt to test it this weekend.

    Points.
    1) This framework only see Java Objects. These objects must be swizzled in from the ‘real world’. Assume the ‘real-world’ will never fit in any memory (distributed or otherwise).
    2) We are dealing with subsets of external data. These subsets are most performant with high locality.
    3) We have Rules Ri,Rj: Ri(A,B,C,S) -> K = f(A,B,C); insert(K)
    Rj(D,E,F,S) -> L = f(D,E,F); insert(L)
    with S being search space state S
    4) If we have Nodes N1, N2 and Size(tuples member (A,B,C,D,E,F)) >> Intersect( {A,B,C}, {D,E,F} ) then it makes sense to have i = A,B,C and j = D,E,F, and N1=i, N2=j
    5) If K, L are facts in a search space, which may be retracted, then these state constraints S will be accumulated in memory.
    6) Something like Rk(K,S) -> retract K, rollback all associated state from Ri until Rk,update S to avoid K

    Until I understand how the tuples are shared amongst nodes, I am probably just muddying the waters…

    🙂

    Like

  2. Hi, I am able to run the example successfully and noted that the rules are executed at remote server. I have cluster setup with two JVMs and I am want to use the stream mode (drools events with @expire header). To achieve this generally we use following code if we use single instance:

    final KnowledgeBaseConfiguration kbConfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
    kbConfig.setOption(EventProcessingOption.STREAM);
    KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(kbConfig);

    Please can you let me know how to achieve this in Grid environment?

    Thanks,
    Mrunali

    Like

    1. Hi,
      Is stream mode supported in GRID environment?

      I modified the code like:

      final KnowledgeBaseConfiguration kbConfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
      kbConfig.setOption(EventProcessingOption.STREAM);
      KnowledgeBase kbase = node.get(KnowledgeBaseFactoryService.class).newKnowledgeBase(“simpleValidationKbase”,kbConfig);
      kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
      StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

      But when you try to insert the fact after expiration time we get following error:

      org.drools.RuntimeDroolsException: Unexpected exception executing action org.drools.reteoo.ReteooWorkingMemory$WorkingMemoryReteExpireAction@1abdac9
      at org.drools.common.AbstractWorkingMemory.executeQueuedActions(AbstractWorkingMemory.java:1473)
      at org.drools.common.AbstractWorkingMemory.insert(AbstractWorkingMemory.java:1159)
      at org.drools.common.AbstractWorkingMemory.insert(AbstractWorkingMemory.java:1123)
      at org.drools.common.AbstractWorkingMemory.insert(AbstractWorkingMemory.java:917)
      at org.drools.impl.StatefulKnowledgeSessionImpl.insert(StatefulKnowledgeSessionImpl.java:251)
      at org.drools.command.runtime.rule.InsertObjectCommand.execute(InsertObjectCommand.java:83)
      at org.drools.command.runtime.rule.InsertObjectCommand.execute(InsertObjectCommand.java:38)
      at org.drools.command.KnowledgeContextResolveFromContextCommand.execute(KnowledgeContextResolveFromContextCommand.java:66)
      at org.drools.grid.internal.GenericMessageHandlerImpl.messageReceived(GenericMessageHandlerImpl.java:52)
      at org.drools.grid.remote.mina.ClientGenericMessageReceiverImpl.messageReceived(ClientGenericMessageReceiverImpl.java:67)
      at org.drools.grid.remote.mina.MinaIoHandler.messageReceived(MinaIoHandler.java:50)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain$TailFilter.messageReceived(DefaultIoFilterChain.java:713)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:793)
      at org.apache.mina.filter.codec.ProtocolCodecFilter$ProtocolDecoderOutputImpl.flush(ProtocolCodecFilter.java:375)
      at org.apache.mina.filter.codec.ProtocolCodecFilter.messageReceived(ProtocolCodecFilter.java:229)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:793)
      at org.apache.mina.filter.logging.LoggingFilter.messageReceived(LoggingFilter.java:176)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:46)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:793)
      at org.apache.mina.core.filterchain.IoFilterAdapter.messageReceived(IoFilterAdapter.java:119)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:434)
      at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:426)
      at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:638)
      at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:598)
      at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:587)
      at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$400(AbstractPollingIoProcessor.java:61)
      at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:969)
      at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64)
      at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:651)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:676)
      at java.lang.Thread.run(Thread.java:595)
      Caused by: java.lang.UnsupportedOperationException: This method is not supported for disconnected objects
      at org.drools.common.DisconnectedWorkingMemoryEntryPoint.retract(DisconnectedWorkingMemoryEntryPoint.java:55)
      at org.drools.reteoo.ReteooWorkingMemory$WorkingMemoryReteExpireAction.execute(ReteooWorkingMemory.java:419)
      at org.drools.common.AbstractWorkingMemory.executeQueuedActions(AbstractWorkingMemory.java:1471)
      … 35 more

      Like

  3. I’m not sure what you mean with:
    “But when you try to insert the fact after expiration time we get following error:”

    What are you executing exactly?

    Can you dig a little bit to find which method is throwing this?

    “Caused by: java.lang.UnsupportedOperationException: This method is not supported for disconnected objects”

    Like

    1. Is stream mode supported in remote GRID environment?

      I have modified drl like:

      declare Person
      @role(event)
      @expires(2m)
      end

      Then in the code:

      Person p1 = new Person(“”,27,Gender.MALE,null);
      ksession.insert(p1);
      ksession.fireAllRules();

      Person p2 = new Person(“”,28,Gender.MALE,null);
      ksession.insert(p2);
      ksession.fireAllRules();

      //Sleep for 2 min
      Thread.currentThread().sleep(120000);

      After that if I execute

      ksession.fireAllRules();

      I get the above mentioned error.

      That is once the face is detracted (after expiry time) I am getting this error.
      Hope this clarifies your question.

      -Mrunali

      Like

      1. Yes, it is but it seems to be a problem with retraction. It seems that the retract command is complaining about disconnected fact handles. In order to achieve remoting we need to disconnect the fact handle, so probably some tuning is necessary in the remoting implementation.

        Like

  4. Hi, Also I found that if we update the domain object in then part of the rule (set the person name in this example), after firing the rules in client application, the person object is not modified. That is name is not set for person object. However this will work in case of single JVM instance. Is this expected behavior or I am missing something here?

    Like

    1. If you don’t share examples is impossible to see what is going wrong.
      How do you know that the object is not modified? How are you testing that? How your rule looks like?

      Like

  5. I have used the same example as attached to post (drools-grid-mini-demo v2) with little modifications.

    1. Drl file is modified like below:

    ————————————————
    package com.wordpress.salaboy.rules;

    import com.wordpress.salaboy.domain.Person
    import java.util.List

    declare Person
    @role(event)
    @expires(2m)
    end

    rule “Simple validation rule1”
    when
    p: Person(name == “”)
    list : List() from collect( Person(name == “”) )
    then
    System.out.println(“1>>>>> ALERT !!! >>>>> The Person name should contain a value” );
    System.out.println(“2>>>>> ALERT !!! >>>>> Number persons withoud name is: ” + list.size());
    p.setName(“XYZ”);
    System.out.println(“3”);
    end

    ————————————————

    2. RemoteTopologyTest is modified as below:

    a. initializeAndRegisterSession() method changes to use Stream Mode KnowledgeBase

    final KnowledgeBaseConfiguration kbConfig = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
    kbConfig.setOption(EventProcessingOption.STREAM);
    KnowledgeBase kbase = node.get(KnowledgeBaseFactoryService.class).newKnowledgeBase(“simpleValidationKbase”,kbConfig);
    kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

    b. After ksession.fireAllRules() in main method print user name

    Person p1 = new Person(“”,27,Gender.MALE,null);
    ksession.insert(p1);
    ksession.fireAllRules();
    System.err.println(“Person’s name is: ” + p1.getName());

    Let me know if you need more details. I can send the complete source code

    Like

  6. Ok so let’s start simple:

    “when
    p: Person(name == “”)
    list : List() from collect( Person(name == “”) )”

    Are you sure to understand what that rule is doing? Because for me that doesn’t make any sense.

    If in the then part you are changing the person object,if you are working in two different JVMs you will end up with two instances of the Person object, do you understand why right?
    Then using Person as an event is logically wrong as well.

    So I guess that you need to start with simple rules in a single JVM and then move to an environment where remote interactions takes place, because in order to get that working you need to gain a more deep understanding of what is going on.

    Cheers

    Like

  7. No fix will be introduced unless you report a jira with a coherent example that demonstrate a real issue. You have the source code as well, so you can implement the fixes and send them if you want to, thats the great thing about open source 🙂

    Cheers

    Like

  8. Hi,

    Thanks for your inputs. I have following requirement:

    We have cluster setup with two JVMs and we have rules in stream mode.
    As we discussed, this (Drools grid remote service) seem to have issue with retracting objects from working memory after specified expiry time. Can you suggest possible fix for the same or any other alternate approach which we can use for application running in cluster mode.

    Thanks,
    Mrunali

    Like

    1. I’ve already suggested you two approaches:
      1) try with the latest version of grid
      2) download the code and check the method that wasn’t implemented

      Cheers

      Like

      1. Thanks for your reply. What are the other alternate ways which we can use for applications running in cluster mode? Can we use Drools Metrics Persistence? Please clarify.

        Like

      2. Thanks for your reply. What are the other alternate ways which we can use for applications running in cluster mode? Can we use Drools Persistence? Please clarify.

        Like

  9. Hi,
    I have a question about switching databases from the default h2 to Oracle 11. Every example on the web shows how to do this prior to install. Could you please direct me to how to do this after JBPM5 has been installed.

    Thanks

    Like

    1. Sure, jBPM5 is based on JPA (Java Persistence API) for persisting things on a relation database. So you need to find JPA files that are pointing to H2 and changed them to Oracle. You will also need the Oracle driver for java to connect to that database and all the information about your Oracle Database to be able to allow jBPM to log in into that specific database. Once you have all that info you just need to change the configuration files inside the jbpm installer folder to point to your Oracle Database and also you will need to tell JPA to generate the tables in the new schema for you. In order to find the configurations files (persistence.xml) you can use your operating system search capabilities on the jbpm installer directory. I recommend you to get familiar with JPA and Hibernate because your question is only related with those topics and not related with jbpm at all.

      Cheers

      Like

      1. Thank you. I made the changes to the perssistence.xml files and the data source files. However I get the following error:

        Deployment “persistence.unit:unitName=cis-brms-prototype-1.0-SNAPSHOT.war#org.jbpm.task” is in error due to the following reason(s): java.lang.RuntimeException: Specification violation [EJB3 JPA 6.2.1.2] – You have not defined a jta-data-source for a JTA enabled persistence context named: org.jbpm.task
        Deployment “persistence.unit:unitName=cis-brms-prototype-1.0-SNAPSHOT.war#org.jbpm.persistence.jpa” is in error due to the following reason(s): java.lang.ClassNotFoundException: org.jbpm.process.audit.VariableInstanceLog from BaseClassLoader@d2bb53{vfsfile:/opt/sw/jboss/jboss/jboss-eap-5.1.2_brms/jboss-as/server/ceas1/conf/jboss-service.xml}

        Any ideas?

        Like

      2. Hi, the error seems clear, you are defining a JTA enabled persistence unit and you are not defining the JTA data source for it, you need to check that.
        Then you need to make sure that you have the jbpm-audit.jar inside your class path.. make sure that you have that dependency in your project.
        Cheers

        Like

      3. Good evening,
        Thanks for your response. I am now seeing a strange error I ahve never seen before when I am in the process of setting up my Knowledge base for jbpm 5.3. The error is java.lang.NoSuchMethodError: org.mvel2.templates.TemplateCompiler.compileTemplate(Ljava/io/InputStream;)Lorg/mvel2/templates/CompiledTemplate;

        I am using the mvel2 2.1.0.drools16 library. The line that is throwing the exception is

        knowledgeBuilder.add(ResourceFactory.newClassPathResource(“bpm/cisProcess.bpmn”), ResourceType.BPMN2);
        I am using spring 3.1. Also how do you get rid of “The inputoutputSpecification ID must be a valid ID and the “The terminalEventDefinition ID must be a valid ID” from your workflow.

        Thanks

        Like

    1. Found the error thanks. I have one question though. In my workflow I am sending several emails using EmailWorkItemHandler emailHandler = new EmailWorkItemHandler(); in my java code. This works fine, however I was wondering what’s the best way to stop an email from being sent. If, for example, my human task is completed before the email is sent out (don’t send the email). I look through the jbpm manual and on the net but no one really address a graceful way of doing this. Thanks

      Like

      1. Hi Smitty, that’s an usual use case, at least how you explain it. The EmailWorkItemHandler is just a connector to an email server, which means that when it’s called it will send an email. As in your regular email client if you click the send button it will end the email. So unless you implement some kind of mechanism to hold the email back for a period of time to see if a human task was completed that will not happen.
        Can you explain what is your business situation to required that kind of functionality? I’m sure that there should be another ways of achieving the same expected behavior.

        Cheers

        Like

      2. Thank you for your quick reply. I actually have timer on the emails that delay the delivery 1 day (The emails act as a reminder to human task to perform its task). What I want to do is cancel the email delivery if the task is performed prior to the timer allowing the email to be sent.

        Like

  10. That should work. How are you setting the timers for the reminder? You mention the EmailWorkItemHandler, do you have node with that WorkItemHandler associated?
    How your process looks like? Please ask the question in the forums sharing all the details and a test to reproduce the issue.

    Like

    1. The timers are being set in the workflow using a pre-set time delay prior to the email node (1 day reminder, 2 day reminder, etc). I do have a node in the workflow that is associated with EmailWorkItemHandler. The human task is on a separate divergent branch. What I want to know is how to cancel an email (stop it form being sent) prior to timer delays being achieved.

      Like

  11. Can you please be more clear, what do you mean by: “pre-set time delay “. It sounds that you are creating your own mechanism for reminders and you also had modelled that inside your process.
    Another option will be to use on-entry and on-exit actions. Which means that when the task is created the timer is set, so the email is scheduled for one or two days. On the exit action you can cancel the timers so the emails are not sent.

    Like

    1. I’m sorry. I meant for the time event node, in the property “Timer Delay” I set the time. How do you cancel the timers (I looked at the exit action for my human tasks as the first option to solve this , but I don’t know how to cancel the timers). Thanks

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s