Skip to content

Commit 7cdcd6a

Browse files
Extend flowtest
* add subscription for artifacts based on given requirement * add new JMESPath function that checks if a path consisting of only partial keys and a given value exists
1 parent d744416 commit 7cdcd6a

File tree

14 files changed

+665
-425
lines changed

14 files changed

+665
-425
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# Maven
1515
log/
1616
target/
17+
bin/
1718

1819
# Others
1920
derby.log

docs/index.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11

22
# _Introduction_
33

4-
Eiffel Intelligence is a service that aggregates information from different events of a flow with the purpose of notifying subscribers when content of interest has been collected from desired flow events. The aggregation is stored in a JSON bject. By flow we mean a chain of Eiffel events that are linked together directly or indirectly.
4+
Eiffel Intelligence is a service that aggregates information from different events of a flow with the purpose of notifying subscribers when content of interest has been collected from desired flow events. The aggregation is stored in a JSON object. By flow we mean a chain of Eiffel events that are linked together directly or indirectly.
55

6-
Eiffel Intelligence uses a set of rules to define what information will be extracted from an Eiffel event in the flow and at what location to store this information in the aggregated object. Today only one rule set can be run in each instance and the reason is that in an Eiffel domain with millions of events flowing we will have multiple extractions and checks for each rule set and multiple rule sets in same service will require large machines and more difficult optimization.
6+
Eiffel Intelligence uses a set of rules to define what information will be extracted from an Eiffel event in the flow and at what location to store this information in the aggregated object. Today only one rule set can be run in each instance. The reason is that in an Eiffel domain with millions of events flowing we will have multiple extractions and checks for each rule set. Multiple rule sets in same service instance will require large machines and more difficult optimization.
77

8-
Eiffel intelligence uses subscriptions to notify interested parties when several evens have occurred with desired content in each event aggregated in an aggregated object. Every time an aggregated object is updated we check whether any subscription is fulfilled by the curent state of the aggregated object.
8+
Eiffel intelligence uses subscriptions to notify interested parties when several events have occurred with desired content in each event aggregated in an aggregated object. Every time an aggregated object is updated we check whether any subscription is fulfilled by the current state of the aggregated object.
9+
10+
[Under the hood](under_hood.md)
911

1012
[Under the hood](under_hood.md)
1113

src/functionaltests/java/com/ericsson/ei/query/QueryAggregatedObjectsTestSteps.java

Lines changed: 104 additions & 112 deletions
Large diffs are not rendered by default.

src/functionaltests/resources/queryAggregatedObject7.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
}
1919
return false;
2020
}
21-
}"
21+
}"
2222
},
2323
"filter": "{id: aggregatedObject.id, artifactGav:aggregatedObject.gav, confidenceLevels:aggregatedObject.confidenceLevels[?value=='SUCCESS'].{name: name, value: value}}"
24-
25-
}
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"criteria": {
3+
"object.testCaseExecutions": {
4+
"$elemMatch": {
5+
"outcome.conclusion": "SUCCESSFUL",
6+
"outcome.id": "TC5"
7+
}
8+
},
9+
"$where" : "function() {
10+
var searchKey = 'id';
11+
var value = 'JIRA-1234';
12+
return searchInObj(obj);
13+
function searchInObj(obj){
14+
for (var k in obj){
15+
if (obj[k] == value && k == searchKey) {
16+
return true;
17+
}
18+
19+
if (isObject(obj[k]) && obj[k] !== null) {
20+
if (searchInObj(obj[k])) {
21+
return true;
22+
}
23+
}
24+
}
25+
return false;
26+
}
27+
}"
28+
}
29+
}

src/main/java/com/ericsson/ei/handlers/HistoryExtractionHandler.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.ericsson.ei.jsonmerge.MergePrepare;
2020
import com.ericsson.ei.rules.RulesObject;
2121
import com.fasterxml.jackson.databind.JsonNode;
22+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
2223
import com.github.wnameless.json.flattener.JsonFlattener;
2324

