Browse Source

Improvements to device profile rule node

pull/3576/head
Andrii Shvaika 6 years ago
parent
commit
6d185f1c01
  1. 8
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  2. 2
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  3. 1
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateService.java
  4. 3
      common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValueSourceType.java
  5. 11
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java
  6. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateDao.java
  7. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java
  8. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java
  9. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  10. 219
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  11. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  12. 30
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java
  13. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java

8
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -469,6 +469,14 @@ class DefaultTbContext implements TbContext {
return mainCtx.getRuleNodeStateService().save(getTenantId(), state);
}
@Override
public void clearRuleNodeStates() {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Going to clear rule node states", getTenantId(), getSelfId());
}
mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId());
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("ruleNodeId", ruleNodeId.toString());

2
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -164,6 +164,8 @@ public class RuleChainController extends BaseController {
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED);
logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
return savedRuleChain;

1
common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateService.java

@ -30,4 +30,5 @@ public interface RuleNodeStateService {
RuleNodeState save(TenantId tenantId, RuleNodeState ruleNodeState);
void removeByRuleNodeId(TenantId tenantId, RuleNodeId selfId);
}

3
common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValueSourceType.java

@ -18,5 +18,6 @@ package org.thingsboard.server.common.data.query;
public enum DynamicValueSourceType {
CURRENT_TENANT,
CURRENT_CUSTOMER,
CURRENT_USER
CURRENT_USER,
CURRENT_DEVICE
}

11
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleNodeStateService.java

