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
Comments