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
Related
I am trying to write a drools rule that would check if a student has an average of more than 5 at a discipline and i get this error whenever i try to run a static method in the when clause. Moreover, i couldn't find in the drools documentation if it's possible to call a static method in the when section.
This is the rule :
rule "student with at least two grades and average less than 5" salience 8
when
$student : Student(grades.size() >= 2)
$list : ArrayList() from eval(computeAverageGrade($student))
$value : Double() from $list
Boolean(booleanValue() == true) from $value < 5.0
then
System.out.println(2);
student.setValid(false)
end
This is the method's header:
public static List<Double> computeAverageGrade(Student student)
and this is the error i am getting :
Caused by: org.mvel2.PropertyAccessException: [Error: null pointer or function not found: eval]
[Near : {... eval(computeAverageGrade($stud ....}]
^
[Line: 1, Column: 1]
The error explains what's wrong: that's not how you use eval.
Unlike other languages like Javascript, eval doesn't execute arbitary code and return a value: it executes arbitrary code and evaluates a boolean.
You should have declared $list like this:
$list: ArrayList() from computeAverageGrade($student)
$value : Double(this < 5.0) from $list
An example for how you'd use eval, would be to evaluate some sort of truthiness from arbitrary code ... for example like this:
eval( $someValue < someExampleFunctionCall() )
Of course you should always avoid the use of eval because Drools is very good at simplifying conditions for efficient processing, and eval forces it to evaluate your code as-written, thus losing all efficiencies/performance improvements the engine could otherwise give you.
Almost any place you can use eval can be rewritten in a better way -- the example above can, for instance, be rewritten as Double(this > $someValue) from someExampleFunctionCall().
Solved it by replacing ArrayList with Object() and removing eval from the static method call.
found it here : https://docs.drools.org/7.73.0.Final/drools-docs/html_single/index.html#drl-rules-WHEN-con_drl-rules
I've got a streaming Dataflow pipeline, written in Java with BEAM 2.35. It commits data to BigQuery via StorageWriteApi. Initially the code looks like
BigQueryIO.writeTableRows()
.withTimePartitioning(/* some column */)
.withClustering(/* another column */)
.withMethod(BigQueryIO.Write.Method.STORAGE_WRITE_API)
.withTriggeringFrequency(Duration.standardSeconds(30))
.withNumStorageWriteApiStreams(20) // want to make this dynamic
This code runs in different environment eg Dev & Prod. When I deploy in Dev I want 2 StorageWriteApiStreams, in Prod I want 20, and I'm trying to pass/resolve these values at the moment I deploy with a Cloudbuild.
The cloudbuild-dev.yaml looks like
steps:
- lots-of-steps
args:
--numStorageWriteApiStreams=${_NUM_STORAGEWRITEAPI_STREAMS}
substitutions:
_PROJECT: dev-project
_NUM_STORAGEWRITEAPI_STREAMS: '2'
I expose the substitution in the job code with an interface
ValueProvider<String> getNumStorageWriteApiStreams();
void setNumStorageWriteApiStreams(ValueProvider<String> numStorageWriteApiStreams);
I then refactor the writeTableRows() call to invoke getNumStorageWriteApiStreams()
BigQueryIO.writeTableRows()
.withTimePartitioning(/* some column */)
.withClustering(/* another column */)
.withMethod(BigQueryIO.Write.Method.STORAGE_WRITE_API)
.withTriggeringFrequency(Duration.standardSeconds(30))
.withNumStorageWriteApiStreams(Integer.parseInt(String.valueOf(options.getNumStorageWriteApiStreams())))
Now it's dynamic but I get a build failure on account of java.lang.IllegalArgumentException: methods with same signature getNumStorageWriteApiStreams() but incompatible return types: [class java.lang.Integer, interface org.apache.beam.sdk.options.ValueProvider]
My understanding was that Integer.parseInt returns an int, which I want so I can pass it to withNumStorageWriteApiStreams() which requires an int.
I'd appreciate any help I can get here thanks
Turns out BigQueryOptions.java already has a method getNumStorageWriteApiStreams() that returns an Integer. I was unknowingly trying to rewrite it with a different return, oops.
https://github.com/apache/beam/blob/master/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/BigQueryOptions.java#L95-L98
I have a 5 row records in mysql, like
sku:001 seller:A stock:UK margin:10
sku:002 seller:B stock:US margin:5
sku:001 seller:A stock:UK margin:10
sku:001 seller:A stock:UK margin:3
sku:001 seller:A stock:UK margin:7
And I've this rows read into spark and transformed them into
JavaPairRDD<Tuple3<String,String,String>, Map>(<sku,seller,stock>, Map<margin,xxx>).
Seems like works fine until now.
However, When I used the reduceByKey function to sum the margin as the structure like:
JavaPairRDD<Tuple3<String,String,String>, Map>(<sku,seller,stock>, Map<marginSummary, xxx>).
the final result got 2 elements
JavaPairRDD<Tuple3<String,String,String>, Map>(<sku,seller,stock>, Map<margin,xxx>).
JavaPairRDD<Tuple3<String,String,String>, Map>(<sku,seller,stock>, Map<marginSummary, xxx>).
seems like the row2 didn't enter the reduceByKey function body. I was wondering why?
It is expected outcome. func is called only when objects for a single key are merged. If there is only one key, there is no reason to call it.
Unfortunately it looks like you have a bigger problem, which can be inferred from you question. You are trying to change the type of the value in reduceByKey. In general it shouldn't even compile as reduceByKey takes Function2<V,V,V> - input and output types have to be identical.
If you want to change a type, you should use either combineByKey
public <C> JavaPairRDD<K,C> combineByKey(Function<V,C> createCombiner,
Function2<C,V,C> mergeValue,
Function2<C,C,C> mergeCombiners)
or aggregateByKey
public <U> JavaPairRDD<K,U> aggregateByKey(U zeroValue,
Function2<U,V,U> seqFunc,
Function2<U,U,U> combFunc)
Both can change the types and fixed your current problem. Please refer to Java test suite for examples: 1 and 2.
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);
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".