I'm very new to drools but I want to integrate it on my existing project. I'm using Spring MVC framework. I successfully implemented the simple example hello world from the free project of drools. What I want to do now is:
Send a bean to the rules for it to evaluate.
Modify the bean depending on the rules
Send it back to the controller to make a response to the user.
The step 1 I'm already done with it. I was able to insert the bean in rules. What I have for now in my rules is something like this:
global String $test;
rule "Excellent"
when
$m: FLTBean ( listeningScore > 85 )
$p: FLTBean ( listeningScore < 101 )
then
$test = "Excellent";
System.out.println( $test );
end
For step 2 and step 3 I don't know how can I do that. If possible please give me a simple code to be able to do this. I want to have nested rules. With 2 nested rules as an example would be great.
Thanks in advance.
There are a couple of ways you can do this, depending on whether you are using a stateless or stateful session.
rule "Excellent"
no-loop
when
$m: FLTBean ( listeningScore > 85 && listeningScore < 101 )
then
$m.setRating("Excellent")
update( $m )
end
In which case your Java code for a stateless session could be:
FLTBean flt = new FLTBean();
flt.setScore(91);
List<Object> facts = new ArrayList<Object>();
facts.add(flt);
ksession.execute(facts);
System.out.println("Result is " + flt.getRating());
If you are using a stateful session then you can insert facts, fire rules and then query facts out of the working memory. Your rule can insert new facts into the working memory like so:
rule "Excellent"
when
$m: FLTBean ( listeningScore > 85 && listeningScore < 101 )
then
insert( new FLTResult("Excellent") )
end
To get the result back out again, you can use the Drools API to find any objects in the working memory.
/** Provide a reference to the session and the class name
* of the fact you are searching for.
*/
public Collection<Object> findFacts(final StatefulKnowledgeSession session,
final String factClass) {
ObjectFilter filter = new ObjectFilter() {
#Override
public boolean accept(Object object) {
return object.getClass().getSimpleName().equals(factClass);
}
};
Collection<Object> results = session.getObjects(filter);
return results;
}
// And call that like so:
FLTBean flt = new FLTBean();
flt.setScore(91);
ksession.insert(flt);
ksession.fireAllRules();
results = findFacts(ksession, "FLTResult");
One option is to write a query for the bean in order to get it back from the rule engine once the rules have been executed. This could become cumbersome if you have a lot of beans to fetch. The docs show you an examples of this approach.
Another option could be to have global collection where you collect all the beans at the end of rule execution. Just make sure to have the "collect" rule to be low salience, so that it'll be last to execute. This rule would be something like below
rule 'collect results'
salience -500
when
$beans : ArrayList() from collect( MyBean() )
then
someGlobal.setBeans( $beans);
end
In fact, you can probably add directly to a global List if you want. You can also add some conditions with the collect if you don't want all the beans.
Related
I'm working on writing a rule set in Drools and the following situation causes an infinite loop by retriggering the rule:
rule "My Rule"
when
a1:ObjectA()
b1:ObjectB(field1 > 0 || a1.field1 in (1,2,3))
ObjectB(field2 > 10)
then
modify( b1 ) { setField3(5) };
end
The following rule change doesn't result in an infinite loop, i.e., when a1 is no longer referenced within ObjectB:
rule "My Rule"
when
b1:ObjectB(field1 > 0 || field4 in (1,2,3))
ObjectB(field2 > 10)
then
modify( b1 ) { setField3(5) };
end
Another scenario which doesn't cause an infinite loop is when I change the || to an && in the second when line:
rule "My Rule"
when
a1:ObjectA()
b1:ObjectB(field1 > 0 && a1.field1 in (1,2,3))
ObjectB(field2 > 10)
then
modify( b1 ) { setField3(5) };
end
From the Drools docs I understand that calling modify(){} will "trigger a revaluation of all patterns of the matching object type in the knowledge base," but since the field I'm modifying, field3, isn't used in the LHS conditions, I didn't think it should reevaluate. However, I am faily certain it has to do with referencing a1.field1 within ObjectB, but I can't find a concrete reason why. Thanks in advance!
It matters at the object level, not the field level. Since you modify a1, the rule is re-evaluated because it relies on the ObjectA objects in working memory. Note that the docs indicate it will "trigger a reevaluation of all patterns of the matching object type in the knowledge base." Not the parameter values.
One way you could avoid this would be to add a constraint like field3 != 5 on the left hand side. That is:
rule "My Rule"
when
a1: ObjectA( field3 != 5 ) // <-- if field3 is already 5, don't fire the rule
b1: ObjectB(field1 > 0 || a1.field1 in (1,2,3))
exists(ObjectA(field2 > 10))
then
modify( a1 ) { setField3(5) };
end
Basically you need to make the rule no longer eligible for re-fire.
Alternatively, depending on your structure you could try to use no-loop, but that only keeps the rule from refiring when immediately triggered by the right hand side. If you have multiple modifications/updates/etc going on, you could get into a "broader" looping scenario where multiple rules cause reevaluation. (Example: Rule A does a re-evaluate, no-loop keeps A from firing again; then B causes re-eval, so A can trigger because no-loop does not apply.)
Let's suppose we have these classes, which are also Hibernate entities, in a Java EE application:
public class A {
private String code;
private List<B> tests;
}
public class B {
private String code;
private List<C> steps;
}
public class C {
private String code;
private List<D> subSteps;
}
In last years, some Swing windows were created to let configurators users create ad deploy DRools packages rules in order to customize the workflow required by our customers. These windows, in some way, convert Swing components into Drool Mvel text to avoid configurators users writing raw code. These rules are then saved and deployed into their DB tables in BLOB fields and executed when required.
The problem is that now we are required to implement a new hierarchy of facts on which to perform assertions to trigger rules.
Using a fact of class A from my example code, this code is generated from our DRools windows:
rule "RULE_TRY"
dialect "mvel"
salience 10
enabled true
no-loop false
when
$a : A( )
$b : B( code == "testCode" ) from $a.tests
$c : C( code== "stepCode" ) from $a.tests.steps
then
end
It is clear that this rule and facts mirror our DB structure, where "code" is the PK or a FK in corresponding relative tables.
But this code causes a DRools error to be raised when package is compiled and deployed:
Unable to build expression for 'from' : Failed to compile: 1 compilation error(s):
- (1,26) unqualified type in strict mode for: steps'$a.tests.steps' : [Rule name='RULE_TRY']
Maybe, is this the right syntax:
when
$a : A( )
$b : B( code == "testCode" ) from $a
$c : C( code== "stepCode" ) from $b
?
Because, considering Mvel code as a sort of getter/setter, I would expect that a syntax like from $a.tests returns a List<B> and only on a single B-ith element I could eval .steps.
I don't know why rules are converted with that syntax, as there are too many EJBs without comment doing this in our core application, written by people who do not work with us anymore. But before correct them or write new ones for these requirements, I need to know if DRools can support an arbitrary nesting level with java List in facts and how to correctly access them, as it the first time our product has had to support these kinds of facts with many lists in rules.
Our version of DRools is 5.0.1
For the moment, I see that final String in DRL language is created by calling method dump() on class DrlDumper.
I think the syntax you are looking for is:
rule "RULE_TRY"
dialect "mvel"
salience 10
enabled true
no-loop false
when
$a : A( )
$b : B( code == "testCode" ) from $a.tests
$c : C( code== "stepCode" ) from $b.steps
then
//...
end
Hope it helps,
I am developing a rating system based on Drool Rules to replace a old one made with ASP and a relational DB.
Everything is running very good as expected, but some rules become very extensive because the rating system needs to compare a lot of constant values with the input - I do not want to back to the solution of using an external database.
A this moment, there is some standard data structure that should be use to persist lot of constant values? I know it is possible to construct a Java structure for that ... but my objective is to give the rules file to the sales team, who barely understand Java but they are very good with ratios.
For example, I want to replace this with something more clean:
if("A".equals($inpt)) { $outpt = 0.1; }
else if("B".equals($inpt)) { $outpt = 0.2; }
else if("C".equals($inpt)) { $outpt = 0.3; }
I'll have to guess a little. Let's say you have
rule "set output"
when
$s: Something( $input: input )
InToOut( input == $input, $output: output )
then
modify( $s ){ setOutput( $output ) }
end
Your sales team members will surely understand if you give them the skeleton
rule "setInToOut"
salience 999999999
when
then
insert( new InToOut( "A", 0.1 ) );
insert( new InToOut( "B", 0.2 ) );
...
end
You can simplify this with a function.
I am aware that you can create global expressions with Esper's Statement Object Model using CreateExpressionClause and ExpressionDeclaration, but I'm not exactly sure how you are able to refer to their aliases when building an EPStatementObjectModel for a pattern. For example, say I have a pattern like this:
every (a=Event(fizz = 3 and buzz = 5) -> b=Event(fizz = 3 and buzz = 5 and foo = 1 and bar = 2))
I would like to declare fizz = 3 and buzz = 5 as a global expression as such:
create expression fizzbuzz alias for {fizz = 3 and buzz = 5}
Therefore, with EPL I could successfully simplify the pattern to the following:
every (a=Event(fizzbuzz) -> b=Event(fizzbuzz and foo = 1 and bar = 2))
I cannot seem to find a method in any of the classes in com.espertech.esper.client.soda in which I can refer to the global expression alias as I build the statement object. The best thing I could think of that would give me a valid pattern when converting the statement object to EPL would involve Expressions.property(alias), but I get the following error when I add the complete statement object to the Esper engine:
Failed to validate filter expression 'fizzbuzz': Property named 'fizzbuzz' is not valid in any stream [every (a=Event(fizzbuzz) -> b=Event(fizzbuzz and foo = 1 and bar = 2))]
Take note that a) global expressions were already declared at this point, b) If I add the pattern containing the global expression aliases in EPL form to the Esper engine, it works.
Any ideas? While it's an option, I'd prefer not to convert from EPStatementObjectModel to an EPL string everytime I add a new pattern to the engine.
You could inspect a generated object model in a debugger to find out. So in order to generate one, you could call "epadmin.compile("some epl with the expression") and see what comes back.
Following user650839's advice, I found through debugging that the way to go about including the alias to named global expressions is to incorporate a DotExpression into your statement object tree as such:
DotExpression globalExpression = new DotExpression();
globalExpression.add("fizzbuzz", new ArrayList<Expression>(), true);
I've got query that looks something like this:
SomeDomain.executeQuery("""
from
SomeDomain x
where
x.linkToSomeA = coalesce(:a, x.linkToSomeA) and
x.linkToSomeB = coalesce(:b, x.linkToSomeB) and
...
""",
[a: someA, b: someB, ...]
)
The intended behaviour is as follows: once null is supplied for a criterion parameter, this criterion should be ignored. If anything other than null is supplied, the criterion is used.
Everything would work fine, except that Hibernate doesn't allow supplying null where domain class instances are expected. So in the end this is not a valid query (in fact NullPointerException will be thrown if any nulls are supplied).
I haven't found any way to rewrite this without using a ton of if-else. Does anybody have any idea how to make this about equally brief and valid at the same time?
NOTE: The real query is more complicated than that so please, do not suggest rewriting this using anything else than executeQuery.
This is generally for this kind of problem that the Criteria api is used: it allows building a query dynamically:
Criteria c = session.createCriteria(SomeDomain.class, "x");
if (a != null) {
c.add(Restrictions.eq("x.linkToSomeA", a));
}
if (b != null) {
c.add(Restrictions.eq("x.linkToSomeB", b));
}
// ...
This answer doesn't follow your request of not using anything else than executeQuery, but you might also ask how to build a house using nothing else than a screwdriver, and the best answer would still be "buy some other tools").
I'm wondering if you can use a map and findAll(), like this:
def query = [
'a': 'x.linkToSomeA = :a',
'b': 'x.linkToSomeB = :b'
].findAll{ k, q -> params[k] != null }.values().join(' and ')
SomeDomain.executeQuery("""
from
SomeDomain x
where
${query}
""",
params
)
This should return a list of all query elements that have non-null parameters in the params map (this should be changed to match the map used in the query). It's a simple job of filtering out based on the "key" of the main map, then joining the values with and.
Note, there's one potential issue, and that's if there's no params at all. In that case, you'll want to check that query is non-empty or provide a fallback method.