@ -68,6 +68,17 @@ public class BaseRuleNodeStateService extends AbstractEntityService implements R
return saveOrUpdate(tenantId, ruleNodeState, false);
}
@Override
public void removeByRuleNodeId(TenantId tenantId, RuleNodeId ruleNodeId) {
if (tenantId == null) {
throw new DataValidationException("Tenant id should be specified!.");
}
if (ruleNodeId == null) {
throw new DataValidationException("Rule node id should be specified!.");
}
ruleNodeStateDao.removeByRuleNodeId(ruleNodeId.getId());
}
public RuleNodeState saveOrUpdate(TenantId tenantId, RuleNodeState ruleNodeState, boolean update) {
try {
if (update) {

4
dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeStateDao.java

@ -16,6 +16,8 @@
package org.thingsboard.server.dao.rule;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleNodeState;
@ -31,4 +33,6 @@ public interface RuleNodeStateDao extends Dao<RuleNodeState> {
PageData<RuleNodeState> findByRuleNodeId(UUID ruleNodeId, PageLink pageLink);
RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId);
void removeByRuleNodeId(UUID ruleNodeId);
}

7
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -56,4 +57,10 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, Rul
public RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId) {
return DaoUtil.getData(ruleNodeStateRepository.findByRuleNodeIdAndEntityId(ruleNodeId, entityId));
}
@Transactional
@Override
public void removeByRuleNodeId(UUID ruleNodeId) {
ruleNodeStateRepository.removeByRuleNodeId(ruleNodeId);
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java

@ -33,4 +33,7 @@ public interface RuleNodeStateRepository extends PagingAndSortingRepository<Rule
@Query("SELECT e FROM RuleNodeStateEntity e WHERE e.ruleNodeId = :ruleNodeId and e.entityId = :entityId")
RuleNodeStateEntity findByRuleNodeIdAndEntityId(@Param("ruleNodeId") UUID ruleNodeId, @Param("entityId") UUID entityId);
void removeByRuleNodeId(@Param("ruleNodeId") UUID ruleNodeId);
}

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -221,4 +221,6 @@ public interface TbContext {
RuleNodeState findRuleNodeStateForEntity(EntityId entityId);
RuleNodeState saveRuleNodeState(RuleNodeState state);
void clearRuleNodeStates();
}

219
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@ -41,6 +42,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Set;
import java.util.function.Function;
@Data
class AlarmRuleState {
@ -246,38 +248,38 @@ class AlarmRuleState {
if (value == null) {
return false;
}
eval = eval && eval(value, keyFilter.getPredicate());
eval = eval && eval(data, value, keyFilter.getPredicate());
}
return eval;
}
private boolean eval(EntityKeyValue value, KeyFilterPredicate predicate) {
private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
return evalStrPredicate(value, (StringFilterPredicate) predicate);
return evalStrPredicate(data, value, (StringFilterPredicate) predicate);
case NUMERIC:
return evalNumPredicate(value, (NumericFilterPredicate) predicate);
case COMPLEX:
return evalComplexPredicate(value, (ComplexFilterPredicate) predicate);
return evalNumPredicate(data, value, (NumericFilterPredicate) predicate);
case BOOLEAN:
return evalBoolPredicate(value, (BooleanFilterPredicate) predicate);
return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate);
case COMPLEX:
return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate);
default:
return false;
}
}
private boolean evalComplexPredicate(EntityKeyValue ekv, ComplexFilterPredicate predicate) {
private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate) {
switch (predicate.getOperation()) {
case OR:
for (KeyFilterPredicate kfp : predicate.getPredicates()) {
if (eval(ekv, kfp)) {
if (eval(data, ekv, kfp)) {
return true;
}
}
return false;
case AND:
for (KeyFilterPredicate kfp : predicate.getPredicates()) {
if (!eval(ekv, kfp)) {
if (!eval(data, ekv, kfp)) {
return false;
}
}
@ -287,109 +289,55 @@ class AlarmRuleState {
}
}
private boolean evalBoolPredicate(EntityKeyValue ekv, BooleanFilterPredicate predicate) {
Boolean value;
switch (ekv.getDataType()) {
case LONG:
value = ekv.getLngValue() > 0;
break;
case DOUBLE:
value = ekv.getDblValue() > 0;
break;
case BOOLEAN:
value = ekv.getBoolValue();
break;
case STRING:
try {
value = Boolean.parseBoolean(ekv.getStrValue());
break;
} catch (RuntimeException e) {
return false;
}
case JSON:
try {
value = Boolean.parseBoolean(ekv.getJsonValue());
break;
} catch (RuntimeException e) {
return false;
}
default:
return false;
}
if (value == null) {
private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate) {
Boolean val = getBoolValue(ekv);
if (val == null) {
return false;
}
Boolean predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getBoolValue);
switch (predicate.getOperation()) {
case EQUAL:
return value.equals(predicate.getValue().getDefaultValue());
return val.equals(predicateValue);
case NOT_EQUAL:
return !value.equals(predicate.getValue().getDefaultValue());
return !val.equals(predicateValue);
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) {
Double value;
switch (ekv.getDataType()) {
case LONG:
value = ekv.getLngValue().doubleValue();
break;
case DOUBLE:
value = ekv.getDblValue();
break;
case BOOLEAN:
value = ekv.getBoolValue() ? 1.0 : 0.0;
break;
case STRING:
try {
value = Double.parseDouble(ekv.getStrValue());
break;
} catch (RuntimeException e) {
return false;
}
case JSON:
try {
value = Double.parseDouble(ekv.getJsonValue());
break;
} catch (RuntimeException e) {
return false;
}
default:
return false;
}
if (value == null) {
private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate) {
Double val = getDblValue(ekv);
if (val == null) {
return false;
}
Double predicateValue = predicate.getValue().getDefaultValue();
Double predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getDblValue);
switch (predicate.getOperation()) {
case NOT_EQUAL:
return !value.equals(predicateValue);
return !val.equals(predicateValue);
case EQUAL:
return value.equals(predicateValue);
return val.equals(predicateValue);
case GREATER:
return value > predicateValue;
return val > predicateValue;
case GREATER_OR_EQUAL:
return value >= predicateValue;
return val >= predicateValue;
case LESS:
return value < predicateValue;
return val < predicateValue;
case LESS_OR_EQUAL:
return value <= predicateValue;
return val <= predicateValue;
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) {
String val;
String predicateValue;
private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate) {
String val = getStrValue(ekv);
if (val == null) {
return false;
}
String predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getStrValue);
if (predicate.isIgnoreCase()) {
val = ekv.getStrValue().toLowerCase();
predicateValue = predicate.getValue().getDefaultValue().toLowerCase();
} else {
val = ekv.getStrValue();
predicateValue = predicate.getValue().getDefaultValue();
val = val.toLowerCase();
predicateValue = predicateValue.toLowerCase();
}
switch (predicate.getOperation()) {
case CONTAINS:
@ -409,4 +357,99 @@ class AlarmRuleState {
}
}
private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, Function<EntityKeyValue, T> transformFunction) {
EntityKeyValue ekv = getDynamicPredicateValue(data, value);
if (ekv != null) {
T result = transformFunction.apply(ekv);
if (result != null) {
return result;
}
}
return value.getDefaultValue();
}
private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) {
EntityKeyValue ekv = null;
if (value.getDynamicValue() != null) {
ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv == null) {
ekv = data.getValue(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv == null) {
ekv = data.getValue(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
if (ekv == null) {
ekv = data.getValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
}
}
}
}
return ekv;
}
private static String getStrValue(EntityKeyValue ekv) {
switch (ekv.getDataType()) {
case LONG:
return ekv.getLngValue() != null ? ekv.getLngValue().toString() : null;
case DOUBLE:
return ekv.getDblValue() != null ? ekv.getDblValue().toString() : null;
case BOOLEAN:
return ekv.getBoolValue() != null ? ekv.getBoolValue().toString() : null;
case STRING:
return ekv.getStrValue();
case JSON:
return ekv.getJsonValue();
default:
return null;
}
}
private static Double getDblValue(EntityKeyValue ekv) {
switch (ekv.getDataType()) {
case LONG:
return ekv.getLngValue() != null ? ekv.getLngValue().doubleValue() : null;
case DOUBLE:
return ekv.getDblValue() != null ? ekv.getDblValue() : null;
case BOOLEAN:
return ekv.getBoolValue() != null ? (ekv.getBoolValue() ? 1.0 : 0.0) : null;
case STRING:
try {
return Double.parseDouble(ekv.getStrValue());
} catch (RuntimeException e) {
return null;
}
case JSON:
try {
return Double.parseDouble(ekv.getJsonValue());
} catch (RuntimeException e) {
return null;
}
default:
return null;
}
}
private static Boolean getBoolValue(EntityKeyValue ekv) {
switch (ekv.getDataType()) {
case LONG:
return ekv.getLngValue() != null ? ekv.getLngValue() > 0 : null;
case DOUBLE:
return ekv.getDblValue() != null ? ekv.getDblValue() > 0 : null;
case BOOLEAN:
return ekv.getBoolValue();
case STRING:
try {
return Boolean.parseBoolean(ekv.getStrValue());
} catch (RuntimeException e) {
return null;
}
case JSON:
try {
return Boolean.parseBoolean(ekv.getJsonValue());
} catch (RuntimeException e) {
return null;
}
default:
return null;
}
}
}

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -98,6 +98,10 @@ class AlarmState {
Boolean evalResult = evalFunction.apply(clearState, data);
if (evalResult) {
stateUpdate |= clearState.checkUpdate();
for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
state.clear();
stateUpdate |= state.checkUpdate();
}
ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
currentAlarm = null;
@ -175,6 +179,8 @@ class AlarmState {
private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) {
if (currentAlarm != null) {
// TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain).
// Maybe we should fetch alarm every time?
currentAlarm.setEndTs(System.currentTimeMillis());
AlarmSeverity oldSeverity = currentAlarm.getSeverity();
if (!oldSeverity.equals(severity)) {

30
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java

@ -22,8 +22,16 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import javax.print.attribute.standard.Severity;
import java.util.Collections;
@ -65,6 +73,7 @@ class ProfileState {
for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) {
entityKeys.add(keyFilter.getKey());
ruleKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
}
}));
if (alarm.getClearRule() != null) {
@ -72,12 +81,33 @@ class ProfileState {
for (KeyFilter keyFilter : alarm.getClearRule().getCondition().getCondition()) {
entityKeys.add(keyFilter.getKey());
clearAlarmKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys);
}
}
}
}
}
private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<EntityKey> entityKeys, Set<EntityKey> ruleKeys) {
switch (predicate.getType()) {
case STRING:
case NUMERIC:
case BOOLEAN:
DynamicValue value = ((SimpleKeyFilterPredicate) predicate).getValue().getDynamicValue();
if (value != null && value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE) {
EntityKey entityKey = new EntityKey(EntityKeyType.ATTRIBUTE, value.getSourceAttribute());
entityKeys.add(entityKey);
ruleKeys.add(entityKey);
}
break;
case COMPLEX:
for (KeyFilterPredicate child : ((ComplexFilterPredicate) predicate).getPredicates()) {
addDynamicValuesRecursively(child, entityKeys, ruleKeys);
}
break;
}
}
DeviceProfileId getProfileId() {
return deviceProfile.getId();
}

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java

@ -89,6 +89,10 @@ public class TbDeviceProfileNode implements TbNode {
}
log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount);
}
if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) {
log.info("[{}] Going to cleanup rule node states", ctx.getSelfId());
ctx.clearRuleNodeStates();
}
}
/**

Loading…
Cancel
Save