Commit f1cc4fbd04fc1ff4e477c6b3e7c3de3f6a7d7197

Authored by Igor Kulikov
2 parents 155b967b 6d185f1c

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 19 changed files with 460 additions and 178 deletions
@@ -469,6 +469,14 @@ class DefaultTbContext implements TbContext { @@ -469,6 +469,14 @@ class DefaultTbContext implements TbContext {
469 return mainCtx.getRuleNodeStateService().save(getTenantId(), state); 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 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { 480 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
473 TbMsgMetaData metaData = new TbMsgMetaData(); 481 TbMsgMetaData metaData = new TbMsgMetaData();
474 metaData.putValue("ruleNodeId", ruleNodeId.toString()); 482 metaData.putValue("ruleNodeId", ruleNodeId.toString());
@@ -164,6 +164,8 @@ public class RuleChainController extends BaseController { @@ -164,6 +164,8 @@ public class RuleChainController extends BaseController {
164 164
165 RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName()); 165 RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
166 166
  167 + tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED);
  168 +
167 logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null); 169 logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
168 170
169 return savedRuleChain; 171 return savedRuleChain;
@@ -30,4 +30,5 @@ public interface RuleNodeStateService { @@ -30,4 +30,5 @@ public interface RuleNodeStateService {
30 30
31 RuleNodeState save(TenantId tenantId, RuleNodeState ruleNodeState); 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,5 +18,6 @@ package org.thingsboard.server.common.data.query;
18 public enum DynamicValueSourceType { 18 public enum DynamicValueSourceType {
19 CURRENT_TENANT, 19 CURRENT_TENANT,
20 CURRENT_CUSTOMER, 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,6 +68,17 @@ public class BaseRuleNodeStateService extends AbstractEntityService implements R
68 return saveOrUpdate(tenantId, ruleNodeState, false); 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 public RuleNodeState saveOrUpdate(TenantId tenantId, RuleNodeState ruleNodeState, boolean update) { 82 public RuleNodeState saveOrUpdate(TenantId tenantId, RuleNodeState ruleNodeState, boolean update) {
72 try { 83 try {
73 if (update) { 84 if (update) {
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 package org.thingsboard.server.dao.rule; 16 package org.thingsboard.server.dao.rule;
17 17
18 import org.thingsboard.server.common.data.id.EntityId; 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 import org.thingsboard.server.common.data.page.PageData; 21 import org.thingsboard.server.common.data.page.PageData;
20 import org.thingsboard.server.common.data.page.PageLink; 22 import org.thingsboard.server.common.data.page.PageLink;
21 import org.thingsboard.server.common.data.rule.RuleNodeState; 23 import org.thingsboard.server.common.data.rule.RuleNodeState;
@@ -31,4 +33,6 @@ public interface RuleNodeStateDao extends Dao<RuleNodeState> { @@ -31,4 +33,6 @@ public interface RuleNodeStateDao extends Dao<RuleNodeState> {
31 PageData<RuleNodeState> findByRuleNodeId(UUID ruleNodeId, PageLink pageLink); 33 PageData<RuleNodeState> findByRuleNodeId(UUID ruleNodeId, PageLink pageLink);
32 34
33 RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId); 35 RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId);
  36 +
  37 + void removeByRuleNodeId(UUID ruleNodeId);
34 } 38 }
@@ -472,13 +472,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -472,13 +472,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
472 if (entityFilter.isFetchLastLevelOnly()) { 472 if (entityFilter.isFetchLastLevelOnly()) {
473 String fromOrTo = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "from" : "to"); 473 String fromOrTo = (entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "from" : "to");
474 StringBuilder notExistsPart = new StringBuilder(); 474 StringBuilder notExistsPart = new StringBuilder();
475 - notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr where ") 475 + notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr ")
  476 + .append(whereFilter.replaceAll("re\\.", "nr\\."))
  477 + .append(" and ")
476 .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id") 478 .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id")
477 .append(" and ") 479 .append(" and ")
478 .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); 480 .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type");
479 - if (!StringUtils.isEmpty(entityFilter.getRelationType())) {  
480 - notExistsPart.append(" and nr.relation_type = :where_relation_type");  
481 - } 481 +
482 notExistsPart.append(")"); 482 notExistsPart.append(")");
483 whereFilter += " and ( re.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")"; 483 whereFilter += " and ( re.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")";
484 } 484 }
@@ -551,12 +551,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -551,12 +551,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
551 551
552 StringBuilder notExistsPart = new StringBuilder(); 552 StringBuilder notExistsPart = new StringBuilder();
553 notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr WHERE "); 553 notExistsPart.append(" NOT EXISTS (SELECT 1 from relation nr WHERE ");
554 - notExistsPart.append(whereFilter.toString());  
555 notExistsPart 554 notExistsPart
556 - .append(" and ")  
557 .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id") 555 .append("nr.").append(fromOrTo).append("_id").append(" = re.").append(toOrFrom).append("_id")
558 .append(" and ") 556 .append(" and ")
559 - .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); 557 + .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type")
  558 + .append(" and ")
  559 + .append(whereFilter.toString().replaceAll("re\\.", "nr\\."));
560 560
561 notExistsPart.append(")"); 561 notExistsPart.append(")");
562 whereFilter.append(" and ( re.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")"); 562 whereFilter.append(" and ( re.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")");
@@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
19 import org.springframework.beans.factory.annotation.Autowired; 19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.data.repository.CrudRepository; 20 import org.springframework.data.repository.CrudRepository;
21 import org.springframework.stereotype.Component; 21 import org.springframework.stereotype.Component;
  22 +import org.springframework.transaction.annotation.Transactional;
22 import org.thingsboard.server.common.data.id.EntityId; 23 import org.thingsboard.server.common.data.id.EntityId;
23 import org.thingsboard.server.common.data.page.PageData; 24 import org.thingsboard.server.common.data.page.PageData;
24 import org.thingsboard.server.common.data.page.PageLink; 25 import org.thingsboard.server.common.data.page.PageLink;
@@ -56,4 +57,10 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, Rul @@ -56,4 +57,10 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao<RuleNodeStateEntity, Rul
56 public RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId) { 57 public RuleNodeState findByRuleNodeIdAndEntityId(UUID ruleNodeId, UUID entityId) {
57 return DaoUtil.getData(ruleNodeStateRepository.findByRuleNodeIdAndEntityId(ruleNodeId, entityId)); 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,4 +33,7 @@ public interface RuleNodeStateRepository extends PagingAndSortingRepository<Rule
33 33
34 @Query("SELECT e FROM RuleNodeStateEntity e WHERE e.ruleNodeId = :ruleNodeId and e.entityId = :entityId") 34 @Query("SELECT e FROM RuleNodeStateEntity e WHERE e.ruleNodeId = :ruleNodeId and e.entityId = :entityId")
35 RuleNodeStateEntity findByRuleNodeIdAndEntityId(@Param("ruleNodeId") UUID ruleNodeId, @Param("entityId") UUID entityId); 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,4 +221,6 @@ public interface TbContext {
221 RuleNodeState findRuleNodeStateForEntity(EntityId entityId); 221 RuleNodeState findRuleNodeStateForEntity(EntityId entityId);
222 222
223 RuleNodeState saveRuleNodeState(RuleNodeState state); 223 RuleNodeState saveRuleNodeState(RuleNodeState state);
  224 +
  225 + void clearRuleNodeStates();
224 } 226 }
@@ -29,6 +29,9 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe @@ -29,6 +29,9 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
29 import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; 29 import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
30 import org.thingsboard.server.common.data.query.BooleanFilterPredicate; 30 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
31 import org.thingsboard.server.common.data.query.ComplexFilterPredicate; 31 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
  32 +import org.thingsboard.server.common.data.query.EntityKey;
  33 +import org.thingsboard.server.common.data.query.EntityKeyType;
  34 +import org.thingsboard.server.common.data.query.FilterPredicateValue;
32 import org.thingsboard.server.common.data.query.KeyFilter; 35 import org.thingsboard.server.common.data.query.KeyFilter;
33 import org.thingsboard.server.common.data.query.KeyFilterPredicate; 36 import org.thingsboard.server.common.data.query.KeyFilterPredicate;
34 import org.thingsboard.server.common.data.query.NumericFilterPredicate; 37 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@@ -38,22 +41,25 @@ import org.thingsboard.server.common.msg.tools.SchedulerUtils; @@ -38,22 +41,25 @@ import org.thingsboard.server.common.msg.tools.SchedulerUtils;
38 import java.time.Instant; 41 import java.time.Instant;
39 import java.time.ZoneId; 42 import java.time.ZoneId;
40 import java.time.ZonedDateTime; 43 import java.time.ZonedDateTime;
41 -import java.util.Calendar; 44 +import java.util.Set;
  45 +import java.util.function.Function;
42 46
43 @Data 47 @Data
44 -public class AlarmRuleState { 48 +class AlarmRuleState {
45 49
46 private final AlarmSeverity severity; 50 private final AlarmSeverity severity;
47 private final AlarmRule alarmRule; 51 private final AlarmRule alarmRule;
48 private final AlarmConditionSpec spec; 52 private final AlarmConditionSpec spec;
49 private final long requiredDurationInMs; 53 private final long requiredDurationInMs;
50 private final long requiredRepeats; 54 private final long requiredRepeats;
  55 + private final Set<EntityKey> entityKeys;
51 private PersistedAlarmRuleState state; 56 private PersistedAlarmRuleState state;
52 private boolean updateFlag; 57 private boolean updateFlag;
53 58
54 - public AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, PersistedAlarmRuleState state) { 59 + AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, Set<EntityKey> entityKeys, PersistedAlarmRuleState state) {
55 this.severity = severity; 60 this.severity = severity;
56 this.alarmRule = alarmRule; 61 this.alarmRule = alarmRule;
  62 + this.entityKeys = entityKeys;
57 if (state != null) { 63 if (state != null) {
58 this.state = state; 64 this.state = state;
59 } else { 65 } else {
@@ -76,6 +82,30 @@ public class AlarmRuleState { @@ -76,6 +82,30 @@ public class AlarmRuleState {
76 this.requiredRepeats = requiredRepeats; 82 this.requiredRepeats = requiredRepeats;
77 } 83 }
78 84
  85 + public boolean validateTsUpdate(Set<EntityKey> changedKeys) {
  86 + for (EntityKey key : changedKeys) {
  87 + if (entityKeys.contains(key)) {
  88 + return true;
  89 + }
  90 + }
  91 + return false;
  92 + }
  93 +
  94 + public boolean validateAttrUpdate(Set<EntityKey> changedKeys) {
  95 + //If the attribute was updated, but no new telemetry arrived - we ignore this until new telemetry is there.
  96 + for (EntityKey key : entityKeys) {
  97 + if (key.getType().equals(EntityKeyType.TIME_SERIES)) {
  98 + return false;
  99 + }
  100 + }
  101 + for (EntityKey key : changedKeys) {
  102 + if (entityKeys.contains(key)) {
  103 + return true;
  104 + }
  105 + }
  106 + return false;
  107 + }
  108 +
79 public AlarmConditionSpec getSpec(AlarmRule alarmRule) { 109 public AlarmConditionSpec getSpec(AlarmRule alarmRule) {
80 AlarmConditionSpec spec = alarmRule.getCondition().getSpec(); 110 AlarmConditionSpec spec = alarmRule.getCondition().getSpec();
81 if (spec == null) { 111 if (spec == null) {
@@ -93,7 +123,7 @@ public class AlarmRuleState { @@ -93,7 +123,7 @@ public class AlarmRuleState {
93 } 123 }
94 } 124 }
95 125
96 - public boolean eval(DeviceDataSnapshot data) { 126 + public boolean eval(DataSnapshot data) {
97 boolean active = isActive(data.getTs()); 127 boolean active = isActive(data.getTs());
98 switch (spec.getType()) { 128 switch (spec.getType()) {
99 case SIMPLE: 129 case SIMPLE:
@@ -167,7 +197,7 @@ public class AlarmRuleState { @@ -167,7 +197,7 @@ public class AlarmRuleState {
167 } 197 }
168 } 198 }
169 199
170 - private boolean evalRepeating(DeviceDataSnapshot data, boolean active) { 200 + private boolean evalRepeating(DataSnapshot data, boolean active) {
171 if (active && eval(alarmRule.getCondition(), data)) { 201 if (active && eval(alarmRule.getCondition(), data)) {
172 state.setEventCount(state.getEventCount() + 1); 202 state.setEventCount(state.getEventCount() + 1);
173 updateFlag = true; 203 updateFlag = true;
@@ -177,7 +207,7 @@ public class AlarmRuleState { @@ -177,7 +207,7 @@ public class AlarmRuleState {
177 } 207 }
178 } 208 }
179 209
180 - private boolean evalDuration(DeviceDataSnapshot data, boolean active) { 210 + private boolean evalDuration(DataSnapshot data, boolean active) {
181 if (active && eval(alarmRule.getCondition(), data)) { 211 if (active && eval(alarmRule.getCondition(), data)) {
182 if (state.getLastEventTs() > 0) { 212 if (state.getLastEventTs() > 0) {
183 if (data.getTs() > state.getLastEventTs()) { 213 if (data.getTs() > state.getLastEventTs()) {
@@ -211,45 +241,45 @@ public class AlarmRuleState { @@ -211,45 +241,45 @@ public class AlarmRuleState {
211 } 241 }
212 } 242 }
213 243
214 - private boolean eval(AlarmCondition condition, DeviceDataSnapshot data) { 244 + private boolean eval(AlarmCondition condition, DataSnapshot data) {
215 boolean eval = true; 245 boolean eval = true;
216 for (KeyFilter keyFilter : condition.getCondition()) { 246 for (KeyFilter keyFilter : condition.getCondition()) {
217 EntityKeyValue value = data.getValue(keyFilter.getKey()); 247 EntityKeyValue value = data.getValue(keyFilter.getKey());
218 if (value == null) { 248 if (value == null) {
219 return false; 249 return false;
220 } 250 }
221 - eval = eval && eval(value, keyFilter.getPredicate()); 251 + eval = eval && eval(data, value, keyFilter.getPredicate());
222 } 252 }
223 return eval; 253 return eval;
224 } 254 }
225 255
226 - private boolean eval(EntityKeyValue value, KeyFilterPredicate predicate) { 256 + private boolean eval(DataSnapshot data, EntityKeyValue value, KeyFilterPredicate predicate) {
227 switch (predicate.getType()) { 257 switch (predicate.getType()) {
228 case STRING: 258 case STRING:
229 - return evalStrPredicate(value, (StringFilterPredicate) predicate); 259 + return evalStrPredicate(data, value, (StringFilterPredicate) predicate);
230 case NUMERIC: 260 case NUMERIC:
231 - return evalNumPredicate(value, (NumericFilterPredicate) predicate);  
232 - case COMPLEX:  
233 - return evalComplexPredicate(value, (ComplexFilterPredicate) predicate); 261 + return evalNumPredicate(data, value, (NumericFilterPredicate) predicate);
234 case BOOLEAN: 262 case BOOLEAN:
235 - return evalBoolPredicate(value, (BooleanFilterPredicate) predicate); 263 + return evalBoolPredicate(data, value, (BooleanFilterPredicate) predicate);
  264 + case COMPLEX:
  265 + return evalComplexPredicate(data, value, (ComplexFilterPredicate) predicate);
236 default: 266 default:
237 return false; 267 return false;
238 } 268 }
239 } 269 }
240 270
241 - private boolean evalComplexPredicate(EntityKeyValue ekv, ComplexFilterPredicate predicate) { 271 + private boolean evalComplexPredicate(DataSnapshot data, EntityKeyValue ekv, ComplexFilterPredicate predicate) {
242 switch (predicate.getOperation()) { 272 switch (predicate.getOperation()) {
243 case OR: 273 case OR:
244 for (KeyFilterPredicate kfp : predicate.getPredicates()) { 274 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
245 - if (eval(ekv, kfp)) { 275 + if (eval(data, ekv, kfp)) {
246 return true; 276 return true;
247 } 277 }
248 } 278 }
249 return false; 279 return false;
250 case AND: 280 case AND:
251 for (KeyFilterPredicate kfp : predicate.getPredicates()) { 281 for (KeyFilterPredicate kfp : predicate.getPredicates()) {
252 - if (!eval(ekv, kfp)) { 282 + if (!eval(data, ekv, kfp)) {
253 return false; 283 return false;
254 } 284 }
255 } 285 }
@@ -259,109 +289,55 @@ public class AlarmRuleState { @@ -259,109 +289,55 @@ public class AlarmRuleState {
259 } 289 }
260 } 290 }
261 291
262 - private boolean evalBoolPredicate(EntityKeyValue ekv, BooleanFilterPredicate predicate) {  
263 - Boolean value;  
264 - switch (ekv.getDataType()) {  
265 - case LONG:  
266 - value = ekv.getLngValue() > 0;  
267 - break;  
268 - case DOUBLE:  
269 - value = ekv.getDblValue() > 0;  
270 - break;  
271 - case BOOLEAN:  
272 - value = ekv.getBoolValue();  
273 - break;  
274 - case STRING:  
275 - try {  
276 - value = Boolean.parseBoolean(ekv.getStrValue());  
277 - break;  
278 - } catch (RuntimeException e) {  
279 - return false;  
280 - }  
281 - case JSON:  
282 - try {  
283 - value = Boolean.parseBoolean(ekv.getJsonValue());  
284 - break;  
285 - } catch (RuntimeException e) {  
286 - return false;  
287 - }  
288 - default:  
289 - return false;  
290 - }  
291 - if (value == null) { 292 + private boolean evalBoolPredicate(DataSnapshot data, EntityKeyValue ekv, BooleanFilterPredicate predicate) {
  293 + Boolean val = getBoolValue(ekv);
  294 + if (val == null) {
292 return false; 295 return false;
293 } 296 }
  297 + Boolean predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getBoolValue);
294 switch (predicate.getOperation()) { 298 switch (predicate.getOperation()) {
295 case EQUAL: 299 case EQUAL:
296 - return value.equals(predicate.getValue().getDefaultValue()); 300 + return val.equals(predicateValue);
297 case NOT_EQUAL: 301 case NOT_EQUAL:
298 - return !value.equals(predicate.getValue().getDefaultValue()); 302 + return !val.equals(predicateValue);
299 default: 303 default:
300 throw new RuntimeException("Operation not supported: " + predicate.getOperation()); 304 throw new RuntimeException("Operation not supported: " + predicate.getOperation());
301 } 305 }
302 } 306 }
303 307
304 - private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) {  
305 - Double value;  
306 - switch (ekv.getDataType()) {  
307 - case LONG:  
308 - value = ekv.getLngValue().doubleValue();  
309 - break;  
310 - case DOUBLE:  
311 - value = ekv.getDblValue();  
312 - break;  
313 - case BOOLEAN:  
314 - value = ekv.getBoolValue() ? 1.0 : 0.0;  
315 - break;  
316 - case STRING:  
317 - try {  
318 - value = Double.parseDouble(ekv.getStrValue());  
319 - break;  
320 - } catch (RuntimeException e) {  
321 - return false;  
322 - }  
323 - case JSON:  
324 - try {  
325 - value = Double.parseDouble(ekv.getJsonValue());  
326 - break;  
327 - } catch (RuntimeException e) {  
328 - return false;  
329 - }  
330 - default:  
331 - return false;  
332 - }  
333 - if (value == null) { 308 + private boolean evalNumPredicate(DataSnapshot data, EntityKeyValue ekv, NumericFilterPredicate predicate) {
  309 + Double val = getDblValue(ekv);
  310 + if (val == null) {
334 return false; 311 return false;
335 } 312 }
336 -  
337 - Double predicateValue = predicate.getValue().getDefaultValue(); 313 + Double predicateValue = getPredicateValue(data, predicate.getValue(), AlarmRuleState::getDblValue);
338 switch (predicate.getOperation()) { 314 switch (predicate.getOperation()) {
339 case NOT_EQUAL: 315 case NOT_EQUAL:
340 - return !value.equals(predicateValue); 316 + return !val.equals(predicateValue);
341 case EQUAL: 317 case EQUAL:
342 - return value.equals(predicateValue); 318 + return val.equals(predicateValue);
343 case GREATER: 319 case GREATER:
344 - return value > predicateValue; 320 + return val > predicateValue;
345 case GREATER_OR_EQUAL: 321 case GREATER_OR_EQUAL:
346 - return value >= predicateValue; 322 + return val >= predicateValue;
347 case LESS: 323 case LESS:
348 - return value < predicateValue; 324 + return val < predicateValue;
349 case LESS_OR_EQUAL: 325 case LESS_OR_EQUAL:
350 - return value <= predicateValue; 326 + return val <= predicateValue;
351 default: 327 default:
352 throw new RuntimeException("Operation not supported: " + predicate.getOperation()); 328 throw new RuntimeException("Operation not supported: " + predicate.getOperation());
353 } 329 }
354 } 330 }
355 331
356 - private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) {  
357 - String val;  
358 - 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);
359 if (predicate.isIgnoreCase()) { 338 if (predicate.isIgnoreCase()) {
360 - val = ekv.getStrValue().toLowerCase();  
361 - predicateValue = predicate.getValue().getDefaultValue().toLowerCase();  
362 - } else {  
363 - val = ekv.getStrValue();  
364 - predicateValue = predicate.getValue().getDefaultValue(); 339 + val = val.toLowerCase();
  340 + predicateValue = predicateValue.toLowerCase();
365 } 341 }
366 switch (predicate.getOperation()) { 342 switch (predicate.getOperation()) {
367 case CONTAINS: 343 case CONTAINS:
@@ -380,4 +356,100 @@ public class AlarmRuleState { @@ -380,4 +356,100 @@ public class AlarmRuleState {
380 throw new RuntimeException("Operation not supported: " + predicate.getOperation()); 356 throw new RuntimeException("Operation not supported: " + predicate.getOperation());
381 } 357 }
382 } 358 }
  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 +
383 } 455 }
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java renamed from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileAlarmState.java
@@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.profile; @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.profile;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import lombok.Data; 19 import lombok.Data;
  20 +import lombok.extern.slf4j.Slf4j;
20 import org.thingsboard.rule.engine.action.TbAlarmResult; 21 import org.thingsboard.rule.engine.action.TbAlarmResult;
21 import org.thingsboard.rule.engine.api.TbContext; 22 import org.thingsboard.rule.engine.api.TbContext;
22 import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; 23 import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState;
@@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity;
27 import org.thingsboard.server.common.data.alarm.AlarmStatus; 28 import org.thingsboard.server.common.data.alarm.AlarmStatus;
28 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; 29 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
29 import org.thingsboard.server.common.data.id.EntityId; 30 import org.thingsboard.server.common.data.id.EntityId;
  31 +import org.thingsboard.server.common.data.query.EntityKeyType;
30 import org.thingsboard.server.common.msg.TbMsg; 32 import org.thingsboard.server.common.msg.TbMsg;
31 import org.thingsboard.server.common.msg.TbMsgMetaData; 33 import org.thingsboard.server.common.msg.TbMsgMetaData;
32 import org.thingsboard.server.common.msg.queue.ServiceQueue; 34 import org.thingsboard.server.common.msg.queue.ServiceQueue;
@@ -39,8 +41,10 @@ import java.util.concurrent.ExecutionException; @@ -39,8 +41,10 @@ import java.util.concurrent.ExecutionException;
39 import java.util.function.BiFunction; 41 import java.util.function.BiFunction;
40 42
41 @Data 43 @Data
42 -class DeviceProfileAlarmState { 44 +@Slf4j
  45 +class AlarmState {
43 46
  47 + private final ProfileState deviceProfile;
44 private final EntityId originator; 48 private final EntityId originator;
45 private DeviceProfileAlarm alarmDefinition; 49 private DeviceProfileAlarm alarmDefinition;
46 private volatile List<AlarmRuleState> createRulesSortedBySeverityDesc; 50 private volatile List<AlarmRuleState> createRulesSortedBySeverityDesc;
@@ -50,27 +54,33 @@ class DeviceProfileAlarmState { @@ -50,27 +54,33 @@ class DeviceProfileAlarmState {
50 private volatile TbMsgMetaData lastMsgMetaData; 54 private volatile TbMsgMetaData lastMsgMetaData;
51 private volatile String lastMsgQueueName; 55 private volatile String lastMsgQueueName;
52 56
53 - public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) { 57 + AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) {
  58 + this.deviceProfile = deviceProfile;
54 this.originator = originator; 59 this.originator = originator;
55 this.updateState(alarmDefinition, alarmState); 60 this.updateState(alarmDefinition, alarmState);
56 } 61 }
57 62
58 - public boolean process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException { 63 + public boolean process(TbContext ctx, TbMsg msg, DataSnapshot data, SnapshotUpdate update) throws ExecutionException, InterruptedException {
59 initCurrentAlarm(ctx); 64 initCurrentAlarm(ctx);
60 lastMsgMetaData = msg.getMetaData(); 65 lastMsgMetaData = msg.getMetaData();
61 lastMsgQueueName = msg.getQueueName(); 66 lastMsgQueueName = msg.getQueueName();
62 - return createOrClearAlarms(ctx, data, AlarmRuleState::eval); 67 + return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval);
63 } 68 }
64 69
65 public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { 70 public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
66 initCurrentAlarm(ctx); 71 initCurrentAlarm(ctx);
67 - return createOrClearAlarms(ctx, ts, AlarmRuleState::eval); 72 + return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval);
68 } 73 }
69 74
70 - public <T> boolean createOrClearAlarms(TbContext ctx, T data, BiFunction<AlarmRuleState, T, Boolean> evalFunction) { 75 + public <T> boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, Boolean> evalFunction) {
71 boolean stateUpdate = false; 76 boolean stateUpdate = false;
72 AlarmSeverity resultSeverity = null; 77 AlarmSeverity resultSeverity = null;
  78 + log.debug("[{}] processing update: {}", alarmDefinition.getId(), data);
73 for (AlarmRuleState state : createRulesSortedBySeverityDesc) { 79 for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
  80 + if (!validateUpdate(update, state)) {
  81 + log.debug("[{}][{}] Update is not valid for current rule state", alarmDefinition.getId(), state.getSeverity());
  82 + continue;
  83 + }
74 boolean evalResult = evalFunction.apply(state, data); 84 boolean evalResult = evalFunction.apply(state, data);
75 stateUpdate |= state.checkUpdate(); 85 stateUpdate |= state.checkUpdate();
76 if (evalResult) { 86 if (evalResult) {
@@ -81,9 +91,17 @@ class DeviceProfileAlarmState { @@ -81,9 +91,17 @@ class DeviceProfileAlarmState {
81 if (resultSeverity != null) { 91 if (resultSeverity != null) {
82 pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity)); 92 pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity));
83 } else if (currentAlarm != null && clearState != null) { 93 } else if (currentAlarm != null && clearState != null) {
  94 + if (!validateUpdate(update, clearState)) {
  95 + log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId());
  96 + return stateUpdate;
  97 + }
84 Boolean evalResult = evalFunction.apply(clearState, data); 98 Boolean evalResult = evalFunction.apply(clearState, data);
85 if (evalResult) { 99 if (evalResult) {
86 stateUpdate |= clearState.checkUpdate(); 100 stateUpdate |= clearState.checkUpdate();
  101 + for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
  102 + state.clear();
  103 + stateUpdate |= state.checkUpdate();
  104 + }
87 ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); 105 ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
88 pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm)); 106 pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
89 currentAlarm = null; 107 currentAlarm = null;
@@ -92,6 +110,18 @@ class DeviceProfileAlarmState { @@ -92,6 +110,18 @@ class DeviceProfileAlarmState {
92 return stateUpdate; 110 return stateUpdate;
93 } 111 }
94 112
  113 + public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) {
  114 + if (update != null) {
  115 + //Check that the update type and that keys match.
  116 + if (update.getType().equals(EntityKeyType.TIME_SERIES)) {
  117 + return state.validateTsUpdate(update.getKeys());
  118 + } else if (update.getType().equals(EntityKeyType.ATTRIBUTE)) {
  119 + return state.validateAttrUpdate(update.getKeys());
  120 + }
  121 + }
  122 + return true;
  123 + }
  124 +
95 public void initCurrentAlarm(TbContext ctx) throws InterruptedException, ExecutionException { 125 public void initCurrentAlarm(TbContext ctx) throws InterruptedException, ExecutionException {
96 if (!initialFetchDone) { 126 if (!initialFetchDone) {
97 Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get(); 127 Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get();
@@ -137,17 +167,20 @@ class DeviceProfileAlarmState { @@ -137,17 +167,20 @@ class DeviceProfileAlarmState {
137 alarmState.getCreateRuleStates().put(severity, ruleState); 167 alarmState.getCreateRuleStates().put(severity, ruleState);
138 } 168 }
139 } 169 }
140 - createRulesSortedBySeverityDesc.add(new AlarmRuleState(severity, rule, ruleState)); 170 + createRulesSortedBySeverityDesc.add(new AlarmRuleState(severity, rule,
  171 + deviceProfile.getCreateAlarmKeys(alarm.getId(), severity), ruleState));
141 }); 172 });
142 createRulesSortedBySeverityDesc.sort(Comparator.comparingInt(state -> state.getSeverity().ordinal())); 173 createRulesSortedBySeverityDesc.sort(Comparator.comparingInt(state -> state.getSeverity().ordinal()));
143 PersistedAlarmRuleState ruleState = alarmState == null ? null : alarmState.getClearRuleState(); 174 PersistedAlarmRuleState ruleState = alarmState == null ? null : alarmState.getClearRuleState();
144 if (alarmDefinition.getClearRule() != null) { 175 if (alarmDefinition.getClearRule() != null) {
145 - clearState = new AlarmRuleState(null, alarmDefinition.getClearRule(), ruleState); 176 + clearState = new AlarmRuleState(null, alarmDefinition.getClearRule(), deviceProfile.getClearAlarmKeys(alarm.getId()), ruleState);
146 } 177 }
147 } 178 }
148 179
149 private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { 180 private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) {
150 if (currentAlarm != null) { 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?
151 currentAlarm.setEndTs(System.currentTimeMillis()); 184 currentAlarm.setEndTs(System.currentTimeMillis());
152 AlarmSeverity oldSeverity = currentAlarm.getSeverity(); 185 AlarmSeverity oldSeverity = currentAlarm.getSeverity();
153 if (!oldSeverity.equals(severity)) { 186 if (!oldSeverity.equals(severity)) {
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.profile; 16 package org.thingsboard.rule.engine.profile;
17 17
18 -public enum AlarmStateUpdateResult { 18 +enum AlarmStateUpdateResult {
19 19
20 NONE, CREATED, UPDATED, SEVERITY_UPDATED, CLEARED; 20 NONE, CREATED, UPDATED, SEVERITY_UPDATED, CLEARED;
21 21
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DataSnapshot.java renamed from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceDataSnapshot.java
@@ -24,7 +24,7 @@ import java.util.Map; @@ -24,7 +24,7 @@ import java.util.Map;
24 import java.util.Set; 24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap; 25 import java.util.concurrent.ConcurrentHashMap;
26 26
27 -public class DeviceDataSnapshot { 27 +class DataSnapshot {
28 28
29 private volatile boolean ready; 29 private volatile boolean ready;
30 @Getter 30 @Getter
@@ -33,7 +33,7 @@ public class DeviceDataSnapshot { @@ -33,7 +33,7 @@ public class DeviceDataSnapshot {
33 private final Set<EntityKey> keys; 33 private final Set<EntityKey> keys;
34 private final Map<EntityKey, EntityKeyValue> values = new ConcurrentHashMap<>(); 34 private final Map<EntityKey, EntityKeyValue> values = new ConcurrentHashMap<>();
35 35
36 - public DeviceDataSnapshot(Set<EntityKey> entityKeysToFetch) { 36 + DataSnapshot(Set<EntityKey> entityKeysToFetch) {
37 this.keys = entityKeysToFetch; 37 this.keys = entityKeysToFetch;
38 } 38 }
39 39
@@ -56,28 +56,38 @@ public class DeviceDataSnapshot { @@ -56,28 +56,38 @@ public class DeviceDataSnapshot {
56 } 56 }
57 } 57 }
58 58
59 - void putValue(EntityKey key, EntityKeyValue value) { 59 + boolean putValue(EntityKey key, long newTs, EntityKeyValue value) {
  60 + boolean updateOfTs = ts != newTs;
  61 + boolean result = false;
60 switch (key.getType()) { 62 switch (key.getType()) {
61 case ATTRIBUTE: 63 case ATTRIBUTE:
62 - putIfKeyExists(key, value);  
63 - putIfKeyExists(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE), value);  
64 - putIfKeyExists(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE), value);  
65 - putIfKeyExists(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE), value); 64 + result |= putIfKeyExists(key, value, updateOfTs);
  65 + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE), value, updateOfTs);
  66 + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE), value, updateOfTs);
  67 + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE), value, updateOfTs);
66 break; 68 break;
67 case CLIENT_ATTRIBUTE: 69 case CLIENT_ATTRIBUTE:
68 case SHARED_ATTRIBUTE: 70 case SHARED_ATTRIBUTE:
69 case SERVER_ATTRIBUTE: 71 case SERVER_ATTRIBUTE:
70 - putIfKeyExists(key, value);  
71 - putIfKeyExists(getAttrKey(key, EntityKeyType.ATTRIBUTE), value); 72 + result |= putIfKeyExists(key, value, updateOfTs);
  73 + result |= putIfKeyExists(getAttrKey(key, EntityKeyType.ATTRIBUTE), value, updateOfTs);
72 break; 74 break;
73 default: 75 default:
74 - putIfKeyExists(key, value); 76 + result |= putIfKeyExists(key, value, updateOfTs);
75 } 77 }
  78 + return result;
76 } 79 }
77 80
78 - private void putIfKeyExists(EntityKey key, EntityKeyValue value) { 81 + private boolean putIfKeyExists(EntityKey key, EntityKeyValue value, boolean updateOfTs) {
79 if (keys.contains(key)) { 82 if (keys.contains(key)) {
80 - values.put(key, value); 83 + EntityKeyValue oldValue = values.put(key, value);
  84 + if (updateOfTs) {
  85 + return true;
  86 + } else {
  87 + return oldValue == null || !oldValue.equals(value);
  88 + }
  89 + } else {
  90 + return false;
81 } 91 }
82 } 92 }
83 93
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.rule.engine.profile; 16 package org.thingsboard.rule.engine.profile;
17 17
18 import com.google.gson.JsonParser; 18 import com.google.gson.JsonParser;
  19 +import lombok.extern.slf4j.Slf4j;
19 import org.springframework.util.StringUtils; 20 import org.springframework.util.StringUtils;
20 import org.thingsboard.rule.engine.api.TbContext; 21 import org.thingsboard.rule.engine.api.TbContext;
21 import org.thingsboard.rule.engine.profile.state.PersistedAlarmState; 22 import org.thingsboard.rule.engine.profile.state.PersistedAlarmState;
@@ -29,7 +30,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; @@ -29,7 +30,6 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
29 import org.thingsboard.server.common.data.id.DeviceId; 30 import org.thingsboard.server.common.data.id.DeviceId;
30 import org.thingsboard.server.common.data.id.DeviceProfileId; 31 import org.thingsboard.server.common.data.id.DeviceProfileId;
31 import org.thingsboard.server.common.data.id.EntityId; 32 import org.thingsboard.server.common.data.id.EntityId;
32 -import org.thingsboard.server.common.data.id.RuleNodeStateId;  
33 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 33 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
34 import org.thingsboard.server.common.data.kv.KvEntry; 34 import org.thingsboard.server.common.data.kv.KvEntry;
35 import org.thingsboard.server.common.data.kv.TsKvEntry; 35 import org.thingsboard.server.common.data.kv.TsKvEntry;
@@ -53,17 +53,18 @@ import java.util.concurrent.ConcurrentMap; @@ -53,17 +53,18 @@ import java.util.concurrent.ConcurrentMap;
53 import java.util.concurrent.ExecutionException; 53 import java.util.concurrent.ExecutionException;
54 import java.util.stream.Collectors; 54 import java.util.stream.Collectors;
55 55
  56 +@Slf4j
56 class DeviceState { 57 class DeviceState {
57 58
58 private final boolean persistState; 59 private final boolean persistState;
59 private final DeviceId deviceId; 60 private final DeviceId deviceId;
  61 + private final ProfileState deviceProfile;
60 private RuleNodeState state; 62 private RuleNodeState state;
61 - private DeviceProfileState deviceProfile;  
62 private PersistedDeviceState pds; 63 private PersistedDeviceState pds;
63 - private DeviceDataSnapshot latestValues;  
64 - private final ConcurrentMap<String, DeviceProfileAlarmState> alarmStates = new ConcurrentHashMap<>(); 64 + private DataSnapshot latestValues;
  65 + private final ConcurrentMap<String, AlarmState> alarmStates = new ConcurrentHashMap<>();
65 66
66 - public DeviceState(TbContext ctx, TbDeviceProfileNodeConfiguration config, DeviceId deviceId, DeviceProfileState deviceProfile, RuleNodeState state) { 67 + DeviceState(TbContext ctx, TbDeviceProfileNodeConfiguration config, DeviceId deviceId, ProfileState deviceProfile, RuleNodeState state) {
67 this.persistState = config.isPersistAlarmRulesState(); 68 this.persistState = config.isPersistAlarmRulesState();
68 this.deviceId = deviceId; 69 this.deviceId = deviceId;
69 this.deviceProfile = deviceProfile; 70 this.deviceProfile = deviceProfile;
@@ -86,7 +87,7 @@ class DeviceState { @@ -86,7 +87,7 @@ class DeviceState {
86 if (pds != null) { 87 if (pds != null) {
87 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { 88 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
88 alarmStates.computeIfAbsent(alarm.getId(), 89 alarmStates.computeIfAbsent(alarm.getId(),
89 - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); 90 + a -> new AlarmState(deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm)));
90 } 91 }
91 } 92 }
92 } 93 }
@@ -107,14 +108,20 @@ class DeviceState { @@ -107,14 +108,20 @@ class DeviceState {
107 if (alarmStates.containsKey(alarm.getId())) { 108 if (alarmStates.containsKey(alarm.getId())) {
108 alarmStates.get(alarm.getId()).updateState(alarm, getOrInitPersistedAlarmState(alarm)); 109 alarmStates.get(alarm.getId()).updateState(alarm, getOrInitPersistedAlarmState(alarm));
109 } else { 110 } else {
110 - alarmStates.putIfAbsent(alarm.getId(), new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); 111 + alarmStates.putIfAbsent(alarm.getId(), new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm)));
111 } 112 }
112 } 113 }
113 } 114 }
114 115
115 public void harvestAlarms(TbContext ctx, long ts) throws ExecutionException, InterruptedException { 116 public void harvestAlarms(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
116 - for (DeviceProfileAlarmState state : alarmStates.values()) {  
117 - state.process(ctx, ts); 117 + log.debug("[{}] Going to harvest alarms: {}", ctx.getSelfId(), ts);
  118 + boolean stateChanged = false;
  119 + for (AlarmState state : alarmStates.values()) {
  120 + stateChanged |= state.process(ctx, ts);
  121 + }
  122 + if (persistState && stateChanged) {
  123 + state.setStateData(JacksonUtil.toString(pds));
  124 + state = ctx.saveRuleNodeState(state);
118 } 125 }
119 } 126 }
120 127
@@ -146,8 +153,8 @@ class DeviceState { @@ -146,8 +153,8 @@ class DeviceState {
146 boolean stateChanged = false; 153 boolean stateChanged = false;
147 Alarm alarmNf = JacksonUtil.fromString(msg.getData(), Alarm.class); 154 Alarm alarmNf = JacksonUtil.fromString(msg.getData(), Alarm.class);
148 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { 155 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
149 - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),  
150 - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm))); 156 + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
  157 + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm)));
151 stateChanged |= alarmState.processAlarmClear(ctx, alarmNf); 158 stateChanged |= alarmState.processAlarmClear(ctx, alarmNf);
152 } 159 }
153 ctx.tellSuccess(msg); 160 ctx.tellSuccess(msg);
@@ -175,9 +182,9 @@ class DeviceState { @@ -175,9 +182,9 @@ class DeviceState {
175 EntityKeyType keyType = getKeyTypeFromScope(scope); 182 EntityKeyType keyType = getKeyTypeFromScope(scope);
176 keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key))); 183 keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key)));
177 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { 184 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
178 - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),  
179 - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm)));  
180 - stateChanged |= alarmState.process(ctx, msg, latestValues); 185 + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
  186 + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm)));
  187 + stateChanged |= alarmState.process(ctx, msg, latestValues, null);
