MC meets Drools - new Reliance module
alesj Aug 31, 2007 2:28 PMThe idea started from an endless debate regarding Drool vs. jBPM. :-)
Thinking about the JIRA issue of being able to create your own dependency
+ listening to MarkP here @ Farnborough, I hacked the next stuff over the weekends + some details now during the visit.
The basic idea is to define a set of rules for certain bean.
And than add dependency items based on those rules.
There is a RuleDependencyMapper - aop-lifecycle bean - that matches the beans and applies the
dependency item created from the DependencyItemFactory.
The rules are held in a Drools StatefulSession (WorkingMemory - WM), and I applied JBossAOP interceptors
for all CRUD methods (insert, update, retract) on the WM instance, so that WM.fireAllRules() method is
transperently invoked. Basically meaning any change to WM can lead to some rules dependencies being resolved.
Here I still need to find a way to transperently push forward contexts that have been resolved by this WM invocation.
I also added a few ways to aviod fireAllRules for any CRUD. You can set an @DisableFireAllRules on a method, and a ThreadLocal
flag will be set which will prevent FireAllRulesAdvice to fire.
Or you can set @FireAllRulesAfter, in which case all invocation of fireAllRules method will be ignored, and the interceptor will
issue the execution at the end of the method.
I've also added a couple of deployers that know how to handle Drools .drl, .dsl, .xsl, .csv files.
All this stuff is pushed into new MC module called reliance (since dependency already exists, I went to webster thesaurus for the name).
When I find some time - and it looks it won't be any time soon :-) - I have a similar idea for jBPM.
I'll wait with commiting this stuff after Scott releases MC beta4.
I still have to write some tests, but this will be done with jBPM idea - after the JBAS5 dust settles.
The concept test case is here:
<deployment xmlns="urn:jboss:bean-deployer:2.0"> <!-- aspects --> <bean name="AspectManager" class="org.jboss.aop.AspectManager"> <constructor factoryClass="org.jboss.aop.AspectManager" factoryMethod="instance"/> </bean> <!-- intercepts any StatefulSession creation from our RuleBase bean --> <aop:aspect xmlns:aop="urn:jboss:aop-beans:1.0" name="WrapStatefulSessionAdvice" class="org.jboss.reliance.drools.core.aspects.StatefulSessionCreationInterceptor" pointcut="execution(* org.jboss.reliance.drools.core.RuleBaseBean->newStatefulSession(..))"/> <!-- invokes fireAllRules after any CRUD method invocation on WorkingMemory instance --> <aop:aspect xmlns:aop="urn:jboss:aop-beans:1.0" name="FireAllRulesCheckAdvice" class="org.jboss.reliance.drools.core.aspects.CRUDAwareAspect" pointcut="execution(* $instanceof{org.drools.StatefulSession}->$implements{org.drools.WorkingMemory}(..))" /> <!-- disables fireAllRules if @DisableFireAllRules is present --> <aop:aspect xmlns:aop="urn:jboss:aop-beans:1.0" name="DisableFireAllRulesAdvice" class="org.jboss.reliance.drools.core.aspects.DisableFireAllRulesInterceptor" pointcut="execution(* @org.jboss.reliance.drools.core.aspects.DisableFireAllRules->*(..)) OR execution(* *->@org.jboss.reliance.drools.core.aspects.DisableFireAllRulesAllRules(..))" /> <!-- only invokes fireAllRules at the end of invocation --> <aop:aspect xmlns:aop="urn:jboss:aop-beans:1.0" name="FireAllRulesAfterInvocationAdvice" class="org.jboss.reliance.drools.core.aspects.FireAllRulesAfterInvocationInterceptor" pointcut="execution(* @org.jboss.reliance.drools.core.aspects.FireAllRulesAfter->*(..)) OR execution(* *->@org.jboss.reliance.drools.core.aspects.FireAllRulesAfterlesAfter(..))"> <constructor> <parameter><inject bean="WorkingMemory" property="statefulSession"/></parameter> </constructor> </aop:aspect> <!-- applies RuleBasedDependencyItem to all @RulesAware beans --> <aop:lifecycle-describe xmlns:aop="urn:jboss:aop-beans:1.0" name="RulesMapper" class="org.jboss.reliance.drools.dependency.RuleDependencyMapper" classes="@org.jboss.reliance.drools.dependency.RulesAware"> <property name="factory"><inject bean="RuleBasedDependencyFactory"/></property> </aop:lifecycle-describe> <bean name="RuleBasedDependencyFactory" class="org.jboss.reliance.drools.dependency.RuleBasedDependencyItemFactory"> <property name="whenRequired">Create</property> </bean> <!-- drools beans --> <bean name="WorkingMemory" class="org.jboss.reliance.drools.core.ManagedWorkingMemory"> <constructor> <parameter><inject bean="RuleBase"/></parameter> <parameter><inject bean="jboss.kernel:service=KernelController"/></parameter> </constructor> <incallback method="addEventListener" signature="org.drools.event.AgendaEventListener"/> <incallback method="addEventListener" signature="org.drools.event.WorkingMemoryEventListener"/> <uncallback method="removeEventListener" signature="org.drools.event.AgendaEventListener"/> <uncallback method="removeEventListener" signature="org.drools.event.WorkingMemoryEventListener"/> </bean> <bean name="debugAgendaEventListener" class="org.drools.event.DebugAgendaEventListener"/> <bean name="debugWorkingMemoryEventListener" class="org.drools.event.DebugWorkingMemoryEventListener"/> <bean name="RuleBase" class="org.jboss.reliance.drools.core.RuleBaseBean"> <demand>packageGenerator</demand> </bean> <bean name="packageGenerator" class="org.jboss.reliance.drools.core.PackageGenerator"> <property name="fromDRL"> <value> package SimplePermissions; import org.jboss.reliance.drools.core.security.PermissionCheck; import org.jboss.reliance.drools.core.security.RoleTracker; import org.jboss.reliance.drools.core.security.Role; import org.jboss.reliance.drools.core.rules.Flag; global org.jboss.dependency.spi.Controller controller; rule TesterCanBeCreated when c: PermissionCheck(name == "tester", action == "Create") Role(name == "admin") then c.grant(); end; rule RoleTrackerAddition salience 10 when Role(name == "admin") then insert( new Flag("admin_flag") ); insertLogical( new RoleTracker("admin") ); end; rule RoleTrackerRemovalTrigger when f : Flag(name == "admin_flag") not( RoleTracker(name == "admin") ) then retract( f ); RoleTracker.change(controller, "tester", "Configured"); end; </value> </property> </bean> <bean name="configuration" class="org.jboss.reliance.identity.Configuration"/> <bean name="identity" class="org.jboss.reliance.drools.core.security.RoleBasedIdentity"> <property name="securityContext"><inject bean="WorkingMemory" property="statefulSession"/></property> <property name="configuration"><inject bean="configuration"/></property> </bean> <!-- user beans --> <bean name="roles" class="org.jboss.test.reliance.drools.support.RolesAdapter"> <constructor> <parameter><inject bean="identity"/></parameter> </constructor> <property name="username">alesj</property> <property name="password">qwert</property> <property name="roles"> <set elementClass="java.lang.String"> <value>guest</value> </set> </property> </bean> <bean name="tester" class="org.jboss.test.reliance.drools.support.Tester"> <annotation>@org.jboss.reliance.drools.dependency.RulesAware</annotation> <demand state="PreInstall">roles</demand> </bean> </deployment>
and the matching JUnit code
public void testRulesConcept() throws Throwable { // only in Configured state, since there is no admin role present KernelControllerContext testerContext = getControllerContext("tester", ControllerState.CONFIGURED); assertEquals(ControllerState.CONFIGURED, testerContext.getState()); RolesAdapter rolesAdapter = (RolesAdapter)getBean("roles"); rolesAdapter.addRole("admin"); // should be Installed now, since admin role is present change(testerContext, ControllerState.INSTALLED); assertEquals(ControllerState.INSTALLED, testerContext.getState()); rolesAdapter.removeRole("admin"); // should be unwinded to Configured, since the admin role was removed assertEquals(ControllerState.CONFIGURED, testerContext.getState()); KernelControllerContext identityContext = getControllerContext("identity"); change(identityContext, ControllerState.NOT_INSTALLED); // should be uninstalled as well, since it relies on identity assertEquals(ControllerState.NOT_INSTALLED, testerContext.getState()); }