Drools: Strange behaviour of a String global variable - java

The situation is easy. I created a rules file:
package org.domain.rules;
dialect "mvel"
import eu.ohim.fsp.core.configuration.domain.xsd.Section;
global java.lang.String sectionName;
rule "rule 1"
salience 1000
when
Section($name : nameOfTheSection)
eval(sectionName == null)
then
System.out.println("Section: " + $name+ "("+$name.length()+")");
System.out.println("Section Name: " + sectionName + "("+sectionName.length()+")");
System.out.println("Mark Details: " + sectionName.equals(null));
end
And before firing the rules I added the Section object with a valid coreName and the globals:
public void fireInserted(Section section1) {
kstateful.insert(section1);
kstateful.setGlobal("sectionName", new String("markudetails"));
kstateful.fireAllRules();
}
The result is:
Section: markudetails(12)
Section Name: markudetails(12)
Mark Details: false
QUESTION: How can it be possible? in when part is null and in then part is not null!!!

Global vars are not a part of the knowledge base, but a separate channel to push some context into the rule execution. It is not appropriate to use them in a when clause. The exact reason why it was null in your case may be hard to trace, since rule activation is completely decoupled from rule execution. The variable may simply not be bound at when clause evaluation time, but is bound at then clause execution time.
To summarize: don't use globals in a when clause, that's not what they are for.
Your problem has an easy general solution: you can insert a configuration object into the knowledge. That object can have your desired "sectionName" property which you will then find easy to test in a when.
As an aside, it is meaningless to test for object.equals(null) -- this can never produce true. There is also no need to use new String("markudetails"). Instead use just "markudetails".

Related

Drools not working as intended. In when section object status doesn't change. How do I make it update?

Intention is to rule to activate when TradeEvent with specific book name appears, and PanicButtonManager panic mode is enabled:
when
$tradeEvent : TradeEvent(
bookShortName == "FMBTHQLA")
p : PanicButtonManager(
panicModeEnabled)
The thing is that when I change field panicModeEnabled value in drools it's not updated. The field value is constant from time of PanicButtonManager creation. But when I try to "print" it it's working fine:
when
$tradeEvent : TradeEvent(
bookShortName == "FMBTHQLA");
p : PanicButtonManager(
panicModeEnabled)
then
modify ($tradeEvent){
messageCode = "PM003",
message = "HQLA: FMBTHQLA is restricted to HQLA mode. Panic status: " + p.isPanicModeEnabled(),
tradeValidationStatus = STATUS.ERROR
}
This code activated whenever new TradeEvent appears with specific book name, but second condition stays same even tho I change panicMode field. But at the same time in message it prints correct status(it changes).
Why in "when" section evaluation of panicModeEnabled stays same? It keeps value from creation of class PanicButtonManager and doesn't update with change of field, but field prints correctly in message.
How do I make PanicButtonManager evaluate correctly in when section(Not keep state from creation time but update with field update).
From the comments, the state of PanicButtonManager is modified outside of Drools. Therefore to make the changed state visible, you need to call update to trigger a reevaluation of the rules with the new value. The modify is only making the new state of the TradeEvent visible.
The syntax would be like this:
rule "..."
when
// ...
p: PanicButtonManager( panicModeEnabled )
then
// ...
update(p);
end
Note that update will reevaluate all of the rules with the new condition(s), not just the remaining rules in the previous set of matches.

How to keep a value taken from a rule in a variable and reuse it in the ANTLR grammar?