181 } 188 }
182 } 189 }
183 ctx.tellSuccess(msg); 190 ctx.tellSuccess(msg);
@@ -192,11 +199,11 @@ class DeviceState { @@ -192,11 +199,11 @@ class DeviceState {
192 private boolean processAttributesUpdate(TbContext ctx, TbMsg msg, Set<AttributeKvEntry> attributes, String scope) throws ExecutionException, InterruptedException { 199 private boolean processAttributesUpdate(TbContext ctx, TbMsg msg, Set<AttributeKvEntry> attributes, String scope) throws ExecutionException, InterruptedException {
193 boolean stateChanged = false; 200 boolean stateChanged = false;
194 if (!attributes.isEmpty()) { 201 if (!attributes.isEmpty()) {
195 - merge(latestValues, attributes, scope); 202 + SnapshotUpdate update = merge(latestValues, attributes, scope);
196 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { 203 for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
197 - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),  
198 - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm)));  
199 - stateChanged |= alarmState.process(ctx, msg, latestValues); 204 + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
  205 + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm)));
  206 + stateChanged |= alarmState.process(ctx, msg, latestValues, update);
200 } 207 }
201 } 208 }
202 ctx.tellSuccess(msg); 209 ctx.tellSuccess(msg);
@@ -206,34 +213,47 @@ class DeviceState { @@ -206,34 +213,47 @@ class DeviceState {
206 protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { 213 protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
207 boolean stateChanged = false; 214 boolean stateChanged = false;
208 Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg)); 215 Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg));
  216 + // iterate over data by ts (ASC order).
