Commit 6d185f1c01090c3180b7312f1fdfbd258f8ba8ba

Authored by Andrii Shvaika
1 parent c4625177

Improvements to device profile rule node

... ... @@ -469,6 +469,14 @@ class DefaultTbContext implements TbContext {
469 469 return mainCtx.getRuleNodeStateService().save(getTenantId(), state);
470 470 }
471 471
  472 + @Override
  473 + public void clearRuleNodeStates() {
  474 + if (log.isDebugEnabled()) {
  475 + log.debug("[{}][{}] Going to clear rule node states", getTenantId(), getSelfId());
  476 + }
  477 + mainCtx.getRuleNodeStateService().removeByRuleNodeId(getTenantId(), getSelfId());
  478 + }
  479 +
472 480 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
473 481 TbMsgMetaData metaData = new TbMsgMetaData();
474 482 metaData.putValue("ruleNodeId", ruleNodeId.toString());
... ...
... ... @@ -164,6 +164,8 @@ public class RuleChainController extends BaseController {
164 164
165 165 RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
166 166
  167 + tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED);
  168 +
167 169 logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
168 170
169 171 return savedRuleChain;
... ...
... ... @@ -30,4 +30,5 @@ public interface RuleNodeStateService {
30 30
31 31 RuleNodeState save(TenantId tenantId, RuleNodeState ruleNodeState);
32 32
  33 + void removeByRuleNodeId(TenantId tenantId, RuleNodeId selfId);
33 34 }
... ...
... ... @@ -18,5 +18,6 @@ package org.thingsboard.server.common.data.query;
18 18 public enum DynamicValueSourceType {
19 19 CURRENT_TENANT,
20 20 CURRENT_CUSTOMER,
21   - CURRENT_USER
  21 + CURRENT_USER,
  22 + CURRENT_DEVICE
22 23 }
... ...
... ... @@ -68,6 +68,17 @@ public class BaseRuleNodeStateService extends AbstractEntityService implements R
68 68 return saveOrUpdate(tenantId, ruleNodeState, false);
69 69 }
70 70
  71 + @Override
  72 + public void removeByRuleNodeId(TenantId tenantId, RuleNodeId ruleNodeId) {
  73 + if (tenantId == null) {
  74 + throw new DataValidationException("Tenant id should be specified!.");
  75 + }
  76 + if (ruleNodeId == null) {
  77 + throw new DataValidationException("Rule node id should be specified!.");
  78 + }
  79 + ruleNodeStateDao.removeByRuleNodeId(ruleNodeId.getId());
  80 + }
  81 +
71 82 public RuleNodeState saveOrUpdate(TenantId tenantId, RuleNodeState ruleNodeState, boolean update) {
72 83 try {
73 84 if (update) {
... ...
... ... @@ -16,6 +16,8 @@
16 16 package org.thingsboard.server.dao.rule;
17 17
18 18 import org.thingsboard.server.common.data.id.EntityId;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
19 21 import org.thingsboard.server.common.data.page.PageData;
20 22 import org.thingsboard.server.common.data.page.PageLink;
21 23 import org.thingsboard.server.common.data.rule.RuleNodeState;
... ... @@ -31,4 +33,6 @@ public interface RuleNodeStateDao extends Dao<RuleNodeState> {
31 33 PageData<RuleNodeState> findByRuleNodeId(UUID ruleNodeId, PageLink pageLink);
32 34
33 35 RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId);
  36 +
  37 + void removeByRuleNodeId(UUID ruleNodeId);
34 38 }
... ...
... ... @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.stereotype.Component;
  22 +import org.springframework.transaction.annotation.Transactional;
22 23 import org.thingsboard.server.common.data.id.EntityId;
23 24 import org.thingsboard.server.common.data.page.PageData;
24 25 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -56,4 +57,10 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, Rul
56 57 public RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId) {
57 58 return DaoUtil.getData(ruleNodeStateRepository.findByRuleNodeIdAndEntityId(ruleNodeId, entityId));
58 59 }
  60 +
  61 + @Transactional
  62 + @Override
  63 + public void removeByRuleNodeId(UUID ruleNodeId) {
  64 + ruleNodeStateRepository.removeByRuleNodeId(ruleNodeId);
  65 + }
59 66 }
... ...
... ... @@ -33,4 +33,7 @@ public interface RuleNodeStateRepository extends PagingAndSortingRepository<Rule
33 33
34 34 @Query("SELECT e FROM RuleNodeStateEntity e WHERE e.ruleNodeId = :ruleNodeId and e.entityId = :entityId")
35 35 RuleNodeStateEntity findByRuleNodeIdAndEntityId(@Param("ruleNodeId") UUID ruleNodeId, @Param("entityId") UUID entityId);
  36 +
  37 + void removeByRuleNodeId(@Param("ruleNodeId") UUID ruleNodeId);
  38 +
