Version 11

    How to invoke rules from a jBPM ActionHandler

    by Jeff Delong

    -


     

    There are several interesting ways JBoss Rules can be invoked from within a jBPM process. One way is through an ActionHandler. This can be used to invoke a rule base at a step in a business process. The following describes a simple example of this.

     

     

    1) Add a Node to the process definition with a "FireRulesActionHandler" on the node-enter event. The following is the source to FireRulesActionHandler (OUTDATED):

     

    package com.jboss.dvd.seam;
    
    import org.drools.PackageIntegrationException;
    import org.drools.RuleBase;
    import org.drools.RuleIntegrationException;
    import org.drools.WorkingMemory;
    import org.drools.compiler.DrlParser;
    import org.drools.compiler.DroolsParserException;
    import org.drools.compiler.PackageBuilder;
    import org.drools.lang.descr.PackageDescr;
    import org.drools.reteoo.RuleBaseImpl;
    import org.drools.rule.InvalidPatternException;
    import org.drools.rule.Package;
    import org.jbpm.context.exe.ContextInstance;
    import org.jbpm.graph.def.ActionHandler;
    import org.jbpm.graph.exe.ExecutionContext;
    import java.io.*;
    import java.util.Iterator;
    import java.util.List;
    
    public class FireRulesActionHandler implements ActionHandler {
    
      private static final long serialVersionUID = 1L;
    
      public List objectNames;
      public static String ruleFile;
    
      /**
       * The FireRulesActionHandler gets variables from the Instance, and asserts
       * them into the Rules Engine and invokes the rules.
       */
      public void execute(ExecutionContext executionContext) throws Exception {
    
        // load up the rulebase
        RuleBase ruleBase = readRule();
        WorkingMemory workingMemory = ruleBase.newWorkingMemory();
    
        // load the data
        Object object = null;
        System.out.println(objectNames.toString());
    
        Iterator iter = objectNames.iterator();
        String objectName = "";
        ContextInstance ci = executionContext.getContextInstance();
        while (iter.hasNext()) {
          objectName = (String) iter.next();
          object = ci.getVariable(objectName);
          System.out.println("object name is: " + objectName);
          // assert the object into the rules engine
          workingMemory.assertObject(object);
        }
        // assert the contextInstance so that it may be used to set results
        workingMemory.assertObject(ci);
        workingMemory.fireAllRules();
    
      }
    
      /**
       * Please note that this is the "low level" rule assembly API.
       */
      private static RuleBase readRule() throws IOException, DroolsParserException, RuleIntegrationException, PackageIntegrationException, InvalidPatternException {
        // read in the source
        Reader reader = new InputStreamReader(FireRulesActionHandler.class.getResourceAsStream(ruleFile));
        DrlParser parser = new DrlParser();
        PackageDescr packageDescr = parser.parse(reader);
    
        // pre build the package
        PackageBuilder builder = new PackageBuilder();
        builder.addPackage(packageDescr);
        Package pkg = builder.getPackage();
    
        // add the package to a rulebase
        RuleBaseImpl ruleBase = new RuleBaseImpl();
        ruleBase.addPackage(pkg);
        return ruleBase;
    
      }
    }
    

     

    2) Set fields on the FireRulesActionHandler in the processDefinition for the ruleFile and objectName. E.g,

     

    <node name="ship">
      <event type="node-enter">
        <action name="Execute Shipping Rules" class="com.jboss.dvd.seam.FireRulesActionHandler">
          <objectNames>
            <element>customer</element>
            <element>purchase</element>
          </objectNames>
          <ruleFile>/Shipper.drl</ruleFile>
        </action>
      </event>
      <transition name="shipped" to="complete" ></transition>
    </node>
    

     

    3) Create a RuleFile, for example /Shipper.drl

     

    package com.jboss.dvd.seam
     
    import com.jboss.dvd.seam.Customer;
    import com.jboss.dvd.seam.Order;
    import org.jbpm.context.exe.ContextInstance;
    
    rule "Determine Shipper"
      when
        Customer( region >= 1 )
        Order( totalAmount >= 20 )
        ci : ContextInstance (id >= 0)
      then
        ci.setVariable("shipper", "FEDX"); 
        System.out.println("ship via FEDX " );
    end
    

     

    4) In the code that starts a processInstance, set whatever objects are required for rules evaluation on the ProcessInstance as variables. If using Seam, this can be accomplished via outjection. For example:

     

    @Out(scope=BUSINESS_PROCESS, required=false)
    Order purchase = null;
    
    @Out(scope=BUSINESS_PROCESS, required=false)
    Customer customer;
    
    ...
    
    @End
    @CreateProcess(definition="OrderManagement")
    public String purchase() {
      try {            
        ...
        customer = order.getCustomer();
        purchase = order;
        ...
    

     

    5) When the process gets to the Node with the "FireRulesActionHandler", it will load the ruleFile, get the object(s) from the contextInstance, assert them into the working memory of the RuleEngine along with the ContextInstance, and execute the rules. The result of the rule execution can be set back on the ProcessInstance

     

     

     

     

     

    How to invoke rules from a jBPM AssignmentHandler

    by Jeff Delong

    -


     

    This is another example of invoking rules from a business process. In this example we use rules and data from the jBPM Identity component to set the actorId on the taskInstance.

     

     

    1) Add a Node to the process definition with a "FireRulesActionHandler" on the node-enter event. The following is the source to RulesAssignmentHandler:

     

    package com.jboss.dvd.seam;
    
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.Reader;
    import java.util.Iterator;
    import java.util.List;
    
    import org.drools.PackageIntegrationException;
    import org.drools.RuleBase;
    import org.drools.RuleIntegrationException;
    import org.drools.WorkingMemory;
    import org.drools.compiler.DrlParser;
    import org.drools.compiler.DroolsParserException;
    import org.drools.compiler.PackageBuilder;
    import org.drools.lang.descr.PackageDescr;
    import org.drools.reteoo.RuleBaseImpl;
    import org.drools.rule.InvalidPatternException;
    import org.drools.rule.Package;
    import org.hibernate.Query;
    import org.hibernate.Session;
    import org.jbpm.context.exe.ContextInstance;
    import org.jbpm.graph.exe.*;
    import org.jbpm.taskmgmt.def.*;
    import org.jbpm.taskmgmt.exe.Assignable;
    import org.jbpm.identity.Entity;
    
    public class RulesAssignmentHandler implements AssignmentHandler {
    
      private static final long serialVersionUID = 1L;
    
         public String group;
         public static String ruleFile;
         public List objectNames;
    
    
      public void assign(Assignable assignable, ExecutionContext executionContext) throws Exception {
          // load up the rulebase
          RuleBase ruleBase = readRule();
          WorkingMemory workingMemory = ruleBase.newWorkingMemory();    
    
          // load the data
          Session s = executionContext.getJbpmContext().getSession();
          assertObjects(getUsers(s), workingMemory);
          assertObjects(getGroupByName(s, group), workingMemory);
          assertObjects(getMemberships(s), workingMemory);
         
          Object object = null;
          System.out.println(objectNames.toString());
          Iterator iter = objectNames.iterator();
          String objectName = "";
          ContextInstance ci = executionContext.getContextInstance();
          while ( iter.hasNext() ) {
               objectName = (String) iter.next();
                 object = ci.getVariable(objectName);
                 
               System.out.println("object name is: " + objectName);
              // assert the object into the rules engine
               workingMemory.assertObject( object );
          }
    
          // assert the assignable so that it may be used to set results
          System.out.println("assignable is: " + assignable);
          
          workingMemory.assertObject(assignable);
          System.out.println("fire all rules: " );
               workingMemory.fireAllRules();  
          } 
    
      
      /**
          * Please note that this is the "low level" rule assembly API.
          */
      private static RuleBase readRule() throws IOException, DroolsParserException, RuleIntegrationException, PackageIntegrationException, InvalidPatternException {
          // read in the source
          Reader reader = new InputStreamReader( FireRulesActionHandler.class.getResourceAsStream( ruleFile ) );
          DrlParser parser = new DrlParser();
          PackageDescr packageDescr = parser.parse( reader );
          
          // pre build the package
          PackageBuilder builder = new PackageBuilder();
          builder.addPackage( packageDescr );
          Package pkg = builder.getPackage();
          
          // add the package to a rulebase
          RuleBaseImpl ruleBase = new RuleBaseImpl();
          ruleBase.addPackage( pkg );
          return ruleBase;
      }
     
      
      private List getUsers(Session session) {
             Query query = session.createQuery(
               "select u " +
               "from org.jbpm.identity.User as u"
             );
             return query.list();
           }
    
      
      private List getGroupByName(Session session, String groupName) {
             Query query = session.createQuery(
               "select g " +
               "from org.jbpm.identity.Group as g " +
               "where g.name = :groupName"
             );
             query.setString("groupName", groupName);
             return query.list();
      }
    
      
      private List getMemberships(Session session) {
             Query query = session.createQuery(
               "select m " +
               "from org.jbpm.identity.Membership as m"
             );
             return query.list();
           }
      
      
      private void assertObjects(List objectList, WorkingMemory workingMemory) {
          Iterator iter = objectList.iterator();
          Entity entity = null;
          while ( iter.hasNext() ) {
               entity = (Entity) iter.next();
               System.out.println("object is: " + entity.getName());
             workingMemory.assertObject( entity );
          }
             
      }
     
    }
    

     

    2) Set fields on the RulesAssignmentHandler in the processDefinition for the ruleFile, objectNames and group. E.g,

     

       <task-node name="approval">
            <task name="approve" description="Review order">
               <assignment class="com.jboss.dvd.seam.RulesAssignmentHandler" >
                         <group>reviewers</group>
                           <objectNames>
                             <element>customer</element>
                             <element>purchase</element>
                        </objectNames>
                          <ruleFile>/Assignment.drl</ruleFile>
                   </assignment>
            </task>
            <transition name="approve" to="ship"></transition>
            <transition name="reject"  to="complete"></transition>
        </task-node>
    

     

    3) Create a RuleFile, for example /Assignment.drl. E.g,

     

    package com.jboss.dvd.seam
     
    import org.jbpm.identity.User;
    import org.jbpm.identity.Group;
    import org.jbpm.identity.Membership;
    import org.jbpm.taskmgmt.exe.Assignable;
    import com.jboss.dvd.seam.Customer;
    import com.jboss.dvd.seam.Order;
    import com.jboss.dvd.seam.Role;
    
    
    rule "Determine Senior Role"
    # in our little example role is based on the amount of the order
         when
              Order( totalAmount >= 500 )
         then
              assert(new Role("senior"));
              System.out.println("assigned role as senior");
              
    end
    
    
    rule "Determine Junior Role"
    # in our little example role is based on the amount of the order
         when
              Order( totalAmount < 500)
         then
              assert(new Role("junior"));
              System.out.println("assigned role as junior");
              
    end
    
    
    rule "Determine Actor"
    # A single group object is asserted, along with all of the membership and user objects
    # we want Memberships for the asserted group and role, and then set the user of that membership
    # Other interesting rules would be to check the user's availability (if we knew their vacation schedule,
    # or their workload: we could assert all of the taskInstances and pick the user (actorId) with the fewest
    # assigned
    
         when
              # role has been determined
              exists Role()
              Role(role_name : roleName)          
              a : Assignable()
              g : Group()
              u : User()
              Membership( group == g , role == role_name, u : user;  )
    
         then
              a.setActorId(u.getName());
              System.out.println("assigned User " + u.getName() );
              
    end
    
    

     

    4) When the process gets to the Node with the "RulesAssignmentHandler", it will load the ruleFile, get the object(s) from the contextInstance, assert them into the working memory of the RuleEngine along with the Users, Memberships, and specified Group from the Identity component, and the TaskInstance (assignable), and execute the rules. The result of the rule execution is to set the actorId on the TaskInstance