209 for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) { 217 for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) {
210 Long ts = entry.getKey(); 218 Long ts = entry.getKey();
211 List<KvEntry> data = entry.getValue(); 219 List<KvEntry> data = entry.getValue();
212 - merge(latestValues, ts, data);  
213 - for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {  
214 - DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),  
215 - a -> new DeviceProfileAlarmState(deviceId, alarm, getOrInitPersistedAlarmState(alarm)));  
216 - stateChanged |= alarmState.process(ctx, msg, latestValues); 220 + SnapshotUpdate update = merge(latestValues, ts, data);
  221 + if (update.hasUpdate()) {
  222 + for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
  223 + AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(),
  224 + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm)));
  225 + stateChanged |= alarmState.process(ctx, msg, latestValues, update);
  226 + }
217 } 227 }
218 } 228 }
219 ctx.tellSuccess(msg); 229 ctx.tellSuccess(msg);
220 return stateChanged; 230 return stateChanged;
221 } 231 }
222 232
223 - private void merge(DeviceDataSnapshot latestValues, Long ts, List<KvEntry> data) {  
224 - latestValues.setTs(ts); 233 + private SnapshotUpdate merge(DataSnapshot latestValues, Long newTs, List<KvEntry> data) {
  234 + Set<EntityKey> keys = new HashSet<>();
225 for (KvEntry entry : data) { 235 for (KvEntry entry : data) {
226 - latestValues.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry)); 236 + EntityKey entityKey = new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey());
  237 + if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) {
  238 + keys.add(entityKey);
  239 + }
