-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DROOLS-4698] Managing user defined expressions for Collections #2691
Conversation
I think review should be postponed until https://github.com/kiegroup/drools/pull/2690 is in, WDYT @yesamer ? |
jenkins please retest this |
jenkins execute compile downstream build |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
Just some minor changes, pls
@@ -61,7 +62,7 @@ protected boolean isStructuredResult(Class<?> resultClass) { | |||
|
|||
/** | |||
* Check if className represents a structured input | |||
* @param className | |||
* @param className Used to determine if a structured input is passed | |||
* @return | |||
*/ | |||
protected boolean isStructuredInput(String className) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
This method is functionally identical to the previous one, even if written differently: please remove one of them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gitgabrio done! Thanks!
@@ -54,15 +58,34 @@ public ExpressionEvaluator getOrCreate(FactMappingValue factMappingValue) { | |||
return getOrCreateDMNExpressionEvaluator(); | |||
} | |||
|
|||
Object rawValue = factMappingValue.getRawValue(); | |||
String rawValue = (String) factMappingValue.getRawValue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
Not related to this PR, but we have to get rid of this Object
rawValue (is cast to String everywhere)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gitgabrio @danielezonca already planned a future refactor to set rawValue to String
everywhere.
JsonNode jsonNode = objectMapper.readTree(json); | ||
return Optional.of(jsonNode); | ||
} catch (IOException e) { | ||
return Optional.empty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
Not sure about swallowing this exception that way: if we get here rawValue is expected to be a valid json, but if it throws exception, something bad has happened, IMO.
"Given a json in String format, it try to convert it in a JsonNode
. In case of success, i.e.
the given string is a valid json"...: what would mean a "json in String format" but invalid json?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gitgabrio This is a good point, I had the thought, but I didn't find a different way to validate a json.
According to Jackson docs:
Then, I can improve it managing JsonParseException
and IOException
in different ways: in the first case, it's a malformed content i.e. not a JSON, in the second case It could be any other IOException, I can re-throw it with an other exception (which one?)
Consider the JsonParseException
is a sub class of IOException
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
If I've understood correctly, ObjectMapper.readTree(String content)
may throw one or the other (it declares only the latter, being the ancestor).
So, I agree both of them should be catch and managed in different way.
In either case, a specific exception should be re-thrown: I would create twos, to help calling code differentiate the actual issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gitgabrio no, a JsonParseException
is thrown if the input is not a valid json. In this case, I return an empty Optional
which means: the given string can't be parsed as json and then it's not a json. In all other cases, when a generic IOException
is thrown, then I re-throw an IllegalArgumentException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
I thought that method would be invoked only to parse an (expected) json string. If this is true, then IMO JsonParseException
should not be hided behind an Optional.empty()
, because it means something unexpected is happening. If - instead - this method is invoked on any possible String to verify if it is a valid json, then the javadoc should somehow clarify that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If - instead - this method is invoked on any possible String to verify if it is a valid json
This is the correct case, I updated the javadoc to clarify it.
* @param json | ||
* @return | ||
*/ | ||
public static boolean isAJSONTextualNode(String json) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
I'm not sure where it is used, but I'm almost sure you would need to re-convert the string again and retrieve the .isTextual
object: would not be better to directly return an Optional"" here, as in convertFromStringToJSONNode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gitgabrio To be honest it's not used, I removed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yesamer
great!
.../main/java/org/drools/scenariosimulation/backend/expression/AbstractExpressionEvaluator.java
Outdated
Show resolved
Hide resolved
.../main/java/org/drools/scenariosimulation/backend/expression/AbstractExpressionEvaluator.java
Outdated
Show resolved
Hide resolved
@@ -59,7 +62,7 @@ public boolean evaluateUnaryExpression(String rawExpression, Object resultValue, | |||
|
|||
@Override | |||
public Object evaluateLiteralExpression(String rawExpression, String className, List<String> genericClasses) { | |||
Object expressionResult = compileAndExecute(rawExpression, Collections.emptyMap()); | |||
Object expressionResult = compileAndExecute(rawExpression, new HashMap<>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changed has been moved to this PR so we can remove it from here (with the tests of course)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danielezonca I just merged master which now contains that PR.
.../src/main/java/org/drools/scenariosimulation/backend/expression/MVELExpressionEvaluator.java
Show resolved
Hide resolved
@@ -162,9 +163,9 @@ public void mapOfComplexTypeTest() { | |||
element.setPhones(phones); | |||
toCheck.put("first", element); | |||
|
|||
assertTrue(expressionEvaluator.verifyResult(mapJsonString, toCheck)); | |||
assertTrue(expressionEvaluator.verifyResult(mapJsonString, toCheck, null)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a test where the last parameter of verifyResult
is used and it has to be different from null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danielezonca added :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review
See inline comments
* @param rawValue | ||
* @return | ||
*/ | ||
protected boolean isAnMVELExpression(String rawValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is standard public static final helper method, so just wondering if the placement is the best choice we have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jomarko It could be moved to a Util class. On the other side, this method is used only in this class and I don't see another place where it could be used. In my opinon, an Util
class should be created if it cans hold static methods which can be shared in multiple places. This is not the case. I vote to don't change it, if you have an alternative pls share.
|
||
if (rawValue instanceof String && ((String) rawValue).trim().startsWith(MVEL_ESCAPE_SYMBOL)) { | ||
if (isAnMVELExpression(rawValue)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please notice we changed the logic, we are now not checking instanceof
in the isAnMVELExpression
. We automatically cast to String what theoretically can cause cast exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jomarko You're right BUT: rawValue is always a String
, even if it's declared as Object
in FactMappingValue
DTO. If is not a String, it's an error, then it's correct to receive an exception. Consider that we planned with @danielezonca a refactor where we'll change that Object
type in String
--> https://issues.redhat.com/browse/DROOLS-4948
@Test(expected = IllegalArgumentException.class) | ||
public void expressionListVerifyResultTest() { | ||
String expressionCollectionJsonString = new TextNode("10").toString(); | ||
List<BigDecimal> contextValue = Collections.singletonList(BigDecimal.valueOf(10)); | ||
assertTrue(expressionEvaluator.verifyResult(expressionCollectionJsonString, contextValue, List.class)); | ||
} | ||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void expressionMapVerifyResultTest() { | ||
String expressionCollectionJsonString = new TextNode("{key_a : 1}").toString(); | ||
Map<String, BigDecimal> contextValue = Collections.singletonMap("key_a", BigDecimal.valueOf(1)); | ||
assertTrue(expressionEvaluator.verifyResult(expressionCollectionJsonString, contextValue, Map.class)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please help me understand this, why IllegalArgumentException
is expected in both cases? Is it because expressionCollectionJsonString
needs to start with '[' and ends with ']' ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jomarko Yes, the expression collection feature doesn't work with BaseExpressionEvaluator
, but only with MVELExpressionEvaluator
and DMNExpressionEvaluator
. Consider that the frontend, with the current status, can never sends an expression defined collection to be evaluated with BaseExpressionEvaluator
. If happens, it's not expected.
"test.add(\"Michael\");\n" + | ||
"test;"), | ||
ArrayList.class.getCanonicalName(), | ||
new ArrayList<>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we please use Collections.emptyList()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jomarko Done!
|
||
assertEquals(expectedMap, evaluator.evaluateLiteralExpression(mvelExpression("[\"Jim\" : \"Person\"]"), | ||
HashMap.class.getCanonicalName(), | ||
new ArrayList<>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we please use Collections.emptyList()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jomarko Done!
"test.put(\"Jim\", a);\n" + | ||
"test;"), | ||
HashMap.class.getCanonicalName(), | ||
new ArrayList<>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we please use Collections.emptyList()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jomarko Done!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving this part, but before merge I would like to:
- increase coverage DROOLS-4698: Increase coverage yesamer/drools#2 slightly
- Review drools-wb- part of ensemble
- Do manual check
Thank you @yesamer
Kudos, SonarCloud Quality Gate passed! 0 Bugs |
@dupliaka @jomarko @danielezonca can you please review it and test it?
Collection editor currently use a JSON to send collections (Maps, Lists) data to the backend.
Lists are wrapped on JSONArray and Maps on JSONObject
The idea I had to manage used defined expressions are to wrap them to a simple JSONTextual node (i.e. a JSON which contains only a string). This is useful to easily separate the scope of data assigning it to a very specific data structure.
Then, the changes here consist basically to handle this new case ( JSONTextual ) to the already present cases of JSONArray and JSONObject, for all three Evaluators (MVEL, Base and FEEL).
JSONTextual format is:
"expression"
, it wraps a String between quote"
char.https://issues.redhat.com/browse/DROOLS-4698
Related PR: kiegroup/drools-wb#1276