I declared String variables in my grammar, vAttributeName and vClassName, and I assign them with the text values of the tokens : className and attributeName. When I use one of the variables that should normally contain a value in an error alternative so in another rule, it returns a null in the message ... Why do not my variables keep the values? How can I fix that?
grammar TestExpression;
#parser::members {
String vAttributeName;
String vClassName;
}
/* SYNTAX RULES */
textInput : classifierContext ;
classifierContext : 'context' c=className attributeContext {vClassName = $c.text;};
attributeContext : '::' a=attributeName ':' dataType initDefinition {vAttributeName = $a.text;};
initDefinition : 'init' ':' initExpression ;
initExpression : boolExpression
| decimalExpression
| dateTimeExpression
| .+? {notifyErrorListeners("Corriger - "l'attribut "+vAttributeName+" de l'entité "+vClassName+" ne correspond pas");}
I'm trying to parse an expression who describe a class with an attribute that has a false value.
And the message i expected was : "Corriger - l'attribut seat de l'entité Car ne correspond pas".
But the actual message was : "Corriger - l'attribut null de l'entité null ne correspond pas".
If you have a rule like a: b c {action}; and an input that matches that rule, events will happen in the following order:
The rule b is applied and its action is executed if it has one.
The rule c is applied and its action is executed if it has one.
The action action is executed and is able to access the results of the b and c rules (including anything set by their actions, which is why it's important that those have run first).
If you have a: b {action} c; instead, then the action will be executed after b, but before c (and will consequently be unable to access c's result).
So for your code that means that the action of initExpression is run before those of classifierContext and attributeContext and that's why the variables aren't set. You can fix the problem by moving the action, so that it happens directly after the needed part (i.e. className/attributeName) has been parsed.
You could also get rid of the variables altogether and instead get the desired information by getting it from your grandparent contexts.

Built-in string formatting vs string concatenation as logging parameter

I'm using SonarLint that shows me an issue in the following line.
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
Side-note: The method that contains this line might get called quite often.
The description for this issue is
"Preconditions" and logging arguments should not require evaluation
(squid:S2629)
Passing message arguments that require further evaluation into a Guava
com.google.common.base.Preconditions check can result in a performance
penalty. That's because whether or not they're needed, each argument
must be resolved before the method is actually called.
Similarly, passing concatenated strings into a logging method can also
incur a needless performance hit because the concatenation will be
performed every time the method is called, whether or not the log
level is low enough to show the message.
Instead, you should structure your code to pass static or pre-computed
values into Preconditions conditions check and logging calls.
Specifically, the built-in string formatting should be used instead of
string concatenation, and if the message is the result of a method
call, then Preconditions should be skipped altoghether, and the
relevant exception should be conditionally thrown instead.
Noncompliant Code Example
logger.log(Level.DEBUG, "Something went wrong: " + message); // Noncompliant; string concatenation performed even when log level too high to show DEBUG messages
LOG.error("Unable to open file " + csvPath, e); // Noncompliant
Preconditions.checkState(a > 0, "Arg must be positive, but got " + a); // Noncompliant. String concatenation performed even when a > 0
Preconditions.checkState(condition, formatMessage()); //Noncompliant. formatMessage() invoked regardless of condition
Preconditions.checkState(condition, "message: %s", formatMessage()); // Noncompliant
Compliant Solution
logger.log(Level.SEVERE, "Something went wrong: %s", message); // String formatting only applied if needed
logger.log(Level.SEVERE, () -> "Something went wrong: " + message); //since Java 8, we can use Supplier , which will be evaluated lazily
LOG.error("Unable to open file {}", csvPath, e);
if (LOG.isDebugEnabled() { LOG.debug("Unable to open file " + csvPath, e); // this is compliant, because it will not evaluate if log level is above debug. }
Preconditions.checkState(arg > 0, "Arg must be positive, but got %d", a); // String formatting only applied if needed
if (!condition) { throw new IllegalStateException(formatMessage()); // formatMessage() only invoked conditionally }
if (!condition) { throw new IllegalStateException("message: " + formatMessage()); }
I'm not 100% sure whether i understand this right. So why is this really an issue. Especially the part about the performance hit when using string concatenation. Because I often read that string concatenation is faster than formatting it.
EDIT: Maybe someone can explain me the difference between
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
AND
LOGGER.debug("Comparing objects: {} and {}",object1, object2);
is in the background. Because I think the String will get created before it is passed to the method. Right? So for me there is no difference. But obviously I'm wrong because SonarLint is complaining about it
I believe you have your answer there.
Concatenation is calculated beforehand the condition check. So if you call your logging framework 10K times conditionally and all of them evaluates to false, you will be concatenating 10K times with no reason.
Also check this topic. And check Icaro's answer's comments.
Take a look to StringBuilder too.
String concatenation means
LOGGER.info("The program started at " + new Date());
Built in formatting of logger means
LOGGER.info("The program started at {}", new Date());
very good article to understand the difference
http://dba-presents.com/index.php/jvm/java/120-use-the-built-in-formatting-to-construct-this-argument
Consider the below logging statement :
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
what is this 'debug' ?
This is the level of logging statement and not level of the LOGGER.
See, there are 2 levels :
a) one of the logging statement (which is debug here) :
"Comparing objects: " + object1 + " and " + object2
b) One is level of the LOGGER. So, what is the level of LOGGER object :
This also must be defined in the code or in some xml , else it takes level from it's ancestor .
Now why am I telling all this ?
Now the logging statement will be printed (or in more technical term send to its 'appender') if and only if :
Level of logging statement >= Level of LOGGER defined/obtained from somewhere in the code
Possible values of a Level can be
DEBUG < INFO < WARN < ERROR
(There can be few more depending on logging framework)
Now lets come back to question :
"Comparing objects: " + object1 + " and " + object2
will always lead to creation of string even if we find that 'level rule' explained above fails.
However,
LOGGER.debug("Comparing objects: {} and {}",object1, object2);
will only result in string formation if 'level rule explained above' satisfies.
So which is more smarter ?
Consult this url.
First let's understand the problem, then talk about solutions.
We can make it simple, assume the following example
LOGGER.debug("User name is " + userName + " and his email is " + email );
The above logging message string consists of 4 parts
And will require 3 String concatenations to be constructed.
Now, let's go to what is the issue of this logging statement.
Assume our logging level is OFF, which means that we don't interested in logging now.
We can imagine that the String concatenations (slow operation) will be ALWAYS applied and will not consider the logging level.
Wow, after understanding the performance issue, let's talk about the best practice.
Solution 1 (NOT optimal)
Instead of using String concatenations, we can use String Builder
StringBuilder loggingMsgStringBuilder = new StringBuilder();
loggingMsgStringBuilder.append("User name is ");
loggingMsgStringBuilder.append(userName);
loggingMsgStringBuilder.append(" and his email is ");
loggingMsgStringBuilder.append(email );
LOGGER.debug(loggingMsgStringBuilder.toString());
Solution 2 (optimal)
We don't need to construct the logging message before check the debugging level.
So we can pass logging message format and all parts as parameters to the LOGGING engine, then delegate String concatenations operations to it, and according to the logging level, the engine will decide to concatenate or not.
So, It's recommended to use parameterized logging as the following example
LOGGER.debug("User name is {} and his email is {}", userName, email);

Configure checkstyle for method chaining?

Our project contains many statements in the method chaining fluent style:
int totalCount = ((Number) em
.createQuery("select count(up) from UserPermission up where " +
"up.database.id = :dbId and " +
"up.user.id <> :currentUserId ")
.setParameter("dbId", cmd.getDatabaseId())
.setParameter("currentUserId", currentUser.getId())
.getSingleResult())
.intValue();
I've got checkstyle mostly configured to match our existing code style, but now it's failing on these snippets, preferring instead:
int totalCount = ((Number) em
.createQuery("select count(up) from UserPermission up where " +
"up.database.id = :dbId and " +
"up.user.id <> :currentUserId ")
.setParameter("dbId", cmd.getDatabaseId())
.setParameter("currentUserId", currentUser.getId())
.getSingleResult())
.intValue();
Which is totally inappropriate. Is there anyway to configure checkstyle to accept the method chaining style? Is there an alternate tool I can run from maven to enforce this kind of indentation?
I never made this work in Eclipse so we barely use Format Source. In the end it is often best to extend. We tried hard and failed. It was one and half year ago. In the end we use formatting text only in Eclipse by Selecting the line or to preformat before we format by hand.
Usually the formating done by a engineer carries a certain meaning. And so automatic format will never work. Especially if you do something like
public static void myMethod(
int value, String value2, String value3)
If you autoformat this it fails similar to your example.
So feel free to join the club of not using automatic formatting beside as a step before you format it the human way.
with intellij , it can be done by selecting "align when multiline" in case of "method chain calls" so i guess this property is misconfigured in the configurations.

Drools Expert output object in Scala

I'm a novice in both Scala and Drools Expert, and need some help getting information out of a Drools session. I've successfully set up some Scala classes that get manipulated by Drools rules. Now I want to create an object to store a set of output facts for processing outside of Drools. Here's what I've got.
I've got a simple object that stores a numeric result (generated in the RHS of a rule), along with a comment string:
class TestResults {
val results = new MutableList[(Float, String)]()
def add(cost: Float, comment: String) {
results += Tuple2(cost, comment)
}
}
In the DRL file, I have the following:
import my.domain.app.TestResults
global TestResults results
rule "always"
dialect "mvel"
when
//
then
System.out.println("75 (fixed)")
results.add(75, "fixed")
end
When I run the code that includes this, I get the following error:
org.drools.runtime.rule.ConsequenceException: rule: always
at org.drools.runtime.rule.impl.DefaultConsequenceExceptionHandler.handleException(DefaultConsequenceExceptionHandler.java:39)
...
Caused by: [Error: null pointer or function not found: add]
[Near : {... results.add(75, "fixed"); ....}]
^
[Line: 2, Column: 9]
at org.mvel2.optimizers.impl.refl.ReflectiveAccessorOptimizer.getMethod(ReflectiveAccessorOptimizer.java:997)
This looks to me like there's something goofy with my definition of the TestResults object in Scala, such that the Java that Drools compiles down to can't quite see it. Type mismatch, perhaps? I can't figure it out. Any suggestions? Thank you!
You need to initialize your results global variable before executing your session. You can initialize it using:
knowledgeSession.setGlobal("results", new TestResults()))
Try
import my.domain.app.TestResults
global TestResults results
rule "always"
dialect "mvel"
when
//
then
System.out.println("75 (fixed)")
results().add(75.0f, "fixed")
end
My guess is that the types don't line up and the error message is poor. (75 is an Int, wants a Float)
That's right.. and try to add a condition to your rule, so it make more sense (the when part).
The condition evaluation is the most important feature of rule engines, writing rules without conditions doesn't make too much senses.
Cheers

Categories