227 } 240 }
  241 + latestValues.setTs(newTs);
  242 + return new SnapshotUpdate(EntityKeyType.TIME_SERIES, keys);
228 } 243 }
229 244
230 - private void merge(DeviceDataSnapshot latestValues, Set<AttributeKvEntry> attributes, String scope) {  
231 - long ts = latestValues.getTs(); 245 + private SnapshotUpdate merge(DataSnapshot latestValues, Set<AttributeKvEntry> attributes, String scope) {
  246 + long newTs = 0;
  247 + Set<EntityKey> keys = new HashSet<>();
232 for (AttributeKvEntry entry : attributes) { 248 for (AttributeKvEntry entry : attributes) {
233 - ts = Math.max(ts, entry.getLastUpdateTs());  
234 - latestValues.putValue(new EntityKey(getKeyTypeFromScope(scope), entry.getKey()), toEntityValue(entry)); 249 + newTs = Math.max(newTs, entry.getLastUpdateTs());
  250 + EntityKey entityKey = new EntityKey(getKeyTypeFromScope(scope), entry.getKey());
  251 + if (latestValues.putValue(entityKey, newTs, toEntityValue(entry))) {
  252 + keys.add(entityKey);
  253 + }
235 } 254 }
236 - latestValues.setTs(ts); 255 + latestValues.setTs(newTs);
  256 + return new SnapshotUpdate(EntityKeyType.ATTRIBUTE, keys);
237 } 257 }
238 258
239 private static EntityKeyType getKeyTypeFromScope(String scope) { 259 private static EntityKeyType getKeyTypeFromScope(String scope) {
@@ -248,14 +268,14 @@ class DeviceState { @@ -248,14 +268,14 @@ class DeviceState {
248 return EntityKeyType.ATTRIBUTE; 268 return EntityKeyType.ATTRIBUTE;
249 } 269 }
250 270
251 - private DeviceDataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { 271 + private DataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException {
252 Set<EntityKey> entityKeysToFetch = deviceProfile.getEntityKeys(); 272 Set<EntityKey> entityKeysToFetch = deviceProfile.getEntityKeys();
253 - DeviceDataSnapshot result = new DeviceDataSnapshot(entityKeysToFetch); 273 + DataSnapshot result = new DataSnapshot(entityKeysToFetch);
254 addEntityKeysToSnapshot(ctx, originator, entityKeysToFetch, result); 274 addEntityKeysToSnapshot(ctx, originator, entityKeysToFetch, result);
255 return result; 275 return result;
256 } 276 }
257 277
258 - private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set<EntityKey> entityKeysToFetch, DeviceDataSnapshot result) throws InterruptedException, ExecutionException { 278 + private void addEntityKeysToSnapshot(TbContext ctx, EntityId originator, Set<EntityKey> entityKeysToFetch, DataSnapshot result) throws InterruptedException, ExecutionException {
259 Set<String> serverAttributeKeys = new HashSet<>(); 279 Set<String> serverAttributeKeys = new HashSet<>();
260 Set<String> clientAttributeKeys = new HashSet<>(); 280 Set<String> clientAttributeKeys = new HashSet<>();
261 Set<String> sharedAttributeKeys = new HashSet<>(); 281 Set<String> sharedAttributeKeys = new HashSet<>();
@@ -291,16 +311,16 @@ class DeviceState { @@ -291,16 +311,16 @@ class DeviceState {
291 if (device != null) { 311 if (device != null) {
292 switch (key) { 312 switch (key) {
293 case EntityKeyMapping.NAME: 313 case EntityKeyMapping.NAME:
294 - result.putValue(entityKey, EntityKeyValue.fromString(device.getName())); 314 + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getName()));
295 break; 315 break;
296 case EntityKeyMapping.TYPE: 316 case EntityKeyMapping.TYPE:
297 - result.putValue(entityKey, EntityKeyValue.fromString(device.getType())); 317 + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getType()));
298 break; 318 break;
299 case EntityKeyMapping.CREATED_TIME: 319 case EntityKeyMapping.CREATED_TIME:
300 - result.putValue(entityKey, EntityKeyValue.fromLong(device.getCreatedTime())); 320 + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromLong(device.getCreatedTime()));
301 break; 321 break;
302 case EntityKeyMapping.LABEL: 322 case EntityKeyMapping.LABEL:
303 - result.putValue(entityKey, EntityKeyValue.fromString(device.getLabel())); 323 + result.putValue(entityKey, device.getCreatedTime(), EntityKeyValue.fromString(device.getLabel()));
304 break; 324 break;
305 } 325 }
306 } 326 }
@@ -312,7 +332,7 @@ class DeviceState { @@ -312,7 +332,7 @@ class DeviceState {
312 List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get(); 332 List<TsKvEntry> data = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), originator, latestTsKeys).get();
313 for (TsKvEntry entry : data) { 333 for (TsKvEntry entry : data) {
314 if (entry.getValue() != null) { 334 if (entry.getValue() != null) {
315 - result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), toEntityValue(entry)); 335 + result.putValue(new EntityKey(EntityKeyType.TIME_SERIES, entry.getKey()), entry.getTs(), toEntityValue(entry));
316 } 336 }
317 } 337 }
318 } 338 }
@@ -330,13 +350,13 @@ class DeviceState { @@ -330,13 +350,13 @@ class DeviceState {
330 } 350 }
331 } 351 }
332 352
333 - private void addToSnapshot(DeviceDataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) { 353 + private void addToSnapshot(DataSnapshot snapshot, Set<String> commonAttributeKeys, List<AttributeKvEntry> data) {
334 for (AttributeKvEntry entry : data) { 354 for (AttributeKvEntry entry : data) {
335 if (entry.getValue() != null) { 355 if (entry.getValue() != null) {
336 EntityKeyValue value = toEntityValue(entry); 356 EntityKeyValue value = toEntityValue(entry);
337 - snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), value); 357 + snapshot.putValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value);
338 if (commonAttributeKeys.contains(entry.getKey())) { 358 if (commonAttributeKeys.contains(entry.getKey())) {
339 - snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), value); 359 + snapshot.putValue(new EntityKey(EntityKeyType.ATTRIBUTE, entry.getKey()), entry.getLastUpdateTs(), value);
340 } 360 }
341 } 361 }
342 } 362 }
@@ -15,9 +15,11 @@ @@ -15,9 +15,11 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.profile; 16 package org.thingsboard.rule.engine.profile;
17 17
  18 +import lombok.EqualsAndHashCode;
