This is an automated email from the ASF dual-hosted git repository. dgriffon pushed a commit to branch UNOMI-518-fix-trackedConditions in repository https://gitbox.apache.org/repos/asf/unomi.git
commit a29af149b91c206f9dc2517991f5eaf772f0354a Author: David Griffon <[email protected]> AuthorDate: Thu Oct 21 13:17:46 2021 +0200 UNOMI-518 : allow to use custom parameters in rules with tracked conditions --- .../test/java/org/apache/unomi/itests/BaseIT.java | 2 + .../org/apache/unomi/itests/RuleServiceIT.java | 41 ++++++++++++++ .../test/resources/testClickEventCondition.json | 58 +++++++++++++++++++ .../services/impl/rules/RulesServiceImpl.java | 65 +++++++++++++++------- 4 files changed, 146 insertions(+), 20 deletions(-) diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java index 9f8a2d9..81d28da 100644 --- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java @@ -158,6 +158,8 @@ public abstract class BaseIT { "src/test/resources/testCopyPropertiesWithoutSystemTags.json")), replaceConfigurationFile("data/tmp/testLoginEventCondition.json", new File( "src/test/resources/testLoginEventCondition.json")), + replaceConfigurationFile("data/tmp/testClickEventCondition.json", new File( + "src/test/resources/testClickEventCondition.json")), replaceConfigurationFile("data/tmp/testRuleGroovyAction.json", new File( "src/test/resources/testRuleGroovyAction.json")), replaceConfigurationFile("data/tmp/groovy/UpdateAddressAction.groovy", new File( diff --git a/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java index 7a4488b..e94e88a 100644 --- a/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java @@ -17,9 +17,13 @@ package org.apache.unomi.itests; import org.apache.unomi.api.*; +import org.apache.unomi.api.conditions.Condition; +import org.apache.unomi.api.conditions.ConditionType; import org.apache.unomi.api.rules.Rule; import org.apache.unomi.api.services.EventService; import org.apache.unomi.api.services.RulesService; +import org.apache.unomi.persistence.spi.CustomObjectMapper; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,7 +36,9 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; +import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.util.*; import static org.junit.Assert.*; @@ -176,6 +182,41 @@ public class RuleServiceIT extends BaseIT { return new Event(UUID.randomUUID().toString(), "view", session, profile, TEST_SCOPE, sourceItem, targetItem, new Date()); } + @Test + public void testGetTrackedConditions() throws InterruptedException, IOException { + // Add custom condition with parameter + ConditionType conditionType = CustomObjectMapper.getObjectMapper().readValue( + new File("data/tmp/testClickEventCondition.json").toURI().toURL(), ConditionType.class); + definitionsService.setConditionType(conditionType); + refreshPersistence(); + rulesService.refreshRules(); + // Test tracked parameter + // Add rule that has a trackParameter condition that matches + ConditionBuilder builder = new ConditionBuilder(definitionsService); + Rule trackParameterRule = new Rule(new Metadata(TEST_SCOPE, "tracked-parameter-rule", "Tracked parameter rule", "A rule with tracked parameter")); + Condition trackedCondition = builder.condition("clickEventCondition").build(); + trackedCondition.setParameter("tracked.properties.pageInfo.pagePath", "/test-page.html"); + trackedCondition.getConditionType().getMetadata().getSystemTags().add("trackedCondition"); + trackParameterRule.setCondition(trackedCondition); + rulesService.setRule(trackParameterRule); + // Add rule that has a trackParameter condition that does not match + Rule unTrackParameterRule = new Rule(new Metadata(TEST_SCOPE, "not-tracked-parameter-rule", "Not Tracked parameter rule", "A rule that has a parameter not tracked")); + Condition unTrackedCondition = builder.condition("clickEventCondition").build(); + unTrackedCondition.setParameter("tracked.properties.pageInfo.pagePath", "/test-page-that-does-not-exist.html"); + unTrackedCondition.getConditionType().getMetadata().getSystemTags().add("trackedCondition"); + unTrackParameterRule.setCondition(unTrackedCondition); + rulesService.setRule(unTrackParameterRule); + refreshPersistence(); + rulesService.refreshRules(); + // Check that the given event return the tracked condition + Profile profile = new Profile(UUID.randomUUID().toString()); + Session session = new Session(UUID.randomUUID().toString(), profile, new Date(), TEST_SCOPE); + Event viewEvent = generateViewEvent(session, profile); + Set<Condition> trackedConditions = rulesService.getTrackedConditions(viewEvent.getTarget()); + Assert.assertTrue(trackedConditions.contains(trackedCondition)); + Assert.assertFalse(trackedConditions.contains(unTrackedCondition)); + } + @Override public void updateServices() throws InterruptedException { super.updateServices(); diff --git a/itests/src/test/resources/testClickEventCondition.json b/itests/src/test/resources/testClickEventCondition.json new file mode 100644 index 0000000..d99e4b7 --- /dev/null +++ b/itests/src/test/resources/testClickEventCondition.json @@ -0,0 +1,58 @@ +{ + "metadata": { + "id": "clickEventCondition", + "name": "clickEventCondition", + "description": "", + "systemTags": [ + "availableToEndUser", + "behavioral", + "profileTags", + "event", + "condition", + "eventCondition", + "usableInPastEventCondition", + "trackedCondition" + ], + "readOnly": true + }, + "parentCondition": { + "type": "booleanCondition", + "parameterValues": { + "subConditions": [ + { + "type": "eventTypeCondition", + "parameterValues": { + "eventTypeId": "click" + } + }, + { + "type": "sourceEventPropertyCondition", + "parameterValues": { + "path": "parameter::tracked.properties.pageInfo.pagePath" + } + }, + { + "type": "eventPropertyCondition", + "parameterValues": { + "propertyName": "target.itemId", + "propertyValue": "parameter::itemId", + "comparisonOperator": "equals" + } + } + ], + "operator": "and" + } + }, + "parameters": [ + { + "id": "tracked.properties.pageInfo.pagePath", + "type": "string", + "multivalued": false + }, + { + "id": "itemId", + "type": "string", + "multivalued": false + } + ] +} \ No newline at end of file diff --git a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java index 01ae985..8cf3a1f 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java @@ -17,6 +17,7 @@ package org.apache.unomi.services.impl.rules; +import org.apache.commons.lang3.StringUtils; import org.apache.unomi.api.Event; import org.apache.unomi.api.Item; import org.apache.unomi.api.Metadata; @@ -45,6 +46,7 @@ import java.util.concurrent.TimeUnit; public class RulesServiceImpl implements RulesService, EventListenerService, SynchronousBundleListener { public static final String RULE_QUERY_PREFIX = "rule_"; + public static final String TRACKED_PARAMETER_PREFIX = "tracked."; private static final Logger logger = LoggerFactory.getLogger(RulesServiceImpl.class.getName()); private BundleContext bundleContext; @@ -57,14 +59,14 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn private ActionExecutorDispatcher actionExecutorDispatcher; private List<Rule> allRules; - private Map<String,RuleStatistics> allRuleStatistics = new ConcurrentHashMap<>(); + private Map<String, RuleStatistics> allRuleStatistics = new ConcurrentHashMap<>(); private Integer rulesRefreshInterval = 1000; private Integer rulesStatisticsRefreshInterval = 10000; private List<RuleListenerService> ruleListeners = new CopyOnWriteArrayList<RuleListenerService>(); - private Map<String,Set<Rule>> rulesByEventType = new HashMap<>(); + private Map<String, Set<Rule>> rulesByEventType = new HashMap<>(); private Boolean optimizedRulesActivated = true; public void setBundleContext(BundleContext bundleContext) { @@ -282,8 +284,8 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn return rules; } - private Map<String,Set<Rule>> getRulesByEventType(List<Rule> rules) { - Map<String,Set<Rule>> newRulesByEventType = new HashMap<>(); + private Map<String, Set<Rule>> getRulesByEventType(List<Rule> rules) { + Map<String, Set<Rule>> newRulesByEventType = new HashMap<>(); for (Rule rule : rules) { updateRulesByEventType(newRulesByEventType, rule); } @@ -313,7 +315,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn changes |= eventService.send(ruleFired); RuleStatistics ruleStatistics = getLocalRuleStatistics(rule); - ruleStatistics.setLocalExecutionCount(ruleStatistics.getLocalExecutionCount()+1); + ruleStatistics.setLocalExecutionCount(ruleStatistics.getLocalExecutionCount() + 1); ruleStatistics.setLocalActionsTime(ruleStatistics.getLocalActionsTime() + totalActionsTime); this.allRuleStatistics.put(rule.getItemId(), ruleStatistics); } @@ -328,14 +330,14 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn return persistenceService.load(ruleId, RuleStatistics.class); } - public Map<String,RuleStatistics> getAllRuleStatistics() { + public Map<String, RuleStatistics> getAllRuleStatistics() { return allRuleStatistics; } @Override public void resetAllRuleStatistics() { Condition matchAllCondition = new Condition(definitionsService.getConditionType("matchAllCondition")); - persistenceService.removeByQuery(matchAllCondition,RuleStatistics.class); + persistenceService.removeByQuery(matchAllCondition, RuleStatistics.class); allRuleStatistics.clear(); } @@ -348,7 +350,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn } public PartialList<Metadata> getRuleMetadatas(Query query) { - if(query.isForceRefresh()){ + if (query.isForceRefresh()) { persistenceService.refresh(); } definitionsService.resolveConditionType(query.getCondition()); @@ -394,24 +396,47 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn persistenceService.save(rule); } - public Set<Condition> getTrackedConditions(Item source){ + public Set<Condition> getTrackedConditions(Item source) { Set<Condition> trackedConditions = new HashSet<>(); for (Rule r : allRules) { if (!r.getMetadata().isEnabled()) { continue; } - Condition trackedCondition = definitionsService.extractConditionBySystemTag(r.getCondition(), "trackedCondition"); - if(trackedCondition != null){ - Condition sourceEventPropertyCondition = definitionsService.extractConditionBySystemTag(r.getCondition(), "sourceEventCondition"); - if(source != null && sourceEventPropertyCondition != null) { - ParserHelper.resolveConditionType(definitionsService, sourceEventPropertyCondition, "rule " + r.getItemId() + " source event condition"); - if(persistenceService.testMatch(sourceEventPropertyCondition, source)){ + Condition ruleCondition = r.getCondition(); + Condition trackedCondition = definitionsService.extractConditionBySystemTag(ruleCondition, "trackedCondition"); + if (trackedCondition != null) { + Condition evalCondition = definitionsService.extractConditionBySystemTag(ruleCondition, "sourceEventCondition"); + if (evalCondition != null) { + if (persistenceService.testMatch(evalCondition, source)) { trackedConditions.add(trackedCondition); } } else { - trackedConditions.add(trackedCondition); + // lookup for track parameters + Map<String, Object> trackedParameters = new HashMap<>(); + trackedCondition.getParameterValues().forEach((key, value) -> { + if (!TRACKED_PARAMETER_PREFIX.equals(key) && StringUtils.startsWith(key, TRACKED_PARAMETER_PREFIX)) { + trackedParameters.put(StringUtils.substringAfter(key, TRACKED_PARAMETER_PREFIX), value); + } + }); + if (trackedParameters.size() > 0) { + evalCondition = new Condition(definitionsService.getConditionType("booleanCondition")); + evalCondition.setParameter("operator", "and"); + ArrayList<Condition> conditions = new ArrayList<>(); + trackedParameters.forEach((key, value) -> { + Condition propCondition = new Condition(definitionsService.getConditionType("eventPropertyCondition")); + propCondition.setParameter("comparisonOperator", "equals"); + propCondition.setParameter("propertyName", key); + propCondition.setParameter("propertyValue", value); + conditions.add(propCondition); + }); + evalCondition.setParameter("subConditions", conditions); + if (persistenceService.testMatch(evalCondition, source)) { + trackedConditions.add(trackedCondition); + } + } else { + trackedConditions.add(trackedCondition); + } } - } } return trackedConditions; @@ -428,7 +453,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn refreshRules(); } }; - schedulerService.getScheduleExecutorService().scheduleWithFixedDelay(task, 0,rulesRefreshInterval, TimeUnit.MILLISECONDS); + schedulerService.getScheduleExecutorService().scheduleWithFixedDelay(task, 0, rulesRefreshInterval, TimeUnit.MILLISECONDS); TimerTask statisticsTask = new TimerTask() { @Override @@ -456,7 +481,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn private void syncRuleStatistics() { List<RuleStatistics> allPersistedRuleStatisticsList = persistenceService.getAllItems(RuleStatistics.class); - Map<String,RuleStatistics> allPersistedRuleStatistics = new HashMap<>(); + Map<String, RuleStatistics> allPersistedRuleStatistics = new HashMap<>(); for (RuleStatistics ruleStatistics : allPersistedRuleStatisticsList) { allPersistedRuleStatistics.put(ruleStatistics.getItemId(), ruleStatistics); } @@ -543,7 +568,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn } } - private void updateRulesByEventType(Map<String,Set<Rule>> rulesByEventType, Rule rule) { + private void updateRulesByEventType(Map<String, Set<Rule>> rulesByEventType, Rule rule) { Set<String> eventTypeIds = ParserHelper.resolveConditionEventTypes(rule.getCondition()); for (String eventTypeId : eventTypeIds) { Set<Rule> rules = rulesByEventType.get(eventTypeId);