2425
import org.slf4j.Logger;
@@ -151,7 +152,10 @@ private JsonNode extractContent(RulesObject rulesObject, String event) {
151152
*/
152153
private JsonNode getHistoryPathRule(RulesObject rulesObject, String event) {
153154
String rule = rulesObject.getHistoryPathRules();
154-
return jmesPathInterface.runRuleOnEvent(rule, event);
155+
if (rule != null)
156+
return jmesPathInterface.runRuleOnEvent(rule, event);
157+
158+
return JsonNodeFactory.instance.objectNode();
155159
}
156160

157161
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.ericsson.ei.jmespath;
2+
3+
import com.github.wnameless.json.flattener.JsonFlattener;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
import io.burt.jmespath.Adapter;
9+
import io.burt.jmespath.JmesPathType;
10+
import io.burt.jmespath.function.ArgumentConstraints;
11+
import io.burt.jmespath.function.BaseFunction;
12+
import io.burt.jmespath.function.FunctionArgument;
13+
14+
public class IncompletePathContainsFunction extends BaseFunction {
15+
16+
public IncompletePathContainsFunction() {
17+
super(ArgumentConstraints.listOf(ArgumentConstraints.typeOf(JmesPathType.OBJECT),
18+
ArgumentConstraints.typeOf(JmesPathType.STRING), ArgumentConstraints.typeOf(JmesPathType.STRING)));
19+
}
20+
21+
/*
22+
* (non-Javadoc)
23+
*
24+
* @see
25+
* io.burt.jmespath.function.BaseFunction#callFunction(io.burt.jmespath.
26+
* Adapter, java.util.List)
27+
*
28+
* Takes a JSON object and a path with value. The path can contain only
29+
* parts of a path or the entire path but then function is redundant since
30+
* JMESPath can already handle full paths in a JSON object. For example
31+
* paths a.b.c.d.e and b.d.c.d.e are valid for incomplete path c.e since
32+
* both elements of the incomplete path are contained in the example paths.
33+
*
34+
* But the value should also match.
35+
*
36+
* Returns true if there is at least one path that contains the elements of
37+
* the partial path and given value.
38+
*/
39+
@Override
40+
protected <T> T callFunction(Adapter<T> runtime, List<FunctionArgument<T>> arguments) {
41+
T objectArgument = arguments.get(0).value();
42+
T pathArgument = arguments.get(1).value();
43+
T pathValueArgument = arguments.get(2).value();
44+
45+
String object = runtime.toString(objectArgument);
46+
String path = runtime.toString(pathArgument);
47+
String pathValue = runtime.toString(pathValueArgument);
48+
String[] pathPair = path.split(":");
49+
String pathKey = pathPair[0];
50+
String[] pathParts = pathKey.split("\\.");
51+
52+
boolean result = objectContainsIncompletePathWithValue(object, pathParts, pathValue);
53+
54+
return runtime.createBoolean(result);
55+
}
56+
57+
private boolean objectContainsIncompletePathWithValue(String object, String[] pathParts, String pathValue) {
58+
Map<String, Object> flattenJson = JsonFlattener.flattenAsMap(object);
59+
60+
for (Map.Entry<String, Object> entry : flattenJson.entrySet()) {
61+
String entryKey = entry.getKey();
62+
Object entryValue = entry.getValue();
63+
String entryValueString = "";
64+
if (entryValue != null) {
65+
entryValueString = entryValue.toString();
66+
67+
if (entryValueString.equals(pathValue)) {
68+
int lastPosition = getValidKeySequencePosition(pathParts, entryKey);
69+
// all path parts found and in right order
70+
if (lastPosition >= 0)
71+
return true;
72+
}
73+
}
74+
}
75+
76+
return false;
77+
}
78+
79+
private int getLastKeyPosition(int lastPosition, int position, String pathPart) {
80+
if (position > lastPosition) {
81+
return position + pathPart.length();
82+
} else {
83+
// reset to start no complete sequence valid
84+
return -1;
85+
}
86+
}
87+
88+
private int getValidKeySequencePosition(String[] pathParts, String entryKey) {
89+
int index = 0;
90+
int lastPosition = -1;
91+
for (String pathPart : pathParts) {
92+
int position = entryKey.indexOf(pathPart, lastPosition);
93+
if (index > 0) {
94+
// a path part should be followed by a dot in
95+
// the entry key
96+
String subString = entryKey.substring(position - 1, position);
97+
if (!subString.equals(".")) {
98+
lastPosition = -1;
99+
break;
100+
}
101+
}
102+
103+
lastPosition = getLastKeyPosition(lastPosition, position, pathPart);
104+
index++;
105+
}
106+
return lastPosition;
107+
}
108+
}

src/main/java/com/ericsson/ei/jmespath/JmesPathInterface.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class JmesPathInterface {
4242
public JmesPathInterface() {
4343
FunctionRegistry defaultFunctions = FunctionRegistry.defaultRegistry();
4444
FunctionRegistry customFunctions = defaultFunctions.extend(new DiffFunction());
45+
customFunctions = customFunctions.extend(new IncompletePathContainsFunction());
4546
customFunctions = customFunctions.extend(new IncompletePathFilterFunction());
4647
jmespath = new JacksonRuntime(customFunctions);
4748
}

src/main/java/com/ericsson/ei/subscriptionhandler/RunSubscription.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,18 @@
1616
*/
1717
package com.ericsson.ei.subscriptionhandler;
1818

19-
import java.util.HashMap;
19+
import com.ericsson.ei.jmespath.JmesPathInterface;
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.node.ArrayNode;
23+
2024
import java.util.Iterator;
21-
import java.util.concurrent.ConcurrentHashMap;
2225

2326
import org.slf4j.Logger;
2427
import org.slf4j.LoggerFactory;
2528
import org.springframework.beans.factory.annotation.Autowired;
2629
import org.springframework.stereotype.Component;
2730

28-
import com.ericsson.ei.jmespath.JmesPathInterface;
29-
import com.fasterxml.jackson.databind.JsonNode;
30-
import com.fasterxml.jackson.databind.ObjectMapper;
31-
import com.fasterxml.jackson.databind.node.ArrayNode;
32-
3331
/**
3432
* This class represents the mechanism to fetch the rule conditions from the
3533
* Subscription Object and match it with the aggregatedObject to check if it is
@@ -50,9 +48,9 @@ public class RunSubscription {
5048
private SubscriptionRepeatDbHandler subscriptionRepeatDbHandler;
5149

5250
/**
53-
* This method matches every condition specified in the subscription Object
54-
* and if all conditions are matched then only the aggregatedObject is
55-
* eligible for notification via e-mail or REST POST.
51+
* This method matches every condition specified in the subscription Object and
52+
* if all conditions are matched then only the aggregatedObject is eligible for
53+
* notification via e-mail or REST POST.
5654
*
5755
* (AND between conditions in requirements, "OR" between requirements with
5856
* conditions)
@@ -107,12 +105,14 @@ public boolean runSubscriptionOnObject(String aggregatedObject, Iterator<JsonNod
107105
while (conditionIterator.hasNext()) {
108106
String rule = conditionIterator.next().get("jmespath").toString().replaceAll("^\"|\"$", "");
109107
JsonNode result = jmespath.runRuleOnEvent(rule, aggregatedObject);
110-
boolean resultNotEqualsToNull = !result.toString().equals("null");
111-
boolean resultNotEqualsToFalse = !result.toString().equals("false");
112-
boolean resultNotEmpty = !result.toString().equals("[]");
113-
LOGGER.debug("Jmespath rule result: '" + result.toString() + "'\nConditions fullfullment:"
114-
+ "'\nResult not equals to null' is '" + resultNotEqualsToNull
115-
+ " '\nResult not equals to false' is '" + resultNotEqualsToFalse
108+
String resultString = result.toString();
109+
resultString = destringify(resultString);
110+
boolean resultNotEqualsToNull = !resultString.equals("null");
111+
boolean resultNotEqualsToFalse = !resultString.equals("false");
112+
boolean resultNotEmpty = !resultString.equals("");
113+
LOGGER.debug("Jmespath rule result: '" + result.toString() + "'\nConditions fullfullment:"
114+
+ "'\nResult not equals to null' is '" + resultNotEqualsToNull
115+
+ " '\nResult not equals to false' is '" + resultNotEqualsToFalse
116116
+ "' '\nResult not empty' is '" + resultNotEmpty + "'");
117117
if (resultNotEqualsToNull && resultNotEqualsToFalse && resultNotEmpty) {
118118
count_condition_fulfillment++;
@@ -140,4 +140,13 @@ public boolean runSubscriptionOnObject(String aggregatedObject, Iterator<JsonNod
140140

141141
return conditionFulfilled;
142142
}
143+
144+
public static String destringify(String str) {
145+
str = str.replaceAll("\"", "");
146+
str = str.replaceAll("\\{", "");
147+
str = str.replaceAll("\\}", "");
148+
str = str.replaceAll("\\]", "");
149+
str = str.replaceAll("\\[", "");
150+
return str;
151+
}
143152
}

0 commit comments

Comments
 (0)