18 import lombok.Getter; 19 import lombok.Getter;
19 import org.thingsboard.server.common.data.kv.DataType; 20 import org.thingsboard.server.common.data.kv.DataType;
20 21
  22 +@EqualsAndHashCode
21 class EntityKeyValue { 23 class EntityKeyValue {
22 24
23 @Getter 25 @Getter
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java renamed from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileState.java
@@ -18,19 +18,33 @@ package org.thingsboard.rule.engine.profile; @@ -18,19 +18,33 @@ package org.thingsboard.rule.engine.profile;
18 import lombok.AccessLevel; 18 import lombok.AccessLevel;
19 import lombok.Getter; 19 import lombok.Getter;
20 import org.thingsboard.server.common.data.DeviceProfile; 20 import org.thingsboard.server.common.data.DeviceProfile;
  21 +import org.thingsboard.server.common.data.alarm.AlarmSeverity;
21 import org.thingsboard.server.common.data.device.profile.AlarmRule; 22 import org.thingsboard.server.common.data.device.profile.AlarmRule;
22 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; 23 import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
23 import org.thingsboard.server.common.data.id.DeviceProfileId; 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;
24 import org.thingsboard.server.common.data.query.EntityKey; 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;
25 import org.thingsboard.server.common.data.query.KeyFilter; 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;
26 35
  36 +import javax.print.attribute.standard.Severity;
  37 +import java.util.Collections;
  38 +import java.util.HashMap;
  39 +import java.util.HashSet;
27 import java.util.List; 40 import java.util.List;
  41 +import java.util.Map;
28 import java.util.Set; 42 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap; 43 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.CopyOnWriteArrayList; 44 import java.util.concurrent.CopyOnWriteArrayList;
31 45
32 46
33 -class DeviceProfileState { 47 +class ProfileState {
34 48
35 private DeviceProfile deviceProfile; 49 private DeviceProfile deviceProfile;
36 @Getter(AccessLevel.PACKAGE) 50 @Getter(AccessLevel.PACKAGE)
@@ -38,26 +52,86 @@ class DeviceProfileState { @@ -38,26 +52,86 @@ class DeviceProfileState {
38 @Getter(AccessLevel.PACKAGE) 52 @Getter(AccessLevel.PACKAGE)
39 private final Set<EntityKey> entityKeys = ConcurrentHashMap.newKeySet(); 53 private final Set<EntityKey> entityKeys = ConcurrentHashMap.newKeySet();
40 54
41 - DeviceProfileState(DeviceProfile deviceProfile) { 55 + private final Map<String, Map<AlarmSeverity, Set<EntityKey>>> alarmCreateKeys = new HashMap<>();
  56 + private final Map<String, Set<EntityKey>> alarmClearKeys = new HashMap<>();
  57 +
  58 + ProfileState(DeviceProfile deviceProfile) {
42 updateDeviceProfile(deviceProfile); 59 updateDeviceProfile(deviceProfile);
43 } 60 }
44 61
45 void updateDeviceProfile(DeviceProfile deviceProfile) { 62 void updateDeviceProfile(DeviceProfile deviceProfile) {
46 this.deviceProfile = deviceProfile; 63 this.deviceProfile = deviceProfile;
47 alarmSettings.clear(); 64 alarmSettings.clear();
  65 + alarmCreateKeys.clear();
  66 + alarmClearKeys.clear();
48 if (deviceProfile.getProfileData().getAlarms() != null) { 67 if (deviceProfile.getProfileData().getAlarms() != null) {
49 alarmSettings.addAll(deviceProfile.getProfileData().getAlarms()); 68 alarmSettings.addAll(deviceProfile.getProfileData().getAlarms());
50 for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) { 69 for (DeviceProfileAlarm alarm : deviceProfile.getProfileData().getAlarms()) {
51 - for (AlarmRule alarmRule : alarm.getCreateRules().values()) { 70 + Map<AlarmSeverity, Set<EntityKey>> createAlarmKeys = alarmCreateKeys.computeIfAbsent(alarm.getId(), id -> new HashMap<>());
  71 + alarm.getCreateRules().forEach(((severity, alarmRule) -> {
  72 + Set<EntityKey> ruleKeys = createAlarmKeys.computeIfAbsent(severity, id -> new HashSet<>());
52 for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) { 73 for (KeyFilter keyFilter : alarmRule.getCondition().getCondition()) {
53 entityKeys.add(keyFilter.getKey()); 74 entityKeys.add(keyFilter.getKey());
  75 + ruleKeys.add(keyFilter.getKey());
  76 + addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
  77 + }
  78 + }));
  79 + if (alarm.getClearRule() != null) {
  80 + Set<EntityKey> clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>());
  81 + for (KeyFilter keyFilter : alarm.getClearRule().getCondition().getCondition()) {
  82 + entityKeys.add(keyFilter.getKey());
  83 + clearAlarmKeys.add(keyFilter.getKey());
  84 + addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys);
54 } 85 }
55 } 86 }
56 } 87 }
57 } 88 }
58 } 89 }
59 90
60 - public DeviceProfileId getProfileId() { 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 +
  111 + DeviceProfileId getProfileId() {
61 return deviceProfile.getId(); 112 return deviceProfile.getId();
62 } 113 }
  114 +
  115 + Set<EntityKey> getCreateAlarmKeys(String id, AlarmSeverity severity) {
  116 + Map<AlarmSeverity, Set<EntityKey>> sKeys = alarmCreateKeys.get(id);
  117 + if (sKeys == null) {
  118 + return Collections.emptySet();
  119 + } else {
  120 + Set<EntityKey> keys = sKeys.get(severity);
  121 + if (keys == null) {
  122 + return Collections.emptySet();
  123 + } else {
  124 + return keys;
  125 + }
  126 + }
  127 + }
  128 +
  129 + Set<EntityKey> getClearAlarmKeys(String id) {
  130 + Set<EntityKey> keys = alarmClearKeys.get(id);
  131 + if (keys == null) {
  132 + return Collections.emptySet();
  133 + } else {
  134 + return keys;
  135 + }
  136 + }
63 } 137 }
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/SnapshotUpdate.java renamed from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/EntityKeyState.java
@@ -15,8 +15,25 @@ @@ -15,8 +15,25 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.profile; 16 package org.thingsboard.rule.engine.profile;
17 17
18 -public class EntityKeyState { 18 +import lombok.Getter;
  19 +import org.thingsboard.server.common.data.query.EntityKey;
  20 +import org.thingsboard.server.common.data.query.EntityKeyType;
19 21
  22 +import java.util.Set;
20 23
  24 +class SnapshotUpdate {
21 25
  26 + @Getter
  27 + private final EntityKeyType type;
  28 + @Getter
  29 + private final Set<EntityKey> keys;
  30 +
  31 + SnapshotUpdate(EntityKeyType type, Set<EntityKey> keys) {
  32 + this.type = type;
  33 + this.keys = keys;
  34 + }
  35 +
  36 + boolean hasUpdate(){
  37 + return !keys.isEmpty();
  38 + }
22 } 39 }
@@ -23,7 +23,6 @@ import org.thingsboard.rule.engine.api.TbNode; @@ -23,7 +23,6 @@ import org.thingsboard.rule.engine.api.TbNode;
23 import org.thingsboard.rule.engine.api.TbNodeConfiguration; 23 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
24 import org.thingsboard.rule.engine.api.TbNodeException; 24 import org.thingsboard.rule.engine.api.TbNodeException;
25 import org.thingsboard.rule.engine.api.util.TbNodeUtils; 25 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
26 -import org.thingsboard.rule.engine.profile.state.PersistedDeviceState;  
27 import org.thingsboard.server.common.data.DataConstants; 26 import org.thingsboard.server.common.data.DataConstants;
28 import org.thingsboard.server.common.data.Device; 27 import org.thingsboard.server.common.data.Device;
29 import org.thingsboard.server.common.data.DeviceProfile; 28 import org.thingsboard.server.common.data.DeviceProfile;
@@ -36,11 +35,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType; @@ -36,11 +35,10 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
36 import org.thingsboard.server.common.data.rule.RuleNodeState; 35 import org.thingsboard.server.common.data.rule.RuleNodeState;
37 import org.thingsboard.server.common.msg.TbMsg; 36 import org.thingsboard.server.common.msg.TbMsg;
38 import org.thingsboard.server.common.msg.TbMsgMetaData; 37 import org.thingsboard.server.common.msg.TbMsgMetaData;
  38 +import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
