Skip to content
Permalink
Browse files

Bug fixes and enhancements for JSON rules

  • Loading branch information...
richturner committed May 11, 2019
1 parent ff41040 commit 3f72885123c7b79510c9b6838023d973d145a0ee
@@ -82,6 +82,7 @@ public RuleActionExecution(Runnable runnable, long delay) {

final TimerService timerService;
boolean notifiedLocationPredicates;
boolean trackUnmatched;
BaseAssetQuery.OrderBy orderBy;
int limit;
RuleCondition<AttributePredicate> attributePredicates = null;
@@ -99,6 +100,7 @@ public RuleActionExecution(Runnable runnable, long delay) {
public RuleTriggerState(RuleTrigger ruleTrigger, boolean trackUnmatched, TimerService timerService) {
this.timerService = timerService;
this.ruleTrigger = ruleTrigger;
this.trackUnmatched = trackUnmatched;

if (trackUnmatched) {
previouslyUnmatchedAssetStates = new HashSet<>();
@@ -141,6 +143,9 @@ public RuleTriggerState(RuleTrigger ruleTrigger, boolean trackUnmatched, TimerSe
}

void updateUnfilteredAssetStates(RulesFacts facts) {
// Clear last trigger to ensure update runs again
lastTriggerResult = null;

if (ruleTrigger.timer != null) {
unfilteredAssetStates = new HashSet<>(facts.getAssetStates());
} else if (ruleTrigger.assets != null) {
@@ -156,8 +161,13 @@ void updateUnfilteredAssetStates(RulesFacts facts) {
}
}


void update() {

// Last trigger is cleared by rule RHS execution
if (lastTriggerResult != null && lastTriggerResult.matches) {
return;
}

if (!TextUtil.isNullOrEmpty(ruleTrigger.timer)) {
lastTriggerResult = null;

@@ -172,7 +182,7 @@ void update() {
if (unfilteredAssetStates.isEmpty()) {
// Maybe assets have been deleted so remove any previous match data
previouslyMatchedAssetStates.clear();
if (previouslyUnmatchedAssetStates != null) {
if (trackUnmatched) {
previouslyUnmatchedAssetStates.clear();
}
log(Level.FINEST, "Rule trigger has no unfiltered asset states so no match");
@@ -182,16 +192,18 @@ void update() {

List<AssetState> matchedAssetStates;
List<AssetState> unmatchedAssetStates = Collections.emptyList();
Collection<String> unmatchedAssetIds = Collections.emptyList();

if (attributePredicates == null) {
matchedAssetStates = new ArrayList<>(unfilteredAssetStates);
} else {
Predicate<AssetState> predicate = AssetQueryPredicate.asPredicate(timerService::getCurrentTimeMillis, attributePredicates);
Map<Boolean,List<AssetState>> results = unfilteredAssetStates.stream().collect(Collectors.groupingBy(predicate::test));
matchedAssetStates = results.get(true);
unmatchedAssetStates = results.get(false);
matchedAssetStates = results.getOrDefault(true, Collections.emptyList());

if (trackUnmatched) {
unmatchedAssetStates = results.getOrDefault(false, Collections.emptyList());

if (previouslyUnmatchedAssetStates != null) {
// Clear out previous unmatched that now match
previouslyUnmatchedAssetStates.removeIf(matchedAssetStates::contains);

@@ -240,29 +252,30 @@ void update() {

// Filter out previous matches to avoid re-triggering
matchedAssetStates.removeIf(assetState -> previouslyMatchedAssetStates.contains(assetState));

// Select unique assets from the states
// Select unique asset states based on asset id
Stream<AssetState> matchedAssetStateStream = matchedAssetStates.stream().filter(distinctByKey(AssetState::getId));
Stream<AssetState> unmatchedAssetStateStream = unmatchedAssetStates.stream().filter(distinctByKey(AssetState::getId));

// Order and limit
// Order asset states before applying limit
if (orderBy != null) {
matchedAssetStateStream = matchedAssetStateStream.sorted(RulesFacts.asComparator(orderBy));
}

if (limit > 0) {
matchedAssetStateStream = matchedAssetStateStream.limit(limit);
}

Collection<String> matchedAssetIds = matchedAssetStateStream.map(AssetState::getId).collect(Collectors.toList());

// Filter out unmatched asset ids that are in the matched list
Collection<String> unmatchedAssetIds = unmatchedAssetStateStream
.filter(assetState -> !matchedAssetIds.contains(assetState.getId()))
.map(AssetState::getId)
.collect(Collectors.toList());

lastTriggerResult = new RuleTriggerResult((!matchedAssetIds.isEmpty() || !unmatchedAssetIds.isEmpty()), matchedAssetStates, matchedAssetIds, unmatchedAssetStates, unmatchedAssetIds);
if (trackUnmatched) {
// Select unique asset states based on asset id
Stream<AssetState> unmatchedAssetStateStream = unmatchedAssetStates.stream().filter(distinctByKey(AssetState::getId));

// Filter out unmatched asset ids that are in the matched list
unmatchedAssetIds = unmatchedAssetStateStream
.filter(assetState -> !matchedAssetIds.contains(assetState.getId()))
.map(AssetState::getId)
.collect(Collectors.toList());
}

lastTriggerResult = new RuleTriggerResult((!matchedAssetIds.isEmpty() || (trackUnmatched && !unmatchedAssetIds.isEmpty())), matchedAssetStates, matchedAssetIds, unmatchedAssetStates, unmatchedAssetIds);
log(Level.FINEST, "Rule trigger result: " + lastTriggerResult);
}

@@ -423,12 +436,19 @@ protected Action buildRhsAction(Rule rule, Map<String, RuleTriggerState> trigger

return facts -> {

log(Level.FINER, "Triggered rule so executing 'then' actions for rule: " + rule.name);
executeRuleActions(rule, rule.then, "then", false, facts, triggerStateMap, assetsFacade, usersFacade, notificationFacade, timerService, assetStorageService, scheduledActionConsumer);
try {
log(Level.FINER, "Triggered rule so executing 'then' actions for rule: " + rule.name);
executeRuleActions(rule, rule.then, "then", false, facts, triggerStateMap, assetsFacade, usersFacade, notificationFacade, timerService, assetStorageService, scheduledActionConsumer);

if (rule.otherwise != null) {
log(Level.FINER, "Triggered rule so executing 'otherwise' actions for rule: " + rule.name);
executeRuleActions(rule, rule.otherwise, "otherwise", true, facts, triggerStateMap, assetsFacade, usersFacade, notificationFacade, timerService, assetStorageService, scheduledActionConsumer);
if (rule.otherwise != null) {
log(Level.FINER, "Triggered rule so executing 'otherwise' actions for rule: " + rule.name);
executeRuleActions(rule, rule.otherwise, "otherwise", true, facts, triggerStateMap, assetsFacade, usersFacade, notificationFacade, timerService, assetStorageService, scheduledActionConsumer);
}
} catch (Exception e) {
log(Level.SEVERE, "Exception thrown during rule RHS execution", e);
throw e;
} finally {
triggerStateMap.values().forEach(triggerState -> triggerState.lastTriggerResult = null);
}
};
}
@@ -440,26 +460,23 @@ public static void executeRuleActions(Rule rule, RuleAction[] ruleActions, Strin
triggerStateMap.values().forEach(ruleTriggerState -> {
if (ruleTriggerState.lastTriggerResult != null) {

if (!useUnmatched) {
// Remove any stale matched asset states
ruleTriggerState.previouslyMatchedAssetStates.removeAll(ruleTriggerState.lastTriggerResult.matchedAssetStates);
ruleTriggerState.previouslyMatchedAssetStates.addAll(ruleTriggerState.lastTriggerResult.matchedAssetStates);

RuleTriggerReset reset = ruleTriggerState.ruleTrigger.reset;
if (ruleTriggerResetHasTimer(reset)) {
if (reset.timestampChanges) {
ruleTriggerState.lastTriggerResult.matchedAssetStates.forEach(assetState ->
ruleTriggerState.previouslyMatchedExpiryTimes.put(assetState, assetState.getTimestamp()));
} else if (ruleTriggerState.resetDurationMillis > 0) {
ruleTriggerState.lastTriggerResult.matchedAssetStates.forEach(assetState ->
ruleTriggerState.previouslyMatchedExpiryTimes.put(assetState, timerService.getCurrentTimeMillis() + ruleTriggerState.resetDurationMillis));
}
}
} else {
if (ruleTriggerState.previouslyUnmatchedAssetStates != null) {
ruleTriggerState.previouslyUnmatchedAssetStates.addAll(ruleTriggerState.lastTriggerResult.unmatchedAssetStates);
// Remove any stale matched asset states
ruleTriggerState.previouslyMatchedAssetStates.removeAll(ruleTriggerState.lastTriggerResult.matchedAssetStates);
ruleTriggerState.previouslyMatchedAssetStates.addAll(ruleTriggerState.lastTriggerResult.matchedAssetStates);

RuleTriggerReset reset = ruleTriggerState.ruleTrigger.reset;
if (ruleTriggerResetHasTimer(reset)) {
if (reset.timestampChanges) {
ruleTriggerState.lastTriggerResult.matchedAssetStates.forEach(assetState ->
ruleTriggerState.previouslyMatchedExpiryTimes.put(assetState, assetState.getTimestamp()));
} else if (ruleTriggerState.resetDurationMillis > 0) {
ruleTriggerState.lastTriggerResult.matchedAssetStates.forEach(assetState ->
ruleTriggerState.previouslyMatchedExpiryTimes.put(assetState, timerService.getCurrentTimeMillis() + ruleTriggerState.resetDurationMillis));
}
}
if (ruleTriggerState.trackUnmatched) {
ruleTriggerState.previouslyUnmatchedAssetStates.addAll(ruleTriggerState.lastTriggerResult.unmatchedAssetStates);
}
}
});
}
@@ -537,7 +554,7 @@ protected static RuleActionExecution buildRuleActionExecution(Rule rule, RuleAct
targetType = Notification.TargetType.USER;
}

if (ids == null) {
if (ids == null || ids.isEmpty()) {
log(Level.FINEST, "No targets for notification rule action so skipping: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
@@ -559,7 +576,7 @@ protected static RuleActionExecution buildRuleActionExecution(Rule rule, RuleAct
if (!TextUtil.isNullOrEmpty(attributeAction.attributeName)) {
Collection<String> ids = getRuleActionTargetIds(ruleAction.target, useUnmatched, triggerStateMap, assetsFacade, usersFacade);

if (ids == null) {
if (ids == null || ids.isEmpty()) {
log(Level.FINEST, "No targets for write attribute rule action so skipping: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
@@ -53,15 +53,19 @@
"action": "write-attribute",
"target": {
"assets": {
"parent": {
"type": "urn:openremote:asset:residence",
"name": "Apartment 2"
},
"type": {
"predicateType": "string",
"match": "EXACT",
"value": "urn:openremote:asset:room"
}
"parents": [
{
"type": "urn:openremote:asset:residence",
"name": "Apartment 2"
}
],
"types": [
{
"predicateType": "string",
"match": "EXACT",
"value": "urn:openremote:asset:room"
}
]
}
},
"attributeName": "lightSwitch",
@@ -71,15 +75,19 @@
"action": "update-attribute",
"target": {
"assets": {
"parent": {
"type": "urn:openremote:asset:residence",
"name": "Apartment 2"
},
"type": {
"predicateType": "string",
"match": "EXACT",
"value": "urn:openremote:asset:room"
}
"parents": [
{
"type": "urn:openremote:asset:residence",
"name": "Apartment 2"
}
],
"types": [
{
"predicateType": "string",
"match": "EXACT",
"value": "urn:openremote:asset:room"
}
]
}
},
"attributeName": "lightSwitchTriggerTimes",
@@ -90,15 +98,19 @@
"action": "update-attribute",
"target": {
"assets": {
"parent": {
"type": "urn:openremote:asset:residence",
"name": "Apartment 2"
},
"type": {
"predicateType": "string",
"match": "EXACT",
"value": "urn:openremote:asset:room"
}
"parents": [
{
"type": "urn:openremote:asset:residence",
"name": "Apartment 2"
}
],
"types": [
{
"predicateType": "string",
"match": "EXACT",
"value": "urn:openremote:asset:room"
}
]
}
},
"attributeName": "plantsWaterLevels",

0 comments on commit 3f72885

Please sign in to comment.
You can’t perform that action at this time.