36 39 }
... ...
... ... @@ -221,4 +221,6 @@ public interface TbContext {
221 221 RuleNodeState findRuleNodeStateForEntity(EntityId entityId);
222 222
223 223 RuleNodeState saveRuleNodeState(RuleNodeState state);
  224 +
  225 + void clearRuleNodeStates();
224 226 }
... ...
... ... @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
31 31 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
32 32 import org.thingsboard.server.common.data.query.EntityKey;
33 33 import org.thingsboard.server.common.data.query.EntityKeyType;
  34 +import org.thingsboard.server.common.data.query.FilterPredicateValue;
34 35 import org.thingsboard.server.common.data.query.KeyFilter;
35 36 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
36 37 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
... ... @@ -41,6 +42,7 @@ import java.time.Instant;
41 42 import java.time.ZoneId;
42 43 import java.time.ZonedDateTime;
43 44 import java.util.Set;
  45 +import java.util.function.Function;
44 46
45 47 @Data
46 48 class AlarmRuleState {
... ... @@ -246,38 +248,38 @@ class AlarmRuleState {
246 248 if (value == null) {
247 249 return false;
248 250 }
249   - eval = eval && eval(value, keyFilter.getPredicate());
  251 + eval = eval && eval(data, value, keyFilter.getPredicate());
250 252 }
251 253 return eval;
252 254 }
253 255
254   - private boolean eval(EntityKeyValue value, KeyFilterPredicate predicate) {
  256 + private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) {
255 257 switch (predicate.getType()) {
256 258 case STRING:
257   - return evalStrPredicate(value, (StringFilterPredicate) predicate);
  259 + return evalStrPredicate(data, value, (StringFilterPredicate) predicate);
258 260 case NUMERIC:
259   - return evalNumPredicate(value, (NumericFilterPredicate) predicate);
260   - case COMPLEX:
261   - return evalComplexPredicate(value, (ComplexFilterPredicate) predicate);
  261 + return evalNumPredicate(data, value, (NumericFilterPredicate) predicate);
262 262 case BOOLEAN:
263   - return evalBoolPredicate(value, (BooleanFilterPredicate) predicate);
  263 + return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate);
  264 + case COMPLEX:
  265 + return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate);
264 266 default:
265 267 return false;
266 268 }
267 269 }
268 270
269   - private boolean evalComplexPredicate(EntityKeyValue ekv, ComplexFilterPredicate predicate) {
  271 + private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate) {
270 272 switch (predicate.getOperation()) {
271 273 case OR:
272 274 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
273   - if (eval(ekv, kfp)) {
  275 + if (eval(data, ekv, kfp)) {
274 276 return true;
275 277 }
276 278 }
277 279 return false;
278 280 case AND:
279 281 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
280   - if (!eval(ekv, kfp)) {
  282 + if (!eval(data, ekv, kfp)) {
281 283 return false;
282 284 }
283 285 }
... ... @@ -287,109 +289,55 @@ class AlarmRuleState {
287 289 }
288 290 }
289 291
290   - private boolean evalBoolPredicate(EntityKeyValue ekv, BooleanFilterPredicate predicate) {
291   - Boolean value;
292   - switch (ekv.getDataType()) {
293   - case LONG:
294   - value = ekv.getLngValue() > 0;
295   - break;
296   - case DOUBLE:
297   - value = ekv.getDblValue() > 0;
298   - break;
299   - case BOOLEAN:
300   - value = ekv.getBoolValue();
301   - break;
302   - case STRING:
303   - try {
304   - value = Boolean.parseBoolean(ekv.getStrValue());
305   - break;
306   - } catch (RuntimeException e) {
307   - return false;
308   - }
309   - case JSON:
310   - try {
311   - value = Boolean.parseBoolean(ekv.getJsonValue());
312   - break;
313   - } catch (RuntimeException e) {
314   - return false;
315   - }
316   - default:
317   - return false;
318   - }
319   - if (value == null) {
  292 + private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate) {
  293 + Boolean val = getBoolValue(ekv);
  294 + if (val == null) {
320 295 return false;
321 296 }
  297 + Boolean predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getBoolValue);
322 298 switch (predicate.getOperation()) {
323 299 case EQUAL:
324   - return value.equals(predicate.getValue().getDefaultValue());
  300 + return val.equals(predicateValue);
325 301 case NOT_EQUAL:
326   - return !value.equals(predicate.getValue().getDefaultValue());
  302 + return !val.equals(predicateValue);
327 303 default:
328 304 throw new RuntimeException("Operation not supported: " + predicate.getOperation());
329 305 }
330 306 }
331 307
332   - private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) {
333   - Double value;
334   - switch (ekv.getDataType()) {
335   - case LONG:
336   - value = ekv.getLngValue().doubleValue();
337   - break;
338   - case DOUBLE:
339   - value = ekv.getDblValue();
340   - break;
341   - case BOOLEAN:
342   - value = ekv.getBoolValue() ? 1.0 : 0.0;
343   - break;
344   - case STRING:
345   - try {
346   - value = Double.parseDouble(ekv.getStrValue());
347   - break;
348   - } catch (RuntimeException e) {
349   - return false;
350   - }
351   - case JSON:
352   - try {
353   - value = Double.parseDouble(ekv.getJsonValue());
354   - break;
355   - } catch (RuntimeException e) {
356   - return false;
357   - }
358   - default:
359   - return false;
360   - }
361   - if (value == null) {
  308 + private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate) {
  309 + Double val = getDblValue(ekv);
  310 + if (val == null) {
362 311 return false;
363 312 }
364   -
365   - Double predicateValue = predicate.getValue().getDefaultValue();
  313 + Double predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getDblValue);
366 314 switch (predicate.getOperation()) {
367 315 case NOT_EQUAL:
368   - return !value.equals(predicateValue);
  316 + return !val.equals(predicateValue);
369 317 case EQUAL:
370   - return value.equals(predicateValue);
  318 + return val.equals(predicateValue);
371 319 case GREATER:
372   - return value > predicateValue;
  320 + return val > predicateValue;
373 321 case GREATER_OR_EQUAL:
374   - return value >= predicateValue;
  322 + return val >= predicateValue;
375 323 case LESS:
376   - return value < predicateValue;
  324 + return val < predicateValue;
377 325 case LESS_OR_EQUAL:
378   - return value <= predicateValue;
  326 + return val <= predicateValue;
379 327 default:
380 328 throw new RuntimeException("Operation not supported: " + predicate.getOperation());
381 329 }
382 330 }
383 331
384   - private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) {
385   - String val;
386   - String predicateValue;
  332 + private boolean evalStrPredicate(DataSnapshot data, EntityKeyValue ekv, StringFilterPredicate predicate) {
  333 + String val = getStrValue(ekv);
  334 + if (val == null) {
  335 + return false;
  336 + }
  337 + String predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getStrValue);
387 338 if (predicate.isIgnoreCase()) {
388   - val = ekv.getStrValue().toLowerCase();
389   - predicateValue = predicate.getValue().getDefaultValue().toLowerCase();
390   - } else {
391   - val = ekv.getStrValue();
392   - predicateValue = predicate.getValue().getDefaultValue();
  339 + val = val.toLowerCase();
  340 + predicateValue = predicateValue.toLowerCase();
393 341 }
394 342 switch (predicate.getOperation()) {
395 343 case CONTAINS:
... ... @@ -409,4 +357,99 @@ class AlarmRuleState {
409 357 }
410 358 }
411 359
  360 + private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, Function<EntityKeyValue, T> transformFunction) {
  361 + EntityKeyValue ekv = getDynamicPredicateValue(data, value);
  362 + if (ekv != null) {
  363 + T result = transformFunction.apply(ekv);
  364 + if (result != null) {
  365 + return result;
  366 + }
  367 + }
  368 + return value.getDefaultValue();
  369 + }
  370 +
  371 + private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) {
  372 + EntityKeyValue ekv = null;
  373 + if (value.getDynamicValue() != null) {
  374 + ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
  375 + if (ekv == null) {
  376 + ekv = data.getValue(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
  377 + if (ekv == null) {
  378 + ekv = data.getValue(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
  379 + if (ekv == null) {
  380 + ekv = data.getValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
  381 + }
  382 + }
  383 + }
  384 + }
  385 + return ekv;
  386 + }
  387 +
  388 + private static String getStrValue(EntityKeyValue ekv) {
  389 + switch (ekv.getDataType()) {
  390 + case LONG:
  391 + return ekv.getLngValue() != null ? ekv.getLngValue().toString() : null;
  392 + case DOUBLE:
  393 + return ekv.getDblValue() != null ? ekv.getDblValue().toString() : null;
  394 + case BOOLEAN:
  395 + return ekv.getBoolValue() != null ? ekv.getBoolValue().toString() : null;
  396 + case STRING:
  397 + return ekv.getStrValue();
  398 + case JSON:
  399 + return ekv.getJsonValue();
  400 + default:
  401 + return null;
  402 + }
  403 + }
  404 +
  405 + private static Double getDblValue(EntityKeyValue ekv) {
  406 + switch (ekv.getDataType()) {
  407 + case LONG:
  408 + return ekv.getLngValue() != null ? ekv.getLngValue().doubleValue() : null;
  409 + case DOUBLE:
  410 + return ekv.getDblValue() != null ? ekv.getDblValue() : null;
  411 + case BOOLEAN:
  412 + return ekv.getBoolValue() != null ? (ekv.getBoolValue() ? 1.0 : 0.0) : null;
  413 + case STRING:
  414 + try {
  415 + return Double.parseDouble(ekv.getStrValue());
  416 + } catch (RuntimeException e) {
  417 + return null;
  418 + }
  419 + case JSON:
  420 + try {
  421 + return Double.parseDouble(ekv.getJsonValue());
  422 + } catch (RuntimeException e) {
  423 + return null;
  424 + }
  425 + default:
  426 + return null;
  427 + }
  428 + }
  429 +
  430 + private static Boolean getBoolValue(EntityKeyValue ekv) {
  431 + switch (ekv.getDataType()) {
  432 + case LONG:
  433 + return ekv.getLngValue() != null ? ekv.getLngValue() > 0 : null;
  434 + case DOUBLE:
  435 + return ekv.getDblValue() != null ? ekv.getDblValue() > 0 : null;
  436 + case BOOLEAN:
  437 + return ekv.getBoolValue();
  438 + case STRING:
  439 + try {
  440 + return Boolean.parseBoolean(ekv.getStrValue());
  441 + } catch (RuntimeException e) {
  442 + return null;
  443 + }
  444 + case JSON:
  445 + try {
  446 + return Boolean.parseBoolean(ekv.getJsonValue());
  447 + } catch (RuntimeException e) {
  448 + return null;
  449 + }
  450 + default:
  451 + return null;
  452 + }
  453 + }
  454 +
412 455 }
... ...
... ... @@ -98,6 +98,10 @@ class AlarmState {
98 98 Boolean evalResult = evalFunction.apply(clearState, data);
99 99 if (evalResult) {
100 100 stateUpdate |= clearState.checkUpdate();
  101 + for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
  102 + state.clear();
  103 + stateUpdate |= state.checkUpdate();
  104 + }
101 105 ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
102 106 pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
103 107 currentAlarm = null;
... ... @@ -175,6 +179,8 @@ class AlarmState {
175 179
176 180 private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) {
177 181 if (currentAlarm != null) {
  182 + // 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).
  183 + // Maybe we should fetch alarm every time?
178 184 currentAlarm.setEndTs(System.currentTimeMillis());
179 185 AlarmSeverity oldSeverity = currentAlarm.getSeverity();
180 186 if (!oldSeverity.equals(severity)) {
... ...
... ... @@ -22,8 +22,16 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
22 22 import org.thingsboard.server.common.data.device.profile.AlarmRule;
23 23 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
24 24 import org.thingsboard.server.common.data.id.DeviceProfileId;
  25 +import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
  26 +import org.thingsboard.server.common.data.query.DynamicValue;
  27 +import org.thingsboard.server.common.data.query.DynamicValueSourceType;
25 28 import org.thingsboard.server.common.data.query.EntityKey;
  29 +import org.thingsboard.server.common.data.query.EntityKeyType;
  30 +import org.thingsboard.server.common.data.query.FilterPredicateValue;
26 31 import org.thingsboard.server.common.data.query.KeyFilter;
  32 +import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  33 +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
  34 +import org.thingsboard.server.common.data.query.StringFilterPredicate;
27 35
28 36 import javax.print.attribute.standard.Severity;
29 37 import java.util.Collections;
... ... @@ -65,6 +73,7 @@ class ProfileState {
65 73 for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) {
66 74 entityKeys.add(keyFilter.getKey());
67 75 ruleKeys.add(keyFilter.getKey());
  76 + addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
68 77 }
69 78 }));
70 79 if (alarm.getClearRule() != null) {
... ... @@ -72,12 +81,33 @@ class ProfileState {
72 81 for (KeyFilter keyFilter : alarm.getClearRule().getCondition().getCondition()) {
73 82 entityKeys.add(keyFilter.getKey());
74 83 clearAlarmKeys.add(keyFilter.getKey());
  84 + addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys);
75 85 }
76 86 }
77 87 }
78 88 }
79 89 }
80 90
  91 + private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<EntityKey> entityKeys, Set<EntityKey> ruleKeys) {
  92 + switch (predicate.getType()) {
  93 + case STRING:
  94 + case NUMERIC:
  95 + case BOOLEAN:
  96 + DynamicValue value = ((SimpleKeyFilterPredicate) predicate).getValue().getDynamicValue();
  97 + if (value != null && value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE) {
  98 + EntityKey entityKey = new EntityKey(EntityKeyType.ATTRIBUTE, value.getSourceAttribute());
  99 + entityKeys.add(entityKey);
  100 + ruleKeys.add(entityKey);
  101 + }
  102 + break;
  103 + case COMPLEX:
  104 + for (KeyFilterPredicate child : ((ComplexFilterPredicate) predicate).getPredicates()) {
  105 + addDynamicValuesRecursively(child, entityKeys, ruleKeys);
  106 + }
  107 + break;
  108 + }
  109 + }
  110 +
81 111 DeviceProfileId getProfileId() {
82 112 return deviceProfile.getId();
83 113 }
... ...
... ... @@ -89,6 +89,10 @@ public class TbDeviceProfileNode implements TbNode {
89 89 }
90 90 log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount);
91 91 }
  92 + if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) {
  93 + log.info("[{}] Going to cleanup rule node states", ctx.getSelfId());
  94 + ctx.clearRuleNodeStates();
  95 + }
92 96 }
93 97
94 98 /**
... ...