39 import org.thingsboard.server.dao.util.mapping.JacksonUtil; 39 import org.thingsboard.server.dao.util.mapping.JacksonUtil;
40 40
41 -import java.util.HashMap;  
42 import java.util.Map; 41 import java.util.Map;
43 -import java.util.UUID;  
44 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.ConcurrentHashMap;
45 import java.util.concurrent.ExecutionException; 43 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.TimeUnit; 44 import java.util.concurrent.TimeUnit;
@@ -70,11 +68,14 @@ public class TbDeviceProfileNode implements TbNode { @@ -70,11 +68,14 @@ public class TbDeviceProfileNode implements TbNode {
70 this.cache = ctx.getDeviceProfileCache(); 68 this.cache = ctx.getDeviceProfileCache();
71 scheduleAlarmHarvesting(ctx); 69 scheduleAlarmHarvesting(ctx);
72 if (config.isFetchAlarmRulesStateOnStart()) { 70 if (config.isFetchAlarmRulesStateOnStart()) {
  71 + log.info("[{}] Fetching alarm rule state", ctx.getSelfId());
  72 + int fetchCount = 0;
73 PageLink pageLink = new PageLink(1024); 73 PageLink pageLink = new PageLink(1024);
74 while (true) { 74 while (true) {
75 PageData<RuleNodeState> states = ctx.findRuleNodeStates(pageLink); 75 PageData<RuleNodeState> states = ctx.findRuleNodeStates(pageLink);
76 if (!states.getData().isEmpty()) { 76 if (!states.getData().isEmpty()) {
77 for (RuleNodeState rns : states.getData()) { 77 for (RuleNodeState rns : states.getData()) {
  78 + fetchCount++;
78 if (rns.getEntityId().getEntityType().equals(EntityType.DEVICE) && ctx.isLocalEntity(rns.getEntityId())) { 79 if (rns.getEntityId().getEntityType().equals(EntityType.DEVICE) && ctx.isLocalEntity(rns.getEntityId())) {
79 getOrCreateDeviceState(ctx, new DeviceId(rns.getEntityId().getId()), rns); 80 getOrCreateDeviceState(ctx, new DeviceId(rns.getEntityId().getId()), rns);
80 } 81 }
@@ -86,6 +87,11 @@ public class TbDeviceProfileNode implements TbNode { @@ -86,6 +87,11 @@ public class TbDeviceProfileNode implements TbNode {
86 pageLink = pageLink.nextPageLink(); 87 pageLink = pageLink.nextPageLink();
87 } 88 }
88 } 89 }
  90 + log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount);
  91 + }
  92 + if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) {
  93 + log.info("[{}] Going to cleanup rule node states", ctx.getSelfId());
  94 + ctx.clearRuleNodeStates();
89 } 95 }
90 } 96 }
91 97
@@ -114,11 +120,14 @@ public class TbDeviceProfileNode implements TbNode { @@ -114,11 +120,14 @@ public class TbDeviceProfileNode implements TbNode {
114 } 120 }
115 } 121 }
116 } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) { 122 } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) {
  123 + log.info("[{}] Received device profile update notification: {}", ctx.getSelfId(), msg.getData());
117 if (msg.getType().equals("ENTITY_UPDATED")) { 124 if (msg.getType().equals("ENTITY_UPDATED")) {
118 DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class); 125 DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class);
119 - for (DeviceState state : deviceStates.values()) {  
120 - if (deviceProfile.getId().equals(state.getProfileId())) {  
121 - state.updateProfile(ctx, deviceProfile); 126 + if (deviceProfile != null) {
  127 + for (DeviceState state : deviceStates.values()) {
  128 + if (deviceProfile.getId().equals(state.getProfileId())) {
  129 + state.updateProfile(ctx, deviceProfile);
  130 + }
122 } 131 }
123 } 132 }
124 } 133 }
@@ -141,6 +150,12 @@ public class TbDeviceProfileNode implements TbNode { @@ -141,6 +150,12 @@ public class TbDeviceProfileNode implements TbNode {
141 } 150 }
142 151
143 @Override 152 @Override
  153 + public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) {
  154 + // Cleanup the cache for all entities that are no longer assigned to current server partitions
  155 + deviceStates.entrySet().removeIf(entry -> !ctx.isLocalEntity(entry.getKey()));
  156 + }
  157 +
  158 + @Override
144 public void destroy() { 159 public void destroy() {
145 deviceStates.clear(); 160 deviceStates.clear();
146 } 161 }
@@ -150,7 +165,7 @@ public class TbDeviceProfileNode implements TbNode { @@ -150,7 +165,7 @@ public class TbDeviceProfileNode implements TbNode {
150 if (deviceState == null) { 165 if (deviceState == null) {
151 DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId); 166 DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId);
152 if (deviceProfile != null) { 167 if (deviceProfile != null) {
153 - deviceState = new DeviceState(ctx, config, deviceId, new DeviceProfileState(deviceProfile), rns); 168 + deviceState = new DeviceState(ctx, config, deviceId, new ProfileState(deviceProfile), rns);
154 deviceStates.put(deviceId, deviceState); 169 deviceStates.put(deviceId, deviceState);
155 } 170 }
156 } 171 }