Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into develop/3.3.3

# Conflicts:
#	application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
#	application/src/main/resources/thingsboard.yml
#	application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
#	common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
#	ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts
pull/5741/head
YevhenBondarenko 5 years ago
parent
commit
f0c2b5df35
  1. 71
      application/src/main/data/upgrade/3.3.2/schema_update.sql
  2. 20
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  3. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  4. 71
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  5. 41
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
  6. 49
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java
  7. 18
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  8. 50
      application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java
  9. 4
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  10. 84
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  11. 7
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  12. 27
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  13. 21
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  14. 84
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  15. 191
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  16. 34
      application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java
  17. 23
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  18. 13
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  19. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  20. 35
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  21. 1
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  22. 8
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  23. 18
      application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java
  24. 95
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  25. 4
      application/src/main/resources/thingsboard.yml
  26. 1
      application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
  27. 226
      application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/sql/BasicMqttCredentialsTest.java
  28. 9
      common/cluster-api/src/main/proto/queue.proto
  29. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  30. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
  31. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  32. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  33. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  34. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  35. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java
  36. 4
      common/data/src/main/java/org/thingsboard/server/common/data/TbProperty.java
  37. 40
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
  38. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java
  39. 1
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
  40. 1
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  41. 45
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java
  42. 44
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainUpdateResult.java
  43. 33
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNodeUpdateResult.java
  44. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  45. 74
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  46. 95
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingCtx.java
  47. 47
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java
  48. 3
      common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java
  49. 19
      common/message/src/main/proto/tbmsg.proto
  50. 5
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  51. 2
      common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java
  52. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  53. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  54. 9
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java
  55. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  56. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  57. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  58. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  59. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  60. 7
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  61. 33
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  62. 8
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  63. 5
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  64. 1
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  65. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  66. 42
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmCompositeKey.java
  67. 99
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java
  68. 16
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  69. 5
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  70. 30
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  71. 2
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
  72. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java
  73. 55
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  74. 32
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java
  75. 22
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  76. 21
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  77. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java
  78. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  79. 15
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java
  80. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
  81. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java
  82. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
  83. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java
  84. 12
      dao/src/main/resources/sql/schema-entities-hsql.sql
  85. 6
      dao/src/main/resources/sql/schema-entities-idx.sql
  86. 12
      dao/src/main/resources/sql/schema-entities.sql
  87. 19
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  88. 4
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java
  89. 1
      dao/src/test/resources/sql/hsql/drop-all-tables.sql
  90. 1
      dao/src/test/resources/sql/psql/drop-all-tables.sql
  91. 1
      dao/src/test/resources/sql/timescale/drop-all-tables.sql
  92. 24
      msa/js-executor/docker/Dockerfile
  93. 6
      msa/js-executor/docker/start-js-executor.sh
  94. 7
      msa/js-executor/package.json
  95. 65
      msa/js-executor/pom.xml
  96. 63
      msa/pom.xml
  97. 1
      msa/tb-node/pom.xml
  98. 144
      msa/tb/pom.xml
  99. 1
      msa/transport/coap/pom.xml
  100. 1
      msa/transport/http/pom.xml

71
application/src/main/data/upgrade/3.3.2/schema_update.sql

@ -0,0 +1,71 @@
--
-- Copyright © 2016-2021 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS entity_alarm (
tenant_id uuid NOT NULL,
entity_type varchar(32),
entity_id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_type varchar(255) NOT NULL,
customer_id uuid,
alarm_id uuid,
CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id),
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_id, status, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id);
INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id)
SELECT tenant_id,
CASE
WHEN originator_type = 0 THEN 'TENANT'
WHEN originator_type = 1 THEN 'CUSTOMER'
WHEN originator_type = 2 THEN 'USER'
WHEN originator_type = 3 THEN 'DASHBOARD'
WHEN originator_type = 4 THEN 'ASSET'
WHEN originator_type = 5 THEN 'DEVICE'
WHEN originator_type = 6 THEN 'ALARM'
WHEN originator_type = 7 THEN 'RULE_CHAIN'
WHEN originator_type = 8 THEN 'RULE_NODE'
WHEN originator_type = 9 THEN 'ENTITY_VIEW'
WHEN originator_type = 10 THEN 'WIDGETS_BUNDLE'
WHEN originator_type = 11 THEN 'WIDGET_TYPE'
WHEN originator_type = 12 THEN 'TENANT_PROFILE'
WHEN originator_type = 13 THEN 'DEVICE_PROFILE'
WHEN originator_type = 14 THEN 'API_USAGE_STATE'
WHEN originator_type = 15 THEN 'TB_RESOURCE'
WHEN originator_type = 16 THEN 'OTA_PACKAGE'
WHEN originator_type = 17 THEN 'EDGE'
WHEN originator_type = 18 THEN 'RPC'
else 'UNKNOWN'
END,
originator_id,
created_time,
type,
customer_id,
id
FROM alarm
ON CONFLICT DO NOTHING;
INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id)
SELECT a.tenant_id, r.from_type, r.from_id, created_time, type, customer_id, id
FROM alarm a
INNER JOIN relation r ON r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id
ON CONFLICT DO NOTHING;
DELETE FROM relation r WHERE r.relation_type_group = 'ALARM';

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

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -134,6 +135,25 @@ class DefaultTbContext implements TbContext {
scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor()); scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor());
} }
@Override
public void input(TbMsg msg, RuleChainId ruleChainId) {
msg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
nodeCtx.getChainActor().tell(new RuleChainInputMsg(ruleChainId, msg));
}
@Override
public void output(TbMsg msg, String relationType) {
TbMsgProcessingStackItem item = msg.popFormStack();
if (item == null) {
ack(msg);
} else {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType);
}
nodeCtx.getChainActor().tell(new RuleChainOutputMsg(item.getRuleChainId(), item.getRuleNodeId(), relationType, msg));
}
}
@Override @Override
public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer<Throwable> onFailure) { public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java

@ -60,6 +60,12 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
case RULE_CHAIN_TO_RULE_CHAIN_MSG: case RULE_CHAIN_TO_RULE_CHAIN_MSG:
processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg); processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
break; break;
case RULE_CHAIN_INPUT_MSG:
processor.onRuleChainInputMsg((RuleChainInputMsg) msg);
break;
case RULE_CHAIN_OUTPUT_MSG:
processor.onRuleChainOutputMsg((RuleChainOutputMsg) msg);
break;
case PARTITION_CHANGE_MSG: case PARTITION_CHANGE_MSG:
processor.onPartitionChangeMsg((PartitionChangeMsg) msg); processor.onPartitionChangeMsg((PartitionChangeMsg) msg);
break; break;

71
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -66,6 +66,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> { public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private static final String NA_RELATION_TYPE = "";
private final TbActorRef parent; private final TbActorRef parent;
private final TbActorRef self; private final TbActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors; private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
@ -201,33 +202,58 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
TbMsg msg = envelope.getMsg(); TbMsg msg = envelope.getMsg();
log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg); log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg);
if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) { if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) {
try { onTellNext(msg, true);
checkActive(envelope.getMsg());
RuleNodeId targetId = msg.getRuleNodeId();
RuleNodeCtx targetCtx;
if (targetId == null) {
targetCtx = firstNode;
msg = msg.copyWithRuleChainId(entityId);
} else {
targetCtx = nodeActors.get(targetId);
}
if (targetCtx != null) {
log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
pushMsgToNode(targetCtx, msg, "");
} else {
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess();
}
} catch (RuleNodeException rne) {
envelope.getMsg().getCallback().onFailure(rne);
} catch (Exception e) {
envelope.getMsg().getCallback().onFailure(new RuleEngineException(e.getMessage()));
}
} else { } else {
onTellNext(envelope.getMsg(), envelope.getMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage()); onTellNext(envelope.getMsg(), envelope.getMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage());
} }
} }
private void onTellNext(TbMsg msg, boolean useRuleNodeIdFromMsg) {
try {
checkActive(msg);
RuleNodeId targetId = useRuleNodeIdFromMsg ? msg.getRuleNodeId() : null;
RuleNodeCtx targetCtx;
if (targetId == null) {
targetCtx = firstNode;
msg = msg.copyWithRuleChainId(entityId);
} else {
targetCtx = nodeActors.get(targetId);
}
if (targetCtx != null) {
log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
pushMsgToNode(targetCtx, msg, NA_RELATION_TYPE);
} else {
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess();
}
} catch (RuleNodeException rne) {
msg.getCallback().onFailure(rne);
} catch (Exception e) {
msg.getCallback().onFailure(new RuleEngineException(e.getMessage()));
}
}
public void onRuleChainInputMsg(RuleChainInputMsg envelope) {
if (entityId.equals(envelope.getRuleChainId())) {
onTellNext(envelope.getMsg(), false);
} else {
parent.tell(envelope);
}
}
public void onRuleChainOutputMsg(RuleChainOutputMsg envelope) {
if (entityId.equals(envelope.getRuleChainId())) {
var originatorNodeId = envelope.getTargetRuleNodeId();
RuleNodeCtx ruleNodeCtx = nodeActors.get(originatorNodeId);
if (ruleNodeCtx != null && ruleNodeCtx.getSelf().isDebugMode()) {
systemContext.persistDebugOutput(tenantId, originatorNodeId, envelope.getMsg(), envelope.getRelationType());
}
onTellNext(envelope.getMsg(), originatorNodeId, Collections.singleton(envelope.getRelationType()), RuleNodeException.UNKNOWN);
} else {
parent.tell(envelope);
}
}
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) { void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
try { try {
checkActive(envelope.getMsg()); checkActive(envelope.getMsg());
@ -356,4 +382,5 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null; RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;
return new RuleNodeException("Rule Chain is not active! Failed to initialize.", ruleChainName, firstRuleNode); return new RuleNodeException("Rule Chain is not active! Failed to initialize.", ruleChainName, firstRuleNode);
} }
} }

41
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainInputMsg extends TbToRuleChainActorMsg {
public RuleChainInputMsg(RuleChainId target, TbMsg tbMsg) {
super(tbMsg, target);
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_INPUT_MSG;
}
}

49
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainOutputMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleNodeId targetRuleNodeId;
@Getter
private final String relationType;
public RuleChainOutputMsg(RuleChainId target, RuleNodeId targetRuleNodeId, String relationType, TbMsg tbMsg) {
super(tbMsg, target);
this.targetRuleNodeId = targetRuleNodeId;
this.relationType = relationType;
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_OUTPUT_MSG;
}
}

18
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java

@ -31,33 +31,19 @@ import org.thingsboard.server.common.msg.queue.RuleEngineException;
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString @ToString
public final class RuleChainToRuleChainMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg { public final class RuleChainToRuleChainMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleChainId target;
@Getter @Getter
private final RuleChainId source; private final RuleChainId source;
@Getter @Getter
private final String fromRelationType; private final String fromRelationType;
public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) { public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) {
super(tbMsg); super(tbMsg, target);
this.target = target;
this.source = source; this.source = source;
this.fromRelationType = fromRelationType; this.fromRelationType = fromRelationType;
} }
@Override
public void onTbActorStopped(TbActorStopReason reason) {
String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
msg.getCallback().onFailure(new RuleEngineException(message));
}
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override @Override
public MsgType getMsgType() { public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG; return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;

50
application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
@EqualsAndHashCode(callSuper = true)
@ToString
public abstract class TbToRuleChainActorMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg {
@Getter
private final RuleChainId target;
public TbToRuleChainActorMsg(TbMsg msg, RuleChainId target) {
super(msg);
this.target = target;
}
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override
public void onTbActorStopped(TbActorStopReason reason) {
String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
msg.getCallback().onFailure(new RuleEngineException(message));
}
}

4
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -26,7 +26,9 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate; import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator; import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.ruleChain.RuleChainInputMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -168,6 +170,8 @@ public class TenantActor extends RuleChainManagerActor {
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG: case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg, true); onToDeviceActorMsg((DeviceAwareMsg) msg, true);
break; break;
case RULE_CHAIN_INPUT_MSG:
case RULE_CHAIN_OUTPUT_MSG:
case RULE_CHAIN_TO_RULE_CHAIN_MSG: case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg); onRuleChainMsg((RuleChainAwareMsg) msg);
break; break;

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

@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -43,6 +42,7 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -60,7 +60,9 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
@ -68,15 +70,19 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.rule.TbRuleChainService;
import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -137,6 +143,9 @@ public class RuleChainController extends BaseController {
+ MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END
+ "\n\n Expected result JSON contains \"output\" and \"error\"."; + "\n\n Expected result JSON contains \"output\" and \"error\".";
@Autowired
protected TbRuleChainService tbRuleChainService;
@Autowired @Autowired
private InstallScripts installScripts; private InstallScripts installScripts;
@ -169,6 +178,44 @@ public class RuleChainController extends BaseController {
} }
} }
@ApiOperation(value = "Get Rule Chain output labels (getRuleChainOutputLabels)",
notes = "Fetch the unique labels for the \"output\" Rule Nodes that belong to the Rule Chain based on the provided Rule Chain Id. "
+ RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels", method = RequestMethod.GET)
@ResponseBody
public Set<String> getRuleChainOutputLabels(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
return tbRuleChainService.getRuleChainOutputLabels(getTenantId(), ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get output labels usage (getRuleChainOutputLabelsUsage)",
notes = "Fetch the list of rule chains and the relation types (labels) they use to process output of the current rule chain based on the provided Rule Chain Id. "
+ RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels/usage", method = RequestMethod.GET)
@ResponseBody
public List<RuleChainOutputLabelsUsage> getRuleChainOutputLabelsUsage(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
return tbRuleChainService.getOutputLabelUsage(getCurrentUser().getTenantId(), ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get Rule Chain (getRuleChainById)", @ApiOperation(value = "Get Rule Chain (getRuleChainById)",
notes = "Fetch the Rule Chain Metadata object based on the provided Rule Chain Id. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH) notes = "Fetch the Rule Chain Metadata object based on the provided Rule Chain Id. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@ -310,7 +357,10 @@ public class RuleChainController extends BaseController {
@ResponseBody @ResponseBody
public RuleChainMetaData saveRuleChainMetaData( public RuleChainMetaData saveRuleChainMetaData(
@ApiParam(value = "A JSON value representing the rule chain metadata.") @ApiParam(value = "A JSON value representing the rule chain metadata.")
@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException { @RequestBody RuleChainMetaData ruleChainMetaData,
@ApiParam(value = "Update related rule nodes.")
@RequestParam(value = "updateRelated", required = false, defaultValue = "true") boolean updateRelated
) throws ThingsboardException {
try { try {
TenantId tenantId = getTenantId(); TenantId tenantId = getTenantId();
if (debugPerTenantEnabled) { if (debugPerTenantEnabled) {
@ -322,20 +372,36 @@ public class RuleChainController extends BaseController {
} }
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE); RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData) ? true : null); RuleChainUpdateResult result = ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData);
checkNotNull(result.isSuccess() ? true : null);
List<RuleChain> updatedRuleChains;
if (updateRelated && result.isSuccess()) {
updatedRuleChains = tbRuleChainService.updateRelatedRuleChains(tenantId, ruleChainMetaData.getRuleChainId(), result);
} else {
updatedRuleChains = Collections.emptyList();
}
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId())); RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()));
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
updatedRuleChains.forEach(updatedRuleChain -> {
tbClusterService.broadcastEntityStateChangeEvent(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED);
});
} }
logEntityAction(ruleChain.getId(), ruleChain, logEntityAction(ruleChain.getId(), ruleChain, null, ActionType.UPDATED, null, ruleChainMetaData);
null, for (RuleChain updatedRuleChain : updatedRuleChains) {
ActionType.UPDATED, null, ruleChainMetaData); RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId()));
logEntityAction(updatedRuleChain.getId(), updatedRuleChain, null, ActionType.UPDATED, null, updatedRuleChainMetaData);
}
if (RuleChainType.EDGE.equals(ruleChain.getType())) { if (RuleChainType.EDGE.equals(ruleChain.getType())) {
sendEntityNotificationMsg(ruleChain.getTenantId(), sendEntityNotificationMsg(ruleChain.getTenantId(), ruleChain.getId(), EdgeEventActionType.UPDATED);
ruleChain.getId(), EdgeEventActionType.UPDATED); updatedRuleChains.forEach(updatedRuleChain -> {
sendEntityNotificationMsg(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), EdgeEventActionType.UPDATED);
});
} }
return savedRuleChainMetaData; return savedRuleChainMetaData;
@ -681,7 +747,7 @@ public class RuleChainController extends BaseController {
} }
@ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)", @ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)",
notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

7
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -210,13 +210,12 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ..."); log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ...");
case "3.3.1": case "3.3.1":
log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ..."); log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ...");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
case "3.3.2": case "3.3.2":
log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ..."); log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ...");
log.info("Updating system data...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.2"); databaseEntitiesUpgradeService.upgradeDatabase("3.3.2");
dataUpdateService.updateData("3.3.2");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break; break;
//TODO update CacheCleanupService on the next version upgrade //TODO update CacheCleanupService on the next version upgrade

27
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -152,24 +152,14 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
try { try {
scannedComponent.setType(type); scannedComponent.setType(type);
Class<?> clazz = Class.forName(clazzName); Class<?> clazz = Class.forName(clazzName);
switch (type) { RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
case ENRICHMENT: scannedComponent.setName(ruleNodeAnnotation.name());
case FILTER: scannedComponent.setScope(ruleNodeAnnotation.scope());
case TRANSFORMATION: NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
case ACTION: ObjectNode configurationDescriptor = mapper.createObjectNode();
case EXTERNAL: JsonNode node = mapper.valueToTree(nodeDefinition);
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setConfigurationDescriptor(configurationDescriptor);
scannedComponent.setScope(ruleNodeAnnotation.scope());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setConfigurationDescriptor(configurationDescriptor);
break;
default:
throw new RuntimeException(type + " is not supported yet!");
}
scannedComponent.setClazz(clazzName); scannedComponent.setClazz(clazzName);
log.info("Processing scanned component: {}", scannedComponent); log.info("Processing scanned component: {}", scannedComponent);
} catch (Exception e) { } catch (Exception e) {
@ -200,6 +190,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled());
nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation)); nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation));
nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
nodeDefinition.setRuleChainNode(nodeAnnotation.ruleChainNode());
Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz(); Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance(); NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration(); NodeConfiguration defaultConfiguration = config.defaultConfiguration();

21
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -43,6 +43,7 @@ import java.sql.SQLSyntaxErrorException;
import java.sql.SQLWarning; import java.sql.SQLWarning;
import java.sql.Statement; import java.sql.Statement;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS; import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
@ -472,7 +473,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
case "3.3.2": case "3.3.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ..."); log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", "schema_update_lwm2m_bootstrap.sql"); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
log.info("Updating server`s public key from HexDec to Base64 in profile for LWM2M..."); log.info("Updating server`s public key from HexDec to Base64 in profile for LWM2M...");
conn.createStatement().execute("call update_profile_bootstrap();"); conn.createStatement().execute("call update_profile_bootstrap();");
@ -480,6 +481,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Updating client`s public key and secret key from HexDec to Base64 for LWM2M..."); log.info("Updating client`s public key and secret key from HexDec to Base64 for LWM2M...");
conn.createStatement().execute("call update_device_credentials_to_base64_and_bootstrap();"); conn.createStatement().execute("call update_device_credentials_to_base64_and_bootstrap();");
log.info("Client`s public key and secret key from HexDec to Base64 for LWM2M updated."); log.info("Client`s public key and secret key from HexDec to Base64 for LWM2M updated.");
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;"); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;");
log.info("Schema updated."); log.info("Schema updated.");
} catch (Exception e) { } catch (Exception e) {
@ -493,10 +495,25 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
private void loadSql(Path sqlFile, Connection conn) throws Exception { private void loadSql(Path sqlFile, Connection conn) throws Exception {
String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8")); String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script Statement st = conn.createStatement();
st.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(3));
st.execute(sql);//NOSONAR, ignoring because method used to execute thingsboard database upgrade script
printWarnings(st);
Thread.sleep(5000); Thread.sleep(5000);
} }
protected void printWarnings(Statement statement) throws SQLException {
SQLWarning warnings = statement.getWarnings();
if (warnings != null) {
log.info("{}", warnings.getMessage());
SQLWarning nextWarning = warnings.getNextWarning();
while (nextWarning != null) {
log.info("{}", nextWarning.getMessage());
nextWarning = nextWarning.getNextWarning();
}
}
}
protected boolean isOldSchema(Connection conn, long fromVersion) { protected boolean isOldSchema(Connection conn, long fromVersion) {
boolean isOldSchema = true; boolean isOldSchema = true;
try { try {

84
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
@ -34,6 +36,8 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@ -43,8 +47,11 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
@ -52,7 +59,9 @@ import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -76,6 +85,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
private RelationService relationService;
@Autowired @Autowired
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@ -125,6 +137,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null); deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params(); updateOAuth2Params();
break; break;
case "3.3.2":
log.info("Updating data from version 3.3.2 to 3.3.3 ...");
updateNestedRuleChains();
break;
default: default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
} }
@ -209,6 +225,74 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
}; };
private void updateNestedRuleChains() {
try {
var packSize = 1024;
var updated = 0;
boolean hasNext = true;
while (hasNext) {
List<EntityRelation> relations = relationService.findRuleNodeToRuleChainRelations(TenantId.SYS_TENANT_ID, RuleChainType.CORE, packSize);
hasNext = relations.size() == packSize;
for (EntityRelation relation : relations) {
try {
RuleNodeId sourceNodeId = new RuleNodeId(relation.getFrom().getId());
RuleNode sourceNode = ruleChainService.findRuleNodeById(TenantId.SYS_TENANT_ID, sourceNodeId);
if (sourceNode == null) {
log.info("Skip processing of relation for non existing source rule node: [{}]", sourceNodeId);
relationService.deleteRelation(TenantId.SYS_TENANT_ID, relation);
continue;
}
RuleChainId sourceRuleChainId = sourceNode.getRuleChainId();
RuleChainId targetRuleChainId = new RuleChainId(relation.getTo().getId());
RuleChain targetRuleChain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, targetRuleChainId);
if (targetRuleChain == null) {
log.info("Skip processing of relation for non existing target rule chain: [{}]", targetRuleChainId);
relationService.deleteRelation(TenantId.SYS_TENANT_ID, relation);
continue;
}
TenantId tenantId = targetRuleChain.getTenantId();
RuleNode targetNode = new RuleNode();
targetNode.setName(targetRuleChain.getName());
targetNode.setRuleChainId(sourceRuleChainId);
targetNode.setType(TbRuleChainInputNode.class.getName());
TbRuleChainInputNodeConfiguration configuration = new TbRuleChainInputNodeConfiguration();
configuration.setRuleChainId(targetRuleChain.getId().toString());
targetNode.setConfiguration(JacksonUtil.valueToTree(configuration));
targetNode.setAdditionalInfo(relation.getAdditionalInfo());
targetNode.setDebugMode(false);
targetNode = ruleChainService.saveRuleNode(tenantId, targetNode);
EntityRelation sourceRuleChainToRuleNode = new EntityRelation();
sourceRuleChainToRuleNode.setFrom(sourceRuleChainId);
sourceRuleChainToRuleNode.setTo(targetNode.getId());
sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE);
sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN);
relationService.saveRelation(tenantId, sourceRuleChainToRuleNode);
EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation();
sourceRuleNodeToTargetRuleNode.setFrom(sourceNode.getId());
sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId());
sourceRuleNodeToTargetRuleNode.setType(relation.getType());
sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE);
sourceRuleNodeToTargetRuleNode.setAdditionalInfo(relation.getAdditionalInfo());
relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode);
//Delete old relation
relationService.deleteRelation(tenantId, relation);
updated++;
} catch (Exception e) {
log.info("Failed to update RuleNodeToRuleChainRelation: {}", relation, e);
}
}
if (updated > 0) {
log.info("RuleNodeToRuleChainRelations: {} entities updated so far...", updated);
}
}
} catch (Exception e) {
log.error("Unable to update Tenant", e);
}
}
private final PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {

191
application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java

@ -0,0 +1,191 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rule;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
@TbCoreComponent
@Slf4j
public class DefaultTbRuleChainService implements TbRuleChainService {
private final RuleChainService ruleChainService;
private final RelationService relationService;
@Override
public Set<String> getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId) {
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId);
Set<String> outputLabels = new TreeSet<>();
for (RuleNode ruleNode : metaData.getNodes()) {
if (isOutputRuleNode(ruleNode)) {
outputLabels.add(ruleNode.getName());
}
}
return outputLabels;
}
@Override
public List<RuleChainOutputLabelsUsage> getOutputLabelUsage(TenantId tenantId, RuleChainId ruleChainId) {
List<RuleNode> ruleNodes = ruleChainService.findRuleNodesByTenantIdAndType(tenantId, TbRuleChainInputNode.class.getName(), ruleChainId.getId().toString());
Map<RuleChainId, String> ruleChainNamesCache = new HashMap<>();
// Additional filter, "just in case" the structure of the JSON configuration will change.
var filteredRuleNodes = ruleNodes.stream().filter(node -> {
try {
TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class);
return ruleChainId.getId().toString().equals(configuration.getRuleChainId());
} catch (Exception e) {
log.warn("[{}][{}] Failed to decode rule node configuration", tenantId, ruleChainId, e);
return false;
}
}).collect(Collectors.toList());
return filteredRuleNodes.stream()
.map(ruleNode -> {
RuleChainOutputLabelsUsage usage = new RuleChainOutputLabelsUsage();
usage.setRuleNodeId(ruleNode.getId());
usage.setRuleNodeName(ruleNode.getName());
usage.setRuleChainId(ruleNode.getRuleChainId());
List<EntityRelation> relations = ruleChainService.getRuleNodeRelations(tenantId, ruleNode.getId());
if (relations != null && !relations.isEmpty()) {
usage.setLabels(relations.stream().map(EntityRelation::getType).collect(Collectors.toSet()));
}
return usage;
})
.filter(usage -> usage.getLabels() != null)
.peek(usage -> {
String ruleChainName = ruleChainNamesCache.computeIfAbsent(usage.getRuleChainId(),
id -> ruleChainService.findRuleChainById(tenantId, id).getName());
usage.setRuleChainName(ruleChainName);
})
.sorted(Comparator
.comparing(RuleChainOutputLabelsUsage::getRuleChainName)
.thenComparing(RuleChainOutputLabelsUsage::getRuleNodeName))
.collect(Collectors.toList());
}
@Override
public List<RuleChain> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result) {
Set<RuleChainId> ruleChainIds = new HashSet<>();
log.debug("[{}][{}] Going to update links in related rule chains", tenantId, ruleChainId);
if (result.getUpdatedRuleNodes() == null || result.getUpdatedRuleNodes().isEmpty()) {
return Collections.emptyList();
}
Set<String> oldLabels = new HashSet<>();
Set<String> newLabels = new HashSet<>();
Set<String> confusedLabels = new HashSet<>();
Map<String, String> updatedLabels = new HashMap<>();
for (RuleNodeUpdateResult update : result.getUpdatedRuleNodes()) {
var oldNode = update.getOldRuleNode();
var newNode = update.getNewRuleNode();
if (isOutputRuleNode(newNode)) {
try {
oldLabels.add(oldNode.getName());
newLabels.add(newNode.getName());
if (!oldNode.getName().equals(newNode.getName())) {
String oldLabel = oldNode.getName();
String newLabel = newNode.getName();
if (updatedLabels.containsKey(oldLabel) && !updatedLabels.get(oldLabel).equals(newLabel)) {
confusedLabels.add(oldLabel);
log.warn("[{}][{}] Can't automatically rename the label from [{}] to [{}] due to conflict [{}]", tenantId, ruleChainId, oldLabel, newLabel, updatedLabels.get(oldLabel));
} else {
updatedLabels.put(oldLabel, newLabel);
}
}
} catch (Exception e) {
log.warn("[{}][{}][{}] Failed to decode rule node configuration", tenantId, ruleChainId, newNode.getId(), e);
}
}
}
// Remove all output labels that are renamed to two or more different labels, since we don't which new label to use;
confusedLabels.forEach(updatedLabels::remove);
// Remove all output labels that are renamed but still present in the rule chain;
newLabels.forEach(updatedLabels::remove);
if (!oldLabels.equals(newLabels)) {
ruleChainIds.addAll(updateRelatedRuleChains(tenantId, ruleChainId, updatedLabels));
}
return ruleChainIds.stream().map(id -> ruleChainService.findRuleChainById(tenantId, id)).collect(Collectors.toList());
}
public Set<RuleChainId> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, Map<String, String> labelsMap) {
Set<RuleChainId> updatedRuleChains = new HashSet<>();
List<RuleChainOutputLabelsUsage> usageList = getOutputLabelUsage(tenantId, ruleChainId);
for (RuleChainOutputLabelsUsage usage : usageList) {
labelsMap.forEach((oldLabel, newLabel) -> {
if (usage.getLabels().contains(oldLabel)) {
updatedRuleChains.add(usage.getRuleChainId());
renameOutgoingLinks(tenantId, usage.getRuleNodeId(), oldLabel, newLabel);
}
});
}
return updatedRuleChains;
}
private void renameOutgoingLinks(TenantId tenantId, RuleNodeId ruleNodeId, String oldLabel, String newLabel) {
List<EntityRelation> relations = ruleChainService.getRuleNodeRelations(tenantId, ruleNodeId);
for (EntityRelation relation : relations) {
if (relation.getType().equals(oldLabel)) {
relationService.deleteRelation(tenantId, relation);
relation.setType(newLabel);
relationService.saveRelation(tenantId, relation);
}
}
}
private boolean isOutputRuleNode(RuleNode ruleNode) {
return isRuleNode(ruleNode, TbRuleChainOutputNode.class);
}
private boolean isInputRuleNode(RuleNode ruleNode) {
return isRuleNode(ruleNode, TbRuleChainInputNode.class);
}
private boolean isRuleNode(RuleNode ruleNode, Class<?> clazz) {
return ruleNode != null && ruleNode.getType().equals(clazz.getName());
}
}

34
application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rule;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import java.util.List;
import java.util.Set;
public interface TbRuleChainService {
Set<String> getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId);
List<RuleChainOutputLabelsUsage> getOutputLabelUsage(TenantId tenantId, RuleChainId ruleChainId);
List<RuleChain> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result);
}

23
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -222,7 +222,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
} }
return subscriptionUpdate; return subscriptionUpdate;
}); }, true);
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
updateDeviceInactivityTimeout(tenantId, entityId, ts); updateDeviceInactivityTimeout(tenantId, entityId, ts);
} }
@ -256,7 +256,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
} }
return subscriptionUpdate; return subscriptionUpdate;
}); }, true);
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) { if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) {
updateDeviceInactivityTimeout(tenantId, entityId, attributes); updateDeviceInactivityTimeout(tenantId, entityId, attributes);
@ -333,14 +333,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
} }
return subscriptionUpdate; return subscriptionUpdate;
}); }, false);
callback.onSuccess(); callback.onSuccess();
} }
private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId, private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId,
Function<TbSubscription, T> castFunction, Function<TbSubscription, T> castFunction,
Predicate<T> filterFunction, Predicate<T> filterFunction,
Function<T, List<TsKvEntry>> processFunction) { Function<T, List<TsKvEntry>> processFunction,
boolean ignoreEmptyUpdates) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId); Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (entitySubscriptions != null) { if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
@ -351,7 +352,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else { } else {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
toCoreNotificationsProducer.send(tpi, toProto(s, subscriptionUpdate), null); toCoreNotificationsProducer.send(tpi, toProto(s, subscriptionUpdate, ignoreEmptyUpdates), null);
} }
} }
}); });
@ -467,6 +468,10 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates) { private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates) {
return toProto(subscription, updates, true);
}
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates, boolean ignoreEmptyUpdates) {
TbSubscriptionUpdateProto.Builder builder = TbSubscriptionUpdateProto.newBuilder(); TbSubscriptionUpdateProto.Builder builder = TbSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId()); builder.setSessionId(subscription.getSessionId());
@ -487,14 +492,16 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
boolean hasData = false; boolean hasData = false;
for (Object v : value) { for (Object v : value) {
Object[] array = (Object[]) v; Object[] array = (Object[]) v;
dataBuilder.addTs((long) array[0]); TbSubscriptionUpdateTsValue.Builder tsValueBuilder = TbSubscriptionUpdateTsValue.newBuilder();
tsValueBuilder.setTs((long) array[0]);
String strVal = (String) array[1]; String strVal = (String) array[1];
if (strVal != null) { if (strVal != null) {
hasData = true; hasData = true;
dataBuilder.addValue(strVal); tsValueBuilder.setValue(strVal);
} }
dataBuilder.addTsValue(tsValueBuilder.build());
} }
if (hasData) { if (!ignoreEmptyUpdates || hasData) {
builder.addData(dataBuilder.build()); builder.addData(dataBuilder.build());
} }
}); });

13
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -36,16 +36,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -68,7 +65,6 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -83,8 +79,6 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -133,6 +127,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private int maxEntitiesPerDataSubscription; private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") @Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription; private int maxEntitiesPerAlarmSubscription;
@Value("${server.ws.max_alarm_queries_per_refresh_interval:3}")
private int maxAlarmQueriesPerRefreshInterval;
private ExecutorService wsCallBackExecutor; private ExecutorService wsCallBackExecutor;
private boolean tsInSqlDB; private boolean tsInSqlDB;
@ -282,7 +278,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
if (adq.getPageLink().getTimeWindow() > 0) { if (adq.getPageLink().getTimeWindow() > 0) {
TbAlarmDataSubCtx finalCtx = ctx; TbAlarmDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
finalCtx::cleanupOldAlarms, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); finalCtx::checkAndResetInvocationCounter, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task); finalCtx.setRefreshTask(task);
} }
} }
@ -345,7 +341,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription,
maxAlarmQueriesPerRefreshInterval);
ctx.setAndResolveQuery(cmd.getQuery()); ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx); sessionSubs.put(cmd.getCmdId(), ctx);
return ctx; return ctx;

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -57,6 +57,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
this.subToEntityIdMap = new ConcurrentHashMap<>(); this.subToEntityIdMap = new ConcurrentHashMap<>();
} }
@Override
public void fetchData() { public void fetchData() {
this.data = findEntityData(); this.data = findEntityData();
} }
@ -71,6 +72,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
return result; return result;
} }
@Override
protected synchronized void update() { protected synchronized void update() {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
PageData<EntityData> newData = findEntityData(); PageData<EntityData> newData = findEntityData();

35
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -68,6 +68,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
private final int maxEntitiesPerAlarmSubscription; private final int maxEntitiesPerAlarmSubscription;
private final int maxAlarmQueriesPerRefreshInterval;
@Getter @Getter
@Setter @Setter
private PageData<AlarmData> alarms; private PageData<AlarmData> alarms;
@ -75,18 +77,30 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
@Setter @Setter
private boolean tooManyEntities; private boolean tooManyEntities;
private int alarmInvocationAttempts;
public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService, public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService,
TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerAlarmSubscription) { TelemetryWebSocketSessionRef sessionRef, int cmdId,
int maxEntitiesPerAlarmSubscription, int maxAlarmQueriesPerRefreshInterval) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription; this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription;
this.maxAlarmQueriesPerRefreshInterval = maxAlarmQueriesPerRefreshInterval;
this.alarmService = alarmService; this.alarmService = alarmService;
this.entitiesMap = new LinkedHashMap<>(); this.entitiesMap = new LinkedHashMap<>();
this.alarmsMap = new HashMap<>(); this.alarmsMap = new HashMap<>();
} }
public void fetchAlarms() { public void fetchAlarms() {
alarmInvocationAttempts++;
log.trace("[{}] Fetching alarms: {}", cmdId, alarmInvocationAttempts);
if (alarmInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) {
doFetchAlarms();
}
}
private void doFetchAlarms() {
AlarmDataUpdate update; AlarmDataUpdate update;
if (!entitiesMap.isEmpty()) { if (!entitiesMap.isEmpty()) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
@ -103,6 +117,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
} }
public void fetchData() { public void fetchData() {
resetInvocationCounter();
log.trace("[{}] Fetching data: {}", cmdId, alarmInvocationAttempts);
super.fetchData(); super.fetchData();
entitiesMap.clear(); entitiesMap.clear();
tooManyEntities = data.hasNext(); tooManyEntities = data.hasNext();
@ -222,7 +238,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
} }
} }
if (shouldRefresh) { if (shouldRefresh) {
fetchAlarms(); doFetchAlarms();
} }
} }
@ -256,8 +272,19 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
return true; return true;
} }
public synchronized void checkAndResetInvocationCounter() {
boolean fetchNeeded = this.alarmInvocationAttempts > maxAlarmQueriesPerRefreshInterval;
resetInvocationCounter();
if (fetchNeeded) {
fetchAlarms();
} else {
cleanupOldAlarms();
}
}
@Override @Override
protected synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) { protected synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
resetInvocationCounter();
entitiesMap.clear(); entitiesMap.clear();
tooManyEntities = data.hasNext(); tooManyEntities = data.hasNext();
for (EntityData entityData : data.getData()) { for (EntityData entityData : data.getData()) {
@ -295,6 +322,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
subsToAdd.forEach(localSubscriptionService::addSubscription); subsToAdd.forEach(localSubscriptionService::addSubscription);
} }
private void resetInvocationCounter() {
alarmInvocationAttempts = 0;
}
@Override @Override
protected EntityDataQuery buildEntityDataQuery() { protected EntityDataQuery buildEntityDataQuery() {
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder();

1
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -165,6 +165,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null); return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null);
} }
@Override
public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) { public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
List<Integer> subIdsToCancel = new ArrayList<>(); List<Integer> subIdsToCancel = new ArrayList<>();
List<TbSubscription> subsToAdd = new ArrayList<>(); List<TbSubscription> subsToAdd = new ArrayList<>();

8
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateTsValue;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
@ -171,10 +172,11 @@ public class TbSubscriptionUtils {
Map<String, List<Object>> data = new TreeMap<>(); Map<String, List<Object>> data = new TreeMap<>();
proto.getDataList().forEach(v -> { proto.getDataList().forEach(v -> {
List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>()); List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>());
for (int i = 0; i < v.getTsCount(); i++) { for (int i = 0; i < v.getTsValueCount(); i++) {
Object[] value = new Object[2]; Object[] value = new Object[2];
value[0] = v.getTs(i); TbSubscriptionUpdateTsValue tsValue = v.getTsValue(i);
value[1] = v.getValue(i); value[0] = tsValue.getTs();
value[1] = tsValue.hasValue() ? tsValue.getValue() : null;
values.add(value); values.add(value);
} }
}); });

18
application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java

@ -0,0 +1,18 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.transport;
enum BasicCredentialsValidationResult {HASH_MISMATCH, PASSWORD_MISMATCH, VALID}

95
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -25,7 +25,6 @@ import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
@ -107,6 +107,9 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
/** /**
* Created by ashvayka on 05.10.18. * Created by ashvayka on 05.10.18.
*/ */
@ -181,71 +184,89 @@ public class DefaultTransportApiService implements TransportApiService {
//TODO: Make async and enable caching //TODO: Make async and enable caching
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId);
if (credentials != null && credentials.getCredentialsType() == credentialsType) { if (credentials != null && credentials.getCredentialsType() == credentialsType) {
return getDeviceInfo(credentials.getDeviceId(), credentials); return getDeviceInfo(credentials);
} else { } else {
return getEmptyTransportApiResponseFuture(); return getEmptyTransportApiResponseFuture();
} }
} }
private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = null; DeviceCredentials credentials;
if (!StringUtils.isEmpty(mqtt.getUserName())) { if (StringUtils.isEmpty(mqtt.getUserName())) {
credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
if (credentials != null) { if (credentials != null) {
if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { return getDeviceInfo(credentials);
return getDeviceInfo(credentials.getDeviceId(), credentials); } else {
} else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { return getEmptyTransportApiResponseFuture();
if (!checkMqttCredentials(mqtt, credentials)) { }
credentials = null; } else {
} credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(
} else { EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
if (checkIsMqttCredentials(credentials)) {
var validationResult = validateMqttCredentials(mqtt, credentials);
if (VALID.equals(validationResult)) {
return getDeviceInfo(credentials);
} else if (PASSWORD_MISMATCH.equals(validationResult)) {
return getEmptyTransportApiResponseFuture(); return getEmptyTransportApiResponseFuture();
} else {
return validateUserNameCredentials(mqtt);
} }
} else {
return validateUserNameCredentials(mqtt);
} }
if (credentials == null) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
}
}
if (credentials == null) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
} }
}
private ListenableFuture<TransportApiResponseMsg> validateUserNameCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
if (credentials != null) { if (credentials != null) {
return getDeviceInfo(credentials.getDeviceId(), credentials); switch (credentials.getCredentialsType()) {
} else { case ACCESS_TOKEN:
return getEmptyTransportApiResponseFuture(); return getDeviceInfo(credentials);
case MQTT_BASIC:
if (VALID.equals(validateMqttCredentials(mqtt, credentials))) {
return getDeviceInfo(credentials);
} else {
return getEmptyTransportApiResponseFuture();
}
}
} }
return getEmptyTransportApiResponseFuture();
}
private static boolean checkIsMqttCredentials(DeviceCredentials credentials) {
return credentials != null && DeviceCredentialsType.MQTT_BASIC.equals(credentials.getCredentialsType());
} }
private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) { private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId); return checkMqttCredentials(clientCred, deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId));
}
private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) {
if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
if (!checkMqttCredentials(clientCred, deviceCredentials)) { if (VALID.equals(validateMqttCredentials(clientCred, deviceCredentials))) {
return null;
} else {
return deviceCredentials; return deviceCredentials;
} }
} }
return null; return null;
} }
private boolean checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) { private BasicCredentialsValidationResult validateMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) {
BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class); BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class);
if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) { if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) {
return false; return BasicCredentialsValidationResult.HASH_MISMATCH;
} }
if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) { if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) {
return false; return BasicCredentialsValidationResult.HASH_MISMATCH;
} }
if (!StringUtils.isEmpty(dbCred.getPassword())) { if (!StringUtils.isEmpty(dbCred.getPassword())) {
if (StringUtils.isEmpty(clientCred.getPassword())) { if (StringUtils.isEmpty(clientCred.getPassword())) {
return false; return BasicCredentialsValidationResult.PASSWORD_MISMATCH;
} else { } else {
if (!dbCred.getPassword().equals(clientCred.getPassword())) { return dbCred.getPassword().equals(clientCred.getPassword()) ? VALID : BasicCredentialsValidationResult.PASSWORD_MISMATCH;
return false;
}
} }
} }
return true; return VALID;
} }
private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) {
@ -437,10 +458,10 @@ public class DefaultTransportApiService implements TransportApiService {
.build()); .build());
} }
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> { return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, credentials.getDeviceId()), device -> {
if (device == null) { if (device == null) {
log.trace("[{}] Failed to lookup device by id", deviceId); log.trace("[{}] Failed to lookup device by id", credentials.getDeviceId());
return getEmptyTransportApiResponse(); return getEmptyTransportApiResponse();
} }
try { try {
@ -458,7 +479,7 @@ public class DefaultTransportApiService implements TransportApiService {
return TransportApiResponseMsg.newBuilder() return TransportApiResponseMsg.newBuilder()
.setValidateCredResponseMsg(builder.build()).build(); .setValidateCredResponseMsg(builder.build()).build();
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by id", deviceId, e); log.warn("[{}] Failed to lookup device by id", credentials.getDeviceId(), e);
return getEmptyTransportApiResponse(); return getEmptyTransportApiResponse();
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());

4
application/src/main/resources/thingsboard.yml

@ -70,6 +70,7 @@ server:
dynamic_page_link: dynamic_page_link:
refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}" refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}"
refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}" refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}"
max_alarm_queries_per_refresh_interval: "${TB_SERVER_WS_MAX_ALARM_QUERIES_PER_REFRESH_INTERVAL:3}"
max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}" max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}"
max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}" max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}"
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}" max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}"
@ -821,6 +822,9 @@ transport:
paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}" paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}"
# Use redis for Security and Registration stores # Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}" redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside
# - key: "PROTOCOL_STAGE_THREAD_COUNT"
# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}"
snmp: snmp:
enabled: "${SNMP_ENABLED:true}" enabled: "${SNMP_ENABLED:true}"
response_processing: response_processing:

1
application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java

@ -33,6 +33,7 @@ import java.util.Arrays;
"org.thingsboard.server.transport.*.attributes.request.sql.*Test", "org.thingsboard.server.transport.*.attributes.request.sql.*Test",
"org.thingsboard.server.transport.*.claim.sql.*Test", "org.thingsboard.server.transport.*.claim.sql.*Test",
"org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.*.provision.sql.*Test",
"org.thingsboard.server.transport.*.credentials.sql.*Test",
"org.thingsboard.server.transport.lwm2m.*.sql.*Test" "org.thingsboard.server.transport.lwm2m.*.sql.*Test"
}) })
public class TransportSqlTestSuite { public class TransportSqlTestSuite {

226
application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/sql/BasicMqttCredentialsTest.java

@ -0,0 +1,226 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.mqtt.credentials.sql;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest {
public static final String CLIENT_ID = "ClientId";
public static final String USER_NAME1 = "UserName1";
public static final String USER_NAME2 = "UserName2";
public static final String USER_NAME3 = "UserName3";
public static final String PASSWORD = "secret";
private Device clientIdDevice;
private Device clientIdAndUserNameDevice1;
private Device clientIdAndUserNameAndPasswordDevice2;
private Device clientIdAndUserNameAndPasswordDevice3;
private Device accessTokenDevice;
private Device accessToken2Device;
@Before
public void before() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
BasicMqttCredentials credValue = new BasicMqttCredentials();
credValue.setClientId(CLIENT_ID);
clientIdDevice = createDevice("clientIdDevice", credValue);
credValue = new BasicMqttCredentials();
credValue.setClientId(CLIENT_ID);
credValue.setUserName(USER_NAME1);
clientIdAndUserNameDevice1 = createDevice("clientIdAndUserNameDevice", credValue);
credValue = new BasicMqttCredentials();
credValue.setClientId(CLIENT_ID);
credValue.setUserName(USER_NAME2);
credValue.setPassword(PASSWORD);
clientIdAndUserNameAndPasswordDevice2 = createDevice("clientIdAndUserNameAndPasswordDevice", credValue);
credValue = new BasicMqttCredentials();
credValue.setClientId(CLIENT_ID);
credValue.setUserName(USER_NAME3);
credValue.setPassword(PASSWORD);
clientIdAndUserNameAndPasswordDevice3 = createDevice("clientIdAndUserNameAndPasswordDevice2", credValue);
accessTokenDevice = createDevice("accessTokenDevice", USER_NAME1);
accessToken2Device = createDevice("accessToken2Device", USER_NAME2);
}
@Test
public void testCorrectCredentials() throws Exception {
// Check that correct devices receive telemetry
testTelemetryIsDelivered(accessTokenDevice, getMqttAsyncClient(null, USER_NAME1, null));
testTelemetryIsDelivered(clientIdDevice, getMqttAsyncClient(CLIENT_ID, null, null));
testTelemetryIsDelivered(clientIdAndUserNameDevice1, getMqttAsyncClient(CLIENT_ID, USER_NAME1, null));
testTelemetryIsDelivered(clientIdAndUserNameAndPasswordDevice2, getMqttAsyncClient(CLIENT_ID, USER_NAME2, PASSWORD));
// Also correct. Random clientId and password, but matches access token
testTelemetryIsDelivered(accessToken2Device, getMqttAsyncClient(RandomStringUtils.randomAlphanumeric(10), USER_NAME2, RandomStringUtils.randomAlphanumeric(10)));
}
@Test(expected = MqttSecurityException.class)
public void testCorrectClientIdAndUserNameButWrongPassword() throws Exception {
// Not correct. Correct clientId and username, but wrong password
testTelemetryIsNotDelivered(clientIdAndUserNameAndPasswordDevice3, getMqttAsyncClient(CLIENT_ID, USER_NAME3, "WRONG PASSWORD"));
}
private void testTelemetryIsDelivered(Device device, MqttAsyncClient client) throws Exception {
testTelemetryIsDelivered(device, client, true);
}
private void testTelemetryIsNotDelivered(Device device, MqttAsyncClient client) throws Exception {
testTelemetryIsDelivered(device, client, false);
}
private void testTelemetryIsDelivered(Device device, MqttAsyncClient client, boolean ok) throws Exception {
String randomKey = RandomStringUtils.randomAlphanumeric(10);
List<String> expectedKeys = Arrays.asList(randomKey);
publishMqttMsg(client, JacksonUtil.toString(JacksonUtil.newObjectNode().put(randomKey, true)).getBytes(), MqttTopics.DEVICE_TELEMETRY_TOPIC);
String deviceId = device.getId().getId().toString();
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 5000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {
});
if (actualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
if (ok) {
assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, actualKeySet);
} else {
assertNull(actualKeys);
}
client.disconnect().waitForCompletion();
}
@After
public void after() throws Exception {
processAfterTest();
}
protected MqttAsyncClient getMqttAsyncClient(String clientId, String username, String password) throws MqttException {
if (StringUtils.isEmpty(clientId)) {
clientId = MqttAsyncClient.generateClientId();
}
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
if (StringUtils.isNotEmpty(username)) {
options.setUserName(username);
}
if (StringUtils.isNotEmpty(password)) {
options.setPassword(password.toCharArray());
}
client.connect(options).waitForCompletion();
return client;
}
private Device createDevice(String deviceName, BasicMqttCredentials clientIdCredValue) throws Exception {
Device device = new Device();
device.setName(deviceName);
device.setType("default");
device = doPost("/api/device", device, Device.class);
DeviceCredentials clientIdCred =
doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
clientIdCred.setCredentialsType(DeviceCredentialsType.MQTT_BASIC);
clientIdCred.setCredentialsValue(JacksonUtil.toString(clientIdCredValue));
doPost("/api/device/credentials", clientIdCred).andExpect(status().isOk());
return device;
}
private Device createDevice(String deviceName, String accessToken) throws Exception {
Device device = new Device();
device.setName(deviceName);
device.setType("default");
device = doPost("/api/device", device, Device.class);
DeviceCredentials clientIdCred =
doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
clientIdCred.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
clientIdCred.setCredentialsId(accessToken);
doPost("/api/device/credentials", clientIdCred).andExpect(status().isOk());
return device;
}
}

9
common/cluster-api/src/main/proto/queue.proto

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
syntax = "proto3"; syntax = "proto3";
package transport; package transport;
option java_package = "org.thingsboard.server.gen.transport"; option java_package = "org.thingsboard.server.gen.transport";
@ -581,8 +582,12 @@ message TbSubscriptionKetStateProto {
message TbSubscriptionUpdateValueListProto { message TbSubscriptionUpdateValueListProto {
string key = 1; string key = 1;
repeated int64 ts = 2; repeated TbSubscriptionUpdateTsValue tsValue = 2;
repeated string value = 3; }
message TbSubscriptionUpdateTsValue {
int64 ts = 1;
optional string value = 2;
} }
/** /**

2
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -63,4 +63,6 @@ public interface AlarmService {
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds); AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java

@ -19,6 +19,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import java.util.List;
public interface DeviceCredentialsService { public interface DeviceCredentialsService {
DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId); DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java

@ -30,8 +30,6 @@ public interface EntityService {
CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId); CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
void deleteEntityRelations(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);
PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query); PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query);

6
common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java

@ -16,12 +16,16 @@
package org.thingsboard.server.dao.relation; package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import java.util.List; import java.util.List;
@ -78,6 +82,8 @@ public interface RelationService {
void removeRelations(TenantId tenantId, EntityId entityId); void removeRelations(TenantId tenantId, EntityId entityId);
List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit);
// TODO: This method may be useful for some validations in the future // TODO: This method may be useful for some validations in the future
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to); // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);

7
common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java

@ -28,7 +28,9 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import java.util.List; import java.util.List;
@ -42,7 +44,7 @@ public interface RuleChainService {
boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId); boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId);
boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData); RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId); RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId);
@ -88,4 +90,7 @@ public interface RuleChainService {
PageData<RuleChain> findAutoAssignToEdgeRuleChainsByTenantId(TenantId tenantId, PageLink pageLink); PageData<RuleChain> findAutoAssignToEdgeRuleChainsByTenantId(TenantId tenantId, PageLink pageLink);
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String name, String toString);
RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode);
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
@ -39,6 +40,7 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
@ApiModel @ApiModel
@Data @Data
@ToString(exclude = {"image", "profileDataBytes"})
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Slf4j @Slf4j
public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId, HasOtaPackage { public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements HasName, HasTenantId, HasOtaPackage {

2
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java

@ -29,7 +29,7 @@ import java.util.UUID;
@Value @Value
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true, exclude = "image")
public class DeviceProfileInfo extends EntityInfo { public class DeviceProfileInfo extends EntityInfo {
@ApiModelProperty(position = 3, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of device profiles in the grid view. ") @ApiModelProperty(position = 3, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of device profiles in the grid view. ")

4
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProperty.java → common/data/src/main/java/org/thingsboard/server/common/data/TbProperty.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.queue.kafka; package org.thingsboard.server.common.data;
import lombok.Data; import lombok.Data;
@ -21,7 +21,7 @@ import lombok.Data;
* Created by ashvayka on 25.09.18. * Created by ashvayka on 25.09.18.
*/ */
@Data @Data
public class TbKafkaProperty { public class TbProperty {
private String key; private String key;
private String value; private String value;

40
common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.alarm;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EntityAlarm implements HasTenantId {
private TenantId tenantId;
private EntityId entityId;
private long createdTime;
private String alarmType;
private CustomerId customerId;
private AlarmId alarmId;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java

@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
*/ */
public enum ComponentType { public enum ComponentType {
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, FLOW
} }

1
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java

@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.relation;
public enum RelationTypeGroup { public enum RelationTypeGroup {
COMMON, COMMON,
ALARM,
DASHBOARD, DASHBOARD,
RULE_CHAIN, RULE_CHAIN,
RULE_NODE, RULE_NODE,

1
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Created by igor on 3/13/18. * Created by igor on 3/13/18.

45
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.rule;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.util.Set;
@ApiModel
@Data
@Slf4j
public class RuleChainOutputLabelsUsage {
@ApiModelProperty(position = 1, required = true, value = "Rule Chain Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private RuleChainId ruleChainId;
@ApiModelProperty(position = 2, required = true, value = "Rule Node Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private RuleNodeId ruleNodeId;
@ApiModelProperty(position = 3, required = true, value = "Rule Chain Name", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String ruleChainName;
@ApiModelProperty(position = 4, required = true, value = "Rule Node Name", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String ruleNodeName;
@ApiModelProperty(position = 5, required = true, value = "Output labels", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private Set<String> labels;
}

44
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainUpdateResult.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.rule;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.util.List;
import java.util.Map;
/**
* Created by igor on 3/13/18.
*/
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RuleChainUpdateResult {
private final boolean success;
private final List<RuleNodeUpdateResult> updatedRuleNodes;
public static RuleChainUpdateResult failed(){
return new RuleChainUpdateResult(false, null);
}
public static RuleChainUpdateResult successful(List<RuleNodeUpdateResult> updatedRuleNodes){
return new RuleChainUpdateResult(true, updatedRuleNodes);
}
}

33
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNodeUpdateResult.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.rule;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.util.Map;
/**
* Created by igor on 3/13/18.
*/
@Data
public class RuleNodeUpdateResult {
private final RuleNode oldRuleNode;
private final RuleNode newRuleNode;
}

10
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -61,6 +61,16 @@ public enum MsgType {
*/ */
RULE_CHAIN_TO_RULE_CHAIN_MSG, RULE_CHAIN_TO_RULE_CHAIN_MSG,
/**
* Message that is sent by RuleNodeActor as input to other RuleChain with command to process TbMsg.
*/
RULE_CHAIN_INPUT_MSG,
/**
* Message that is sent by RuleNodeActor as output to RuleNode in other RuleChain with command to process TbMsg.
*/
RULE_CHAIN_OUTPUT_MSG,
/** /**
* Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain. * Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain.
*/ */

74
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -55,24 +55,25 @@ public final class TbMsg implements Serializable {
private final RuleChainId ruleChainId; private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId; private final RuleNodeId ruleNodeId;
@Getter(value = AccessLevel.NONE) @Getter(value = AccessLevel.NONE)
private final AtomicInteger ruleNodeExecCounter; @JsonIgnore
//This field is not serialized because we use queues and there is no need to do it
private final TbMsgProcessingCtx ctx;
public int getAndIncrementRuleNodeCounter() {
return ruleNodeExecCounter.getAndIncrement();
}
//This field is not serialized because we use queues and there is no need to do it //This field is not serialized because we use queues and there is no need to do it
@JsonIgnore @JsonIgnore
transient private final TbMsgCallback callback; transient private final TbMsgCallback callback;
public int getAndIncrementRuleNodeCounter() {
return ctx.getAndIncrementRuleNodeCounter();
}
public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId);
} }
public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, 0, TbMsgCallback.EMPTY); metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
@ -80,7 +81,8 @@ public final class TbMsg implements Serializable {
} }
public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY); return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
} }
// REALLY NEW MSG // REALLY NEW MSG
@ -90,11 +92,13 @@ public final class TbMsg implements Serializable {
} }
public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) {
return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY); return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), dataType, data, null, null, 0, TbMsgCallback.EMPTY); return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId,
metaData.copy(), dataType, data, null, null, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
@ -104,46 +108,48 @@ public final class TbMsg implements Serializable {
// For Tests only // For Tests only
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, 0, TbMsgCallback.EMPTY); return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null,
metaData.copy(), dataType, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) {
return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, callback); return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null,
metaData.copy(), TbMsgDataType.JSON, data, null, null, null, callback);
} }
public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType,
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ruleNodeExecCounter.get(), tbMsg.callback); data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback);
} }
public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) { public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback()); tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
} }
public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId) { public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback()); tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback());
} }
public static TbMsg transformMsg(TbMsg tbMsg, String queueName) { public static TbMsg transformMsg(TbMsg tbMsg, String queueName) {
return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback()); tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ctx.copy(), tbMsg.getCallback());
} }
public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, String queueName) { public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, String queueName) {
return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback()); tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback());
} }
//used for enqueueForTellNext //used for enqueueForTellNext
public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(),
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ruleNodeExecCounter.get(), TbMsgCallback.EMPTY); tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ctx.copy(), TbMsgCallback.EMPTY);
} }
private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) { RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) {
this.id = id; this.id = id;
this.queueName = queueName != null ? queueName : ServiceQueue.MAIN; this.queueName = queueName != null ? queueName : ServiceQueue.MAIN;
if (ts > 0) { if (ts > 0) {
@ -167,7 +173,7 @@ public final class TbMsg implements Serializable {
this.data = data; this.data = data;
this.ruleChainId = ruleChainId; this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId; this.ruleNodeId = ruleNodeId;
this.ruleNodeExecCounter = new AtomicInteger(ruleNodeExecCounter); this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx();
if (callback != null) { if (callback != null) {
this.callback = callback; this.callback = callback;
} else { } else {
@ -209,7 +215,8 @@ public final class TbMsg implements Serializable {
builder.setDataType(msg.getDataType().ordinal()); builder.setDataType(msg.getDataType().ordinal());
builder.setData(msg.getData()); builder.setData(msg.getData());
builder.setRuleNodeExecCounter(msg.ruleNodeExecCounter.get());
builder.setCtx(msg.ctx.toProto());
return builder.build().toByteArray(); return builder.build().toByteArray();
} }
@ -231,8 +238,17 @@ public final class TbMsg implements Serializable {
ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
} }
TbMsgProcessingCtx ctx;
if (proto.hasCtx()) {
ctx = TbMsgProcessingCtx.fromProto(proto.getCtx());
} else {
// Backward compatibility with unprocessed messages fetched from queue after update.
ctx = new TbMsgProcessingCtx(proto.getRuleNodeExecCounter());
}
TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, proto.getRuleNodeExecCounter(), callback); return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId,
metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, ctx, callback);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Could not parse protobuf for TbMsg", e); throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
} }
@ -243,11 +259,13 @@ public final class TbMsg implements Serializable {
} }
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) { public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback); return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, null, this.ctx, callback);
} }
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) { public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback); return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId,
this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ctx, callback);
} }
public TbMsgCallback getCallback() { public TbMsgCallback getCallback() {
@ -262,4 +280,12 @@ public final class TbMsg implements Serializable {
public String getQueueName() { public String getQueueName() {
return queueName != null ? queueName : ServiceQueue.MAIN; return queueName != null ? queueName : ServiceQueue.MAIN;
} }
public void pushToStack(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
ctx.push(ruleChainId, ruleNodeId);
}
public TbMsgProcessingStackItem popFormStack() {
return ctx.pop();
}
} }

95
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingCtx.java

@ -0,0 +1,95 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ashvayka on 13.01.18.
*/
public final class TbMsgProcessingCtx implements Serializable {
private final AtomicInteger ruleNodeExecCounter;
private volatile LinkedList<TbMsgProcessingStackItem> stack;
public TbMsgProcessingCtx() {
this(0);
}
public TbMsgProcessingCtx(int ruleNodeExecCounter) {
this(ruleNodeExecCounter, null);
}
protected TbMsgProcessingCtx(int ruleNodeExecCounter, LinkedList<TbMsgProcessingStackItem> stack) {
this.ruleNodeExecCounter = new AtomicInteger(ruleNodeExecCounter);
this.stack = stack;
}
public int getAndIncrementRuleNodeCounter() {
return ruleNodeExecCounter.getAndIncrement();
}
public TbMsgProcessingCtx copy() {
if (stack == null || stack.isEmpty()) {
return new TbMsgProcessingCtx(ruleNodeExecCounter.get());
} else {
return new TbMsgProcessingCtx(ruleNodeExecCounter.get(), new LinkedList<>(stack));
}
}
public void push(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
if (stack == null) {
stack = new LinkedList<>();
}
stack.add(new TbMsgProcessingStackItem(ruleChainId, ruleNodeId));
}
public TbMsgProcessingStackItem pop() {
return !stack.isEmpty() ? stack.removeLast() : null;
}
public static TbMsgProcessingCtx fromProto(MsgProtos.TbMsgProcessingCtxProto ctx) {
int ruleNodeExecCounter = ctx.getRuleNodeExecCounter();
if (ctx.getStackCount() > 0) {
LinkedList<TbMsgProcessingStackItem> stack = new LinkedList<>();
for (MsgProtos.TbMsgProcessingStackItemProto item : ctx.getStackList()) {
stack.add(TbMsgProcessingStackItem.fromProto(item));
}
return new TbMsgProcessingCtx(ruleNodeExecCounter, stack);
} else {
return new TbMsgProcessingCtx(ruleNodeExecCounter);
}
}
public MsgProtos.TbMsgProcessingCtxProto toProto() {
var ctxBuilder = MsgProtos.TbMsgProcessingCtxProto.newBuilder();
ctxBuilder.setRuleNodeExecCounter(ruleNodeExecCounter.get());
if (stack != null) {
for (TbMsgProcessingStackItem item : stack) {
ctxBuilder.addStack(item.toProto());
}
}
return ctxBuilder.build();
}
}

47
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import java.util.UUID;
@Data
public class TbMsgProcessingStackItem {
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
MsgProtos.TbMsgProcessingStackItemProto toProto() {
return MsgProtos.TbMsgProcessingStackItemProto.newBuilder()
.setRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits())
.setRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits())
.setRuleNodeIdMSB(ruleNodeId.getId().getMostSignificantBits())
.setRuleNodeIdLSB(ruleNodeId.getId().getLeastSignificantBits())
.build();
}
static TbMsgProcessingStackItem fromProto(MsgProtos.TbMsgProcessingStackItemProto item){
return new TbMsgProcessingStackItem(
new RuleChainId(new UUID(item.getRuleChainIdMSB(), item.getRuleChainIdLSB())),
new RuleNodeId(new UUID(item.getRuleNodeIdMSB(), item.getRuleNodeIdLSB()))
);
}
}

3
common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
public class RuleNodeException extends RuleEngineException { public class RuleNodeException extends RuleEngineException {
private static final long serialVersionUID = -1776681087370749776L; private static final long serialVersionUID = -1776681087370749776L;
public static final String UNKNOWN = "Unknown";
@Getter @Getter
private final String ruleChainName; private final String ruleChainName;
@ -45,7 +46,7 @@ public class RuleNodeException extends RuleEngineException {
this.ruleChainId = ruleNode.getRuleChainId(); this.ruleChainId = ruleNode.getRuleChainId();
this.ruleNodeId = ruleNode.getId(); this.ruleNodeId = ruleNode.getId();
} else { } else {
ruleNodeName = "Unknown"; ruleNodeName = UNKNOWN;
ruleChainId = new RuleChainId(RuleChainId.NULL_UUID); ruleChainId = new RuleChainId(RuleChainId.NULL_UUID);
ruleNodeId = new RuleNodeId(RuleNodeId.NULL_UUID); ruleNodeId = new RuleNodeId(RuleNodeId.NULL_UUID);
} }

19
common/message/src/main/proto/tbmsg.proto

@ -19,10 +19,24 @@ package msgqueue;
option java_package = "org.thingsboard.server.common.msg.gen"; option java_package = "org.thingsboard.server.common.msg.gen";
option java_outer_classname = "MsgProtos"; option java_outer_classname = "MsgProtos";
// Stores message metadata as map of strings
message TbMsgMetaDataProto { message TbMsgMetaDataProto {
map<string, string> data = 1; map<string, string> data = 1;
} }
// Stores stack of nested (caller) rule chains
message TbMsgProcessingStackItemProto {
int64 ruleChainIdMSB = 1;
int64 ruleChainIdLSB = 2;
int64 ruleNodeIdMSB = 3;
int64 ruleNodeIdLSB = 4;
}
message TbMsgProcessingCtxProto {
int32 ruleNodeExecCounter = 1;
repeated TbMsgProcessingStackItemProto stack = 2;
}
message TbMsgProto { message TbMsgProto {
string id = 1; string id = 1;
string type = 2; string type = 2;
@ -39,14 +53,17 @@ message TbMsgProto {
TbMsgMetaDataProto metaData = 11; TbMsgMetaDataProto metaData = 11;
//Transaction Data (12) was removed in 2.5 // Transaction Data (12) was removed in 2.5
int32 dataType = 13; int32 dataType = 13;
string data = 14; string data = 14;
int64 ts = 15; int64 ts = 15;
// Will be removed in 3.4. Moved to processing context
int32 ruleNodeExecCounter = 16; int32 ruleNodeExecCounter = 16;
int64 customerIdMSB = 17; int64 customerIdMSB = 17;
int64 customerIdLSB = 18; int64 customerIdLSB = 18;
TbMsgProcessingCtxProto ctx = 19;
} }

5
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java

@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TbProperty;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -104,10 +105,10 @@ public class TbKafkaSettings {
private String securityProtocol; private String securityProtocol;
@Setter @Setter
private List<TbKafkaProperty> other; private List<TbProperty> other;
@Setter @Setter
private Map<String, List<TbKafkaProperty>> consumerPropertiesPerTopic = Collections.emptyMap(); private Map<String, List<TbProperty>> consumerPropertiesPerTopic = Collections.emptyMap();
public Properties toAdminProps() { public Properties toAdminProps() {
Properties props = toProps(); Properties props = toProps();

2
common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java

@ -125,7 +125,7 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient {
})); }));
if (!report.isEmpty()) { if (!report.isEmpty()) {
log.info("Reporting API usage statistics for {} tenants and customers", report.size()); log.debug("Reporting API usage statistics for {} tenants and customers", report.size());
} }
} }

13
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.config; package org.thingsboard.server.transport.lwm2m.config;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -24,12 +25,16 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TbProperty;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig;
import java.util.List;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'")
@ConfigurationProperties(prefix = "transport.lwm2m")
public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Getter @Getter
@ -84,10 +89,6 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Value("${transport.lwm2m.server.security.bind_port:}") @Value("${transport.lwm2m.server.security.bind_port:}")
private Integer securePort; private Integer securePort;
@Getter
@Value("${transport.lwm2m.log_max_length:}")
private int logMaxLength;
@Getter @Getter
@Value("${transport.lwm2m.psm_activity_timer:10000}") @Value("${transport.lwm2m.psm_activity_timer:10000}")
private long psmActivityTimer; private long psmActivityTimer;
@ -96,6 +97,10 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Value("${transport.lwm2m.paging_transmission_window:10000}") @Value("${transport.lwm2m.paging_transmission_window:10000}")
private long pagingTransmissionWindow; private long pagingTransmissionWindow;
@Getter
@Setter
private List<TbProperty> networkConfig;
@Bean @Bean
@ConfigurationProperties(prefix = "transport.lwm2m.server.security.credentials") @ConfigurationProperties(prefix = "transport.lwm2m.server.security.credentials")
public SslCredentialsConfig lwm2mServerCredentials() { public SslCredentialsConfig lwm2mServerCredentials() {

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
@ -29,6 +28,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer;
@ -37,10 +37,7 @@ import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
@ -71,7 +68,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private LeshanServer server; private LeshanServer server;
@PostConstruct @AfterStartUp
public void init() { public void init() {
this.server = getLhServer(); this.server = getLhServer();
/* /*

9
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server;
import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.core.network.config.NetworkConfigDefaults; import org.eclipse.californium.core.network.config.NetworkConfigDefaults;
import org.springframework.util.CollectionUtils;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME;
@ -25,8 +26,8 @@ public class LwM2MNetworkConfig {
public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) {
NetworkConfig coapConfig = new NetworkConfig(); NetworkConfig coapConfig = new NetworkConfig();
coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec); coapConfig.setInt(NetworkConfig.Keys.COAP_PORT, serverPortNoSec);
coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort); coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT, serverSecurePort);
/** /**
Example:Property for large packet: Example:Property for large packet:
#NetworkConfig config = new NetworkConfig(); #NetworkConfig config = new NetworkConfig();
@ -105,6 +106,10 @@ public class LwM2MNetworkConfig {
coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10); coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10);
if (!CollectionUtils.isEmpty(config.getNetworkConfig())) {
config.getNetworkConfig().forEach(p -> coapConfig.setString(p.getKey(), p.getValue()));
}
return coapConfig; return coapConfig;
} }
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
@ -69,6 +70,7 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.ge
@Slf4j @Slf4j
@EqualsAndHashCode(of = {"endpoint"}) @EqualsAndHashCode(of = {"endpoint"})
@ToString(of = "endpoint")
public class LwM2mClient implements Serializable { public class LwM2mClient implements Serializable {
private static final long serialVersionUID = 8793482946289222623L; private static final long serialVersionUID = 8793482946289222623L;

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -84,7 +84,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>();
private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>(); private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>();
@AfterStartUp @AfterStartUp(order = Integer.MAX_VALUE - 1)
public void init() { public void init() {
String nodeId = context.getNodeId(); String nodeId = context.getNodeId();
Set<LwM2mClient> fetchedClients = clientStore.getAll(); Set<LwM2mClient> fetchedClients = clientStore.getAll();

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -102,6 +102,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
return; return;
} }
LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo);
if (client == null) {
log.warn("Missing client for session: [{}]", sessionInfo);
return;
}
if (client.getRegistration() == null) { if (client.getRegistration() == null) {
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR, "Registration is empty"); this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR, "Registration is empty");
return; return;

21
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -382,9 +382,13 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
List<LwM2mClient> clients = clientContext.getLwM2mClients() List<LwM2mClient> clients = clientContext.getLwM2mClients()
.stream().filter(e -> e.getProfileId() != null) .stream().filter(e -> e.getProfileId() != null)
.filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toList()); .filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toList());
clients.forEach(client -> client.onDeviceProfileUpdate(deviceProfile)); clients.forEach(client -> {
this.securityStore.remove(client.getEndpoint(), client.getRegistration().getId());
client.onDeviceProfileUpdate(deviceProfile);
});
if (clients.size() > 0) { if (clients.size() > 0) {
this.onDeviceProfileUpdate(clients, deviceProfile); var oldProfile = clientContext.getProfile(deviceProfile.getUuidId());
this.onDeviceProfileUpdate(clients, oldProfile, deviceProfile);
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] failed to update profile: {}", deviceProfile.getId(), deviceProfile); log.warn("[{}] failed to update profile: {}", deviceProfile.getId(), deviceProfile);
@ -392,11 +396,14 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
@Override @Override
public void onDeviceUpdate(SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) { public void onDeviceUpdate(SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> newDeviceProfileOpt) {
try { try {
LwM2mClient client = clientContext.getClientByDeviceId(device.getUuidId()); LwM2mClient client = clientContext.getClientByDeviceId(device.getUuidId());
if (client != null) { if (client != null) {
this.onDeviceUpdate(client, device, deviceProfileOpt); if (newDeviceProfileOpt.isPresent()) {
this.securityStore.remove(client.getEndpoint(), client.getRegistration().getId());
}
this.onDeviceUpdate(client, device, newDeviceProfileOpt);
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] failed to update device: {}", device.getId(), device); log.warn("[{}] failed to update device: {}", device.getId(), device);
@ -644,7 +651,8 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
private void onDeviceUpdate(LwM2mClient lwM2MClient, Device device, Optional<DeviceProfile> deviceProfileOpt) { private void onDeviceUpdate(LwM2mClient lwM2MClient, Device device, Optional<DeviceProfile> deviceProfileOpt) {
deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singletonList(lwM2MClient), deviceProfile)); var oldProfile = clientContext.getProfile(lwM2MClient.getProfileId());
deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singletonList(lwM2MClient), oldProfile, deviceProfile));
lwM2MClient.onDeviceUpdate(device, deviceProfileOpt); lwM2MClient.onDeviceUpdate(device, deviceProfileOpt);
} }
@ -770,8 +778,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
//TODO: review and optimize the logic to minimize number of the requests to device. //TODO: review and optimize the logic to minimize number of the requests to device.
private void onDeviceProfileUpdate(List<LwM2mClient> clients, DeviceProfile deviceProfile) { private void onDeviceProfileUpdate(List<LwM2mClient> clients, Lwm2mDeviceProfileTransportConfiguration oldProfile, DeviceProfile deviceProfile) {
var oldProfile = clientContext.getProfile(deviceProfile.getUuidId());
if (clientContext.profileUpdate(deviceProfile) != null) { if (clientContext.profileUpdate(deviceProfile) != null) {
// #1 // #1
TelemetryMappingConfiguration oldTelemetryParams = oldProfile.getObserveAttr(); TelemetryMappingConfiguration oldTelemetryParams = oldProfile.getObserveAttr();

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -1184,7 +1184,7 @@ public class DefaultTransportService implements TransportService {
@Scheduled(fixedDelayString = "${transport.stats.print-interval-ms:60000}") @Scheduled(fixedDelayString = "${transport.stats.print-interval-ms:60000}")
public void printStats() { public void printStats() {
if (statsEnabled) { if (statsEnabled && !statsMap.isEmpty()) {
String values = statsMap.entrySet().stream() String values = statsMap.entrySet().stream()
.map(kv -> kv.getKey() + " [" + kv.getValue() + "]").collect(Collectors.joining(", ")); .map(kv -> kv.getKey() + " [" + kv.getValue() + "]").collect(Collectors.joining(", "));
log.info("Transport Stats: {}", values); log.info("Transport Stats: {}", values);

7
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -59,4 +61,9 @@ public interface AlarmDao extends Dao<Alarm> {
PageData<AlarmId> findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink); PageData<AlarmId> findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink);
void createEntityAlarmRecord(EntityAlarm entityAlarm);
List<EntityAlarm> findEntityAlarmRecords(TenantId tenantId, AlarmId id);
void deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId);
} }

33
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -45,7 +46,6 @@ import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
@ -166,23 +166,24 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private AlarmOperationResult createAlarm(Alarm alarm) throws InterruptedException, ExecutionException { private AlarmOperationResult createAlarm(Alarm alarm) throws InterruptedException, ExecutionException {
log.debug("New Alarm : {}", alarm); log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
List<EntityId> propagatedEntitiesList = createAlarmRelations(saved); List<EntityId> propagatedEntitiesList = createEntityAlarmRecords(saved);
return new AlarmOperationResult(saved, true, true, propagatedEntitiesList); return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
} }
private List<EntityId> createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException { private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
List<EntityId> propagatedEntitiesList; List<EntityId> propagatedEntitiesList;
if (alarm.isPropagate()) { if (alarm.isPropagate()) {
Set<EntityId> parentEntities = getParentEntities(alarm); Set<EntityId> parentEntities = getParentEntities(alarm);
propagatedEntitiesList = new ArrayList<>(parentEntities.size() + 1); propagatedEntitiesList = new ArrayList<>(parentEntities.size() + 1);
for (EntityId parentId : parentEntities) { for (EntityId parentId : parentEntities) {
propagatedEntitiesList.add(parentId); propagatedEntitiesList.add(parentId);
createAlarmRelation(alarm.getTenantId(), parentId, alarm.getId()); createEntityAlarmRecord(alarm.getTenantId(), parentId, alarm);
} }
propagatedEntitiesList.add(alarm.getOriginator()); propagatedEntitiesList.add(alarm.getOriginator());
} else { } else {
propagatedEntitiesList = Collections.singletonList(alarm.getOriginator()); propagatedEntitiesList = Collections.singletonList(alarm.getOriginator());
} }
createEntityAlarmRecord(alarm.getTenantId(), alarm.getOriginator(), alarm);
return propagatedEntitiesList; return propagatedEntitiesList;
} }
@ -221,7 +222,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
List<EntityId> propagatedEntitiesList; List<EntityId> propagatedEntitiesList;
if (!oldPropagate && newPropagate) { if (!oldPropagate && newPropagate) {
try { try {
propagatedEntitiesList = createAlarmRelations(result); propagatedEntitiesList = createEntityAlarmRecords(result);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
log.warn("Failed to update alarm relations [{}]", result, e); log.warn("Failed to update alarm relations [{}]", result, e);
throw new RuntimeException(e); throw new RuntimeException(e);
@ -348,6 +349,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null); return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null);
} }
@Override
public void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId) {
alarmDao.deleteEntityAlarmRecords(tenantId, entityId);
}
private Alarm merge(Alarm existing, Alarm alarm) { private Alarm merge(Alarm existing, Alarm alarm) {
if (alarm.getStartTs() > existing.getEndTs()) { if (alarm.getStartTs() > existing.getEndTs()) {
existing.setEndTs(alarm.getStartTs()); existing.setEndTs(alarm.getStartTs());
@ -382,17 +388,20 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private Set<EntityId> getPropagationEntityIds(Alarm alarm) { private Set<EntityId> getPropagationEntityIds(Alarm alarm) {
if (alarm.isPropagate()) { if (alarm.isPropagate()) {
List<EntityRelation> relations = relationService.findByTo(alarm.getTenantId(), alarm.getId(), RelationTypeGroup.ALARM); List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId());
Set<EntityId> propagationEntityIds = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); return entityAlarms.stream().map(EntityAlarm::getEntityId).collect(Collectors.toSet());
propagationEntityIds.add(alarm.getOriginator());
return propagationEntityIds;
} else { } else {
return Collections.singleton(alarm.getOriginator()); return Collections.singleton(alarm.getOriginator());
} }
} }
private void createAlarmRelation(TenantId tenantId, EntityId entityId, EntityId alarmId) { private void createEntityAlarmRecord(TenantId tenantId, EntityId entityId, Alarm alarm) {
createRelation(tenantId, new EntityRelation(entityId, alarmId, AlarmSearchStatus.ANY.name(), RelationTypeGroup.ALARM)); EntityAlarm entityAlarm = new EntityAlarm(tenantId, entityId, alarm.getCreatedTime(), alarm.getType(), alarm.getCustomerId(), alarm.getId());
try {
alarmDao.createEntityAlarmRecord(entityAlarm);
} catch (Exception e) {
log.warn("[{}] Failed to create entity alarm record: {}", tenantId, entityAlarm, e);
}
} }
private <T> ListenableFuture<T> getAndUpdate(TenantId tenantId, AlarmId alarmId, Function<Alarm, T> function) { private <T> ListenableFuture<T> getAndUpdate(TenantId tenantId, AlarmId alarmId, Function<Alarm, T> function) {
@ -402,7 +411,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
private DataValidator<Alarm> alarmDataValidator = private DataValidator<Alarm> alarmDataValidator =
new DataValidator<Alarm>() { new DataValidator<>() {
@Override @Override
protected void validateDataImpl(TenantId tenantId, Alarm alarm) { protected void validateDataImpl(TenantId tenantId, Alarm alarm) {

8
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -18,12 +18,14 @@ package org.thingsboard.server.dao.entity;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -41,6 +43,10 @@ public abstract class AbstractEntityService {
@Autowired @Autowired
protected RelationService relationService; protected RelationService relationService;
@Lazy
@Autowired
protected AlarmService alarmService;
@Autowired @Autowired
protected EntityViewService entityViewService; protected EntityViewService entityViewService;
@ -60,6 +66,8 @@ public abstract class AbstractEntityService {
protected void deleteEntityRelations(TenantId tenantId, EntityId entityId) { protected void deleteEntityRelations(TenantId tenantId, EntityId entityId) {
log.trace("Executing deleteEntityRelations [{}]", entityId); log.trace("Executing deleteEntityRelations [{}]", entityId);
relationService.deleteEntityRelations(tenantId, entityId); relationService.deleteEntityRelations(tenantId, entityId);
log.trace("Executing deleteEntityAlarms [{}]", entityId);
alarmService.deleteEntityAlarmRelations(tenantId, entityId);
} }
protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) { protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) {

5
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -104,11 +104,6 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
@Autowired @Autowired
private OtaPackageService otaPackageService; private OtaPackageService otaPackageService;
@Override
public void deleteEntityRelations(TenantId tenantId, EntityId entityId) {
super.deleteEntityRelations(tenantId, entityId);
}
@Override @Override
public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) { public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
log.trace("Executing countEntitiesByQuery, tenantId [{}], customerId [{}], query [{}]", tenantId, customerId, query); log.trace("Executing countEntitiesByQuery, tenantId [{}], customerId [{}], query [{}]", tenantId, customerId, query);

1
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -257,6 +257,7 @@ public class ModelConstants {
/** /**
* Cassandra alarm constants. * Cassandra alarm constants.
*/ */
public static final String ENTITY_ALARM_COLUMN_FAMILY_NAME = "entity_alarm";
public static final String ALARM_COLUMN_FAMILY_NAME = "alarm"; public static final String ALARM_COLUMN_FAMILY_NAME = "alarm";
public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;

42
dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmCompositeKey.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.UUID;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class EntityAlarmCompositeKey implements Serializable {
@Transient
private static final long serialVersionUID = -245388185894468450L;
private UUID entityId;
private UUID alarmId;
public EntityAlarmCompositeKey(EntityAlarm entityAlarm) {
this.entityId = entityAlarm.getEntityId().getId();
this.alarmId = entityAlarm.getAlarmId().getId();
}
}

99
dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityAlarmEntity.java

@ -0,0 +1,99 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.ToData;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.CREATED_TIME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ALARM_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN;
@Data
@Entity
@Table(name = ENTITY_ALARM_COLUMN_FAMILY_NAME)
@IdClass(EntityAlarmCompositeKey.class)
public final class EntityAlarmEntity implements ToData<EntityAlarm> {
@Column(name = TENANT_ID_COLUMN, columnDefinition = "uuid")
private UUID tenantId;
@Column(name = ENTITY_TYPE_COLUMN)
private String entityType;
@Id
@Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid")
private UUID entityId;
@Id
@Column(name = "alarm_id", columnDefinition = "uuid")
private UUID alarmId;
@Column(name = CREATED_TIME_PROPERTY)
private long createdTime;
@Column(name = "alarm_type")
private String alarmType;
@Column(name = CUSTOMER_ID_PROPERTY, columnDefinition = "uuid")
private UUID customerId;
public EntityAlarmEntity() {
super();
}
public EntityAlarmEntity(EntityAlarm entityAlarm) {
tenantId = entityAlarm.getTenantId().getId();
entityId = entityAlarm.getEntityId().getId();
entityType = entityAlarm.getEntityId().getEntityType().name();
alarmId = entityAlarm.getAlarmId().getId();
alarmType = entityAlarm.getAlarmType();
createdTime = entityAlarm.getCreatedTime();
if (entityAlarm.getCustomerId() != null) {
customerId = entityAlarm.getCustomerId().getId();
}
}
@Override
public EntityAlarm toData() {
EntityAlarm result = new EntityAlarm();
result.setTenantId(new TenantId(tenantId));
result.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
result.setAlarmId(new AlarmId(alarmId));
result.setAlarmType(alarmType);
result.setCreatedTime(createdTime);
if (customerId != null) {
result.setCustomerId(new CustomerId(customerId));
}
return result;
}
}

16
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -30,8 +30,11 @@ import org.springframework.cache.annotation.Caching;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
@ -39,6 +42,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.ConstraintValidator;
@ -54,6 +58,7 @@ import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE; import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId;
/** /**
* Created by ashvayka on 28.04.17. * Created by ashvayka on 28.04.17.
@ -194,11 +199,11 @@ public class BaseRelationService implements RelationService {
outboundRelations.addAll(relationDao.findAllByFrom(tenantId, entityId, typeGroup)); outboundRelations.addAll(relationDao.findAllByFrom(tenantId, entityId, typeGroup));
} }
for (EntityRelation relation : inboundRelations){ for (EntityRelation relation : inboundRelations) {
delete(tenantId, cache, relation, true); delete(tenantId, cache, relation, true);
} }
for (EntityRelation relation : outboundRelations){ for (EntityRelation relation : outboundRelations) {
delete(tenantId, cache, relation, false); delete(tenantId, cache, relation, false);
} }
@ -556,6 +561,13 @@ public class BaseRelationService implements RelationService {
} }
} }
@Override
public List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit) {
log.trace("Executing findRuleNodeToRuleChainRelations, tenantId [{}], ruleChainType {} and limit {}", tenantId, ruleChainType, limit);
validateId(tenantId, "Invalid tenant id: " + tenantId);
return relationDao.findRuleNodeToRuleChainRelations(ruleChainType, limit);
}
protected void validate(EntityRelation relation) { protected void validate(EntityRelation relation) {
if (relation == null) { if (relation == null) {
throw new DataValidationException("Relation type should be specified!"); throw new DataValidationException("Relation type should be specified!");

5
dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java

@ -16,13 +16,11 @@
package org.thingsboard.server.dao.relation; package org.thingsboard.server.dao.relation;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import java.util.List; import java.util.List;
@ -63,4 +61,5 @@ public interface RelationDao {
ListenableFuture<Boolean> deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity); ListenableFuture<Boolean> deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity);
List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit);
} }

30
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -48,7 +48,9 @@ import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -72,6 +74,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.TENANT; import static org.thingsboard.server.common.data.DataConstants.TENANT;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
/** /**
* Created by igor on 3/12/18. * Created by igor on 3/12/18.
@ -130,13 +133,14 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override @Override
@Transactional @Transactional
public boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) { public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) {
Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id.");
RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId());
if (ruleChain == null) { if (ruleChain == null) {
return false; return RuleChainUpdateResult.failed();
} }
ConstraintValidator.validateFields(ruleChainMetaData); ConstraintValidator.validateFields(ruleChainMetaData);
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections()); validateCircles(ruleChainMetaData.getConnections());
@ -161,11 +165,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
for (RuleNode existingNode : existingRuleNodes) { for (RuleNode existingNode : existingRuleNodes) {
deleteEntityRelations(tenantId, existingNode.getId()); deleteEntityRelations(tenantId, existingNode.getId());
Integer index = ruleNodeIndexMap.get(existingNode.getId()); Integer index = ruleNodeIndexMap.get(existingNode.getId());
RuleNode newRuleNode = null;
if (index != null) { if (index != null) {
toAddOrUpdate.add(ruleChainMetaData.getNodes().get(index)); newRuleNode = ruleChainMetaData.getNodes().get(index);
toAddOrUpdate.add(newRuleNode);
} else { } else {
updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, null));
toDelete.add(existingNode); toDelete.add(existingNode);
} }
updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, newRuleNode));
} }
if (nodes != null) { if (nodes != null) {
for (RuleNode node : toAddOrUpdate) { for (RuleNode node : toAddOrUpdate) {
@ -209,7 +217,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
} }
} }
return true; return RuleChainUpdateResult.successful(updatedRuleNodes);
} }
private void validateCircles(List<NodeConnectionInfo> connectionInfos) { private void validateCircles(List<NodeConnectionInfo> connectionInfos) {
@ -652,6 +660,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return ruleChainDao.findAutoAssignToEdgeRuleChainsByTenantId(tenantId.getId(), pageLink); return ruleChainDao.findAutoAssignToEdgeRuleChainsByTenantId(tenantId.getId(), pageLink);
} }
@Override
public List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search) {
log.trace("Executing findRuleNodes, tenantId [{}], type {}, search {}", tenantId, type, search);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateString(type, "Incorrect type of the rule node");
validateString(search, "Incorrect search text");
return ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId, type, search);
}
@Override
public RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode) {
return ruleNodeDao.save(tenantId, ruleNode);
}
private void checkRuleNodesAndDelete(TenantId tenantId, RuleChainId ruleChainId) { private void checkRuleNodesAndDelete(TenantId tenantId, RuleChainId ruleChainId) {
try { try {
ruleChainDao.removeById(tenantId, ruleChainId.getId()); ruleChainDao.removeById(tenantId, ruleChainId.getId());

2
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java

@ -19,11 +19,13 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityDao; import org.thingsboard.server.dao.TenantEntityDao;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**

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

@ -15,12 +15,16 @@
*/ */
package org.thingsboard.server.dao.rule; package org.thingsboard.server.dao.rule;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.List;
/** /**
* Created by igor on 3/12/18. * Created by igor on 3/12/18.
*/ */
public interface RuleNodeDao extends Dao<RuleNode> { public interface RuleNodeDao extends Dao<RuleNode> {
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search);
} }

55
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -42,47 +42,28 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> {
Pageable pageable); Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " + @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
"LEFT JOIN RelationEntity re ON a.id = re.toId " + "LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " +
"AND re.relationTypeGroup = 'ALARM' " +
"AND re.toType = 'ALARM' " +
"AND re.fromId = :affectedEntityId " +
"AND re.fromType = :affectedEntityType " +
"WHERE a.tenantId = :tenantId " + "WHERE a.tenantId = :tenantId " +
"AND (a.originatorId = :affectedEntityId or re.fromId IS NOT NULL) " + "AND ea.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND ea.entityId = :affectedEntityId " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ea.entityType = :affectedEntityType " +
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) " " OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) "
, ,
countQuery = "" + countQuery = "" +
"SELECT count(a) + " + //alarms with relations only "SELECT count(a) " + //alarms with relations only
" (SELECT count(a) FROM AlarmEntity a " + //alarms WITHOUT any relations
" LEFT JOIN RelationEntity re ON a.id = re.toId " +
" AND re.relationTypeGroup = 'ALARM' " +
" AND re.toType = 'ALARM' " +
" AND re.fromId = :affectedEntityId " +
" AND re.fromType = :affectedEntityType " +
" WHERE a.tenantId = :tenantId " +
" AND (a.originatorId = :affectedEntityId) " +
" AND (re.fromId IS NULL) " + //anti join
" AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
" AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
" AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
" AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT('%', :searchText, '%'))) " +
" )" +
"FROM AlarmEntity a " + "FROM AlarmEntity a " +
"INNER JOIN RelationEntity re ON a.id = re.toId " + "LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " +
"AND re.relationTypeGroup = 'ALARM' " +
"AND re.toType = 'ALARM' " +
"AND re.fromId = :affectedEntityId " +
"AND re.fromType = :affectedEntityType " +
"WHERE a.tenantId = :tenantId " + "WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND ea.tenantId = :tenantId " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ea.entityId = :affectedEntityId " +
"AND ea.entityType = :affectedEntityType " +
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " + "AND (LOWER(a.type) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " + " OR LOWER(a.severity) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
@ -149,13 +130,11 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> {
Pageable pageable); Pageable pageable);
@Query(value = "SELECT a.severity FROM AlarmEntity a " + @Query(value = "SELECT a.severity FROM AlarmEntity a " +
"LEFT JOIN RelationEntity re ON a.id = re.toId " + "LEFT JOIN EntityAlarmEntity ea ON a.id = ea.alarmId " +
"AND re.relationTypeGroup = 'ALARM' " +
"AND re.toType = 'ALARM' " +
"AND re.fromId = :affectedEntityId " +
"AND re.fromType = :affectedEntityType " +
"WHERE a.tenantId = :tenantId " + "WHERE a.tenantId = :tenantId " +
"AND (a.originatorId = :affectedEntityId or re.fromId IS NOT NULL) " + "AND ea.tenantId = :tenantId " +
"AND ea.entityId = :affectedEntityId " +
"AND ea.entityType = :affectedEntityType " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))") "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))")
Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId, Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId,
@Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityId") UUID affectedEntityId,

32
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.alarm;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.EntityAlarmCompositeKey;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import java.util.List;
import java.util.UUID;
public interface EntityAlarmRepository extends CrudRepository<EntityAlarmEntity, EntityAlarmCompositeKey> {
List<EntityAlarmEntity> findAllByAlarmId(UUID alarmId);
@Transactional
void deleteByEntityId(UUID id);
}

22
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -37,6 +38,7 @@ import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.query.AlarmQueryRepository; import org.thingsboard.server.dao.sql.query.AlarmQueryRepository;
@ -62,7 +64,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
private AlarmQueryRepository alarmQueryRepository; private AlarmQueryRepository alarmQueryRepository;
@Autowired @Autowired
private RelationDao relationDao; private EntityAlarmRepository entityAlarmRepository;
@Override @Override
protected Class<AlarmEntity> getEntityClass() { protected Class<AlarmEntity> getEntityClass() {
@ -169,4 +171,22 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return DaoUtil.pageToPageData(alarmRepository.findAlarmsIdsByEndTsBeforeAndTenantId(time, tenantId.getId(), DaoUtil.toPageable(pageLink))) return DaoUtil.pageToPageData(alarmRepository.findAlarmsIdsByEndTsBeforeAndTenantId(time, tenantId.getId(), DaoUtil.toPageable(pageLink)))
.mapData(AlarmId::new); .mapData(AlarmId::new);
} }
@Override
public void createEntityAlarmRecord(EntityAlarm entityAlarm) {
log.debug("Saving entity {}", entityAlarm);
entityAlarmRepository.save(new EntityAlarmEntity(entityAlarm));
}
@Override
public List<EntityAlarm> findEntityAlarmRecords(TenantId tenantId, AlarmId id) {
log.trace("[{}] Try to find entity alarm records using [{}]", tenantId, id);
return DaoUtil.convertDataList(entityAlarmRepository.findAllByAlarmId(id.getId()));
}
@Override
public void deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId) {
log.trace("[{}] Try to delete entity alarm records using [{}]", tenantId, entityId);
entityAlarmRepository.deleteByEntityId(entityId.getId());
}
} }

21
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -105,7 +105,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" a.propagate_relation_types as propagate_relation_types, " + " a.propagate_relation_types as propagate_relation_types, " +
" a.type as type," + SELECT_ORIGINATOR_NAME + ", "; " a.type as type," + SELECT_ORIGINATOR_NAME + ", ";
private static final String JOIN_RELATIONS = "left join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id and r.from_id in (:entity_ids)"; private static final String JOIN_ENTITY_ALARMS = "inner join entity_alarm ea on a.id = ea.alarm_id";
protected final NamedParameterJdbcTemplate jdbcTemplate; protected final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate; private final TransactionTemplate transactionTemplate;
@ -132,8 +132,8 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
StringBuilder joinPart = new StringBuilder(); StringBuilder joinPart = new StringBuilder();
boolean addAnd = false; boolean addAnd = false;
if (pageLink.isSearchPropagatedAlarms()) { if (pageLink.isSearchPropagatedAlarms()) {
selectPart.append(" CASE WHEN r.from_id IS NULL THEN a.originator_id ELSE r.from_id END as entity_id "); selectPart.append(" ea.entity_id as entity_id ");
fromPart.append(JOIN_RELATIONS); fromPart.append(JOIN_ENTITY_ALARMS);
wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx)); wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx));
addAnd = true; addAnd = true;
} else { } else {
@ -145,7 +145,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey)) sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey))
.append(" ").append(sortOrder.getDirection().name()); .append(" ").append(sortOrder.getDirection().name());
if (pageLink.isSearchPropagatedAlarms()) { if (pageLink.isSearchPropagatedAlarms()) {
wherePart.append(" and (a.originator_id in (:entity_ids) or r.from_id IS NOT NULL)"); wherePart.append(" and ea.entity_id in (:entity_ids)");
} else { } else {
addAndIfNeeded(wherePart, addAnd); addAndIfNeeded(wherePart, addAnd);
addAnd = true; addAnd = true;
@ -166,7 +166,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
} }
joinPart.append(" as e(id, priority)) e "); joinPart.append(" as e(id, priority)) e ");
if (pageLink.isSearchPropagatedAlarms()) { if (pageLink.isSearchPropagatedAlarms()) {
joinPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)"); joinPart.append("on ea.entity_id = e.id");
} else { } else {
joinPart.append("on a.originator_id = e.id"); joinPart.append("on a.originator_id = e.id");
} }
@ -188,6 +188,9 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
addAnd = true; addAnd = true;
ctx.addLongParameter("startTime", startTs); ctx.addLongParameter("startTime", startTs);
wherePart.append("a.created_time >= :startTime"); wherePart.append("a.created_time >= :startTime");
if (pageLink.isSearchPropagatedAlarms()) {
wherePart.append(" and ea.created_time >= :startTime");
}
} }
if (endTs > 0) { if (endTs > 0) {
@ -195,6 +198,9 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
addAnd = true; addAnd = true;
ctx.addLongParameter("endTime", endTs); ctx.addLongParameter("endTime", endTs);
wherePart.append("a.created_time <= :endTime"); wherePart.append("a.created_time <= :endTime");
if (pageLink.isSearchPropagatedAlarms()) {
wherePart.append(" and ea.created_time <= :endTime");
}
} }
if (pageLink.getTypeList() != null && !pageLink.getTypeList().isEmpty()) { if (pageLink.getTypeList() != null && !pageLink.getTypeList().isEmpty()) {
@ -202,6 +208,9 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
addAnd = true; addAnd = true;
ctx.addStringListParameter("alarmTypes", pageLink.getTypeList()); ctx.addStringListParameter("alarmTypes", pageLink.getTypeList());
wherePart.append("a.type in (:alarmTypes)"); wherePart.append("a.type in (:alarmTypes)");
if (pageLink.isSearchPropagatedAlarms()) {
wherePart.append(" and ea.alarm_type in (:alarmTypes)");
}
} }
if (pageLink.getSeverityList() != null && !pageLink.getSeverityList().isEmpty()) { if (pageLink.getSeverityList() != null && !pageLink.getSeverityList().isEmpty()) {
@ -279,7 +288,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
private String buildPermissionsQuery(TenantId tenantId, CustomerId customerId, QueryContext ctx) { private String buildPermissionsQuery(TenantId tenantId, CustomerId customerId, QueryContext ctx) {
StringBuilder permissionsQuery = new StringBuilder(); StringBuilder permissionsQuery = new StringBuilder();
ctx.addUuidParameter("permissions_tenant_id", tenantId.getId()); ctx.addUuidParameter("permissions_tenant_id", tenantId.getId());
permissionsQuery.append(" a.tenant_id = :permissions_tenant_id "); permissionsQuery.append(" a.tenant_id = :permissions_tenant_id and ea.tenant_id = :permissions_tenant_id ");
/* /*
No need to check the customer id, because we already use entity id list that passed security check when we were evaluating the data query. No need to check the customer id, because we already use entity id list that passed security check when we were evaluating the data query.
*/ */

2
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java

@ -33,7 +33,7 @@ public class DefaultQueryLogComponent implements QueryLogComponent {
@Override @Override
public void logQuery(QueryContext ctx, String query, long duration) { public void logQuery(QueryContext ctx, String query, long duration) {
if (logSqlQueries && duration > logQueriesThreshold) { if (logSqlQueries && duration > logQueriesThreshold) {
log.info("QUERY: {} took {}ms", query, duration); log.info("QUERY: {} took {} ms", query, duration);
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param)));
} }
} }

9
dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RelationCompositeKey; import org.thingsboard.server.dao.model.sql.RelationCompositeKey;
import org.thingsboard.server.dao.model.sql.RelationEntity; import org.thingsboard.server.dao.model.sql.RelationEntity;
@ -196,11 +198,16 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
}); });
} }
@Override
public List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit) {
return DaoUtil.convertDataList(relationRepository.findRuleNodeToRuleChainRelations(ruleChainType, PageRequest.of(0, limit)));
}
private Specification<RelationEntity> getEntityFieldsSpec(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType) { private Specification<RelationEntity> getEntityFieldsSpec(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType) {
return (root, criteriaQuery, criteriaBuilder) -> { return (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>(); List<Predicate> predicates = new ArrayList<>();
if (from != null) { if (from != null) {
Predicate fromIdPredicate = criteriaBuilder.equal(root.get("fromId"), from.getId()); Predicate fromIdPredicate = criteriaBuilder.equal(root.get("fromId"), from.getId());
predicates.add(fromIdPredicate); predicates.add(fromIdPredicate);
Predicate fromEntityTypePredicate = criteriaBuilder.equal(root.get("fromType"), from.getEntityType().name()); Predicate fromEntityTypePredicate = criteriaBuilder.equal(root.get("fromType"), from.getEntityType().name());
predicates.add(fromEntityTypePredicate); predicates.add(fromEntityTypePredicate);

15
dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java

@ -15,11 +15,20 @@
*/ */
package org.thingsboard.server.dao.sql.relation; package org.thingsboard.server.dao.sql.relation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.model.sql.RelationCompositeKey; import org.thingsboard.server.dao.model.sql.RelationCompositeKey;
import org.thingsboard.server.dao.model.sql.RelationEntity; import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.model.sql.RuleChainEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -48,6 +57,11 @@ public interface RelationRepository
List<RelationEntity> findAllByFromIdAndFromType(UUID fromId, List<RelationEntity> findAllByFromIdAndFromType(UUID fromId,
String fromType); String fromType);
@Query("SELECT r FROM RelationEntity r WHERE " +
"r.relationTypeGroup = 'RULE_NODE' AND r.toType = 'RULE_CHAIN' " +
"AND r.toId in (SELECT id from RuleChainEntity where type = :ruleChainType )")
List<RelationEntity> findRuleNodeToRuleChainRelations(@Param("ruleChainType") RuleChainType ruleChainType, Pageable page);
@Transactional @Transactional
<S extends RelationEntity> S save(S entity); <S extends RelationEntity> S save(S entity);
@ -56,4 +70,5 @@ public interface RelationRepository
@Transactional @Transactional
void deleteByFromIdAndFromType(UUID fromId, String fromType); void deleteByFromIdAndFromType(UUID fromId, String fromType);
} }

2
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RuleChainEntity; import org.thingsboard.server.dao.model.sql.RuleChainEntity;
@ -30,6 +31,7 @@ import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;

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

@ -19,11 +19,14 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.RuleNodeEntity; import org.thingsboard.server.dao.model.sql.RuleNodeEntity;
import org.thingsboard.server.dao.rule.RuleNodeDao; import org.thingsboard.server.dao.rule.RuleNodeDao;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Slf4j @Slf4j
@ -43,4 +46,8 @@ public class JpaRuleNodeDao extends JpaAbstractSearchTextDao<RuleNodeEntity, Rul
return ruleNodeRepository; return ruleNodeRepository;
} }
@Override
public List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search) {
return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByTenantIdAndType(tenantId.getId(), type, search));
}
} }

5
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java

@ -20,7 +20,11 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.model.sql.RuleChainEntity; import org.thingsboard.server.dao.model.sql.RuleChainEntity;
import java.util.List; import java.util.List;
@ -59,6 +63,7 @@ public interface RuleChainRepository extends PagingAndSortingRepository<RuleChai
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
RuleChainEntity findByTenantIdAndTypeAndRootIsTrue(UUID tenantId, RuleChainType ruleChainType); RuleChainEntity findByTenantIdAndTypeAndRootIsTrue(UUID tenantId, RuleChainType ruleChainType);
Long countByTenantId(UUID tenantId); Long countByTenantId(UUID tenantId);

10
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java

@ -15,11 +15,21 @@
*/ */
package org.thingsboard.server.dao.sql.rule; package org.thingsboard.server.dao.sql.rule;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.RuleNodeEntity; import org.thingsboard.server.dao.model.sql.RuleNodeEntity;
import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface RuleNodeRepository extends CrudRepository<RuleNodeEntity, UUID> { public interface RuleNodeRepository extends CrudRepository<RuleNodeEntity, UUID> {
@Query("SELECT r FROM RuleNodeEntity r WHERE r.ruleChainId in " +
"(select id from RuleChainEntity rc WHERE rc.tenantId = :tenantId) " +
"AND r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ")
List<RuleNodeEntity> findRuleNodesByTenantIdAndType(@Param("tenantId") UUID tenantId,
@Param("ruleType") String ruleType,
@Param("searchText") String searchText);
} }

12
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -43,6 +43,18 @@ CREATE TABLE IF NOT EXISTS alarm (
type varchar(255) type varchar(255)
); );
CREATE TABLE IF NOT EXISTS entity_alarm (
tenant_id uuid NOT NULL,
entity_type varchar(32),
entity_id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_type varchar(255) NOT NULL,
customer_id uuid,
alarm_id uuid,
CONSTRAINT entity_alarm_pkey PRIMARY KEY(entity_id, alarm_id),
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS asset ( CREATE TABLE IF NOT EXISTS asset (
id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY, id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY,
created_time bigint NOT NULL, created_time bigint NOT NULL,

6
dao/src/main/resources/sql/schema-entities-idx.sql

@ -20,8 +20,14 @@ CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_id, status, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC); CREATE INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id);
CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id);
CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id);

12
dao/src/main/resources/sql/schema-entities.sql

@ -58,6 +58,18 @@ CREATE TABLE IF NOT EXISTS alarm (
type varchar(255) type varchar(255)
); );
CREATE TABLE IF NOT EXISTS entity_alarm (
tenant_id uuid NOT NULL,
entity_type varchar(32),
entity_id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_type varchar(255) NOT NULL,
customer_id uuid,
alarm_id uuid,
CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id),
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS asset ( CREATE TABLE IF NOT EXISTS asset (
id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY, id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY,
created_time bigint NOT NULL, created_time bigint NOT NULL,

19
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java

@ -612,16 +612,6 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0)); Assert.assertEquals(created, alarms.getData().get(0));
List<EntityRelation> toAlarmRelations = relationService.findByTo(tenantId, created.getId(), RelationTypeGroup.ALARM);
Assert.assertEquals(1, toAlarmRelations.size());
List<EntityRelation> fromChildRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(0, fromChildRelations.size());
List<EntityRelation> fromParentRelations = relationService.findByFrom(tenantId, parentId, RelationTypeGroup.ALARM);
Assert.assertEquals(1, fromParentRelations.size());
Assert.assertTrue("Alarm was not deleted when expected", alarmService.deleteAlarm(tenantId, created.getId()).isSuccessful()); Assert.assertTrue("Alarm was not deleted when expected", alarmService.deleteAlarm(tenantId, created.getId()).isSuccessful());
Alarm fetched = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get(); Alarm fetched = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
@ -647,14 +637,5 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(0, alarms.getData().size()); Assert.assertEquals(0, alarms.getData().size());
toAlarmRelations = relationService.findByTo(tenantId, created.getId(), RelationTypeGroup.ALARM);
Assert.assertEquals(0, toAlarmRelations.size());
fromChildRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(0, fromChildRelations.size());
fromParentRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(0, fromParentRelations.size());
} }
} }

4
dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java

@ -293,7 +293,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
ruleNodes.set(name3Index, ruleNode4); ruleNodes.set(name3Index, ruleNode4);
Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, savedRuleChainMetaData)); Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, savedRuleChainMetaData).isSuccess());
RuleChainMetaData updatedRuleChainMetaData = ruleChainService.loadRuleChainMetaData(tenantId, savedRuleChainMetaData.getRuleChainId()); RuleChainMetaData updatedRuleChainMetaData = ruleChainService.loadRuleChainMetaData(tenantId, savedRuleChainMetaData.getRuleChainId());
Assert.assertEquals(3, updatedRuleChainMetaData.getNodes().size()); Assert.assertEquals(3, updatedRuleChainMetaData.getNodes().size());
@ -406,7 +406,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
ruleChainMetaData.addConnectionInfo(0,2,"fail"); ruleChainMetaData.addConnectionInfo(0,2,"fail");
ruleChainMetaData.addConnectionInfo(1,2,"success"); ruleChainMetaData.addConnectionInfo(1,2,"success");
Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData)); Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData).isSuccess());
return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()); return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId());
} }

1
dao/src/test/resources/sql/hsql/drop-all-tables.sql

@ -1,4 +1,5 @@
DROP TABLE IF EXISTS admin_settings; DROP TABLE IF EXISTS admin_settings;
DROP TABLE IF EXISTS entity_alarm;
DROP TABLE IF EXISTS alarm; DROP TABLE IF EXISTS alarm;
DROP TABLE IF EXISTS asset; DROP TABLE IF EXISTS asset;
DROP TABLE IF EXISTS audit_log; DROP TABLE IF EXISTS audit_log;

1
dao/src/test/resources/sql/psql/drop-all-tables.sql

@ -1,4 +1,5 @@
DROP TABLE IF EXISTS admin_settings; DROP TABLE IF EXISTS admin_settings;
DROP TABLE IF EXISTS entity_alarm;
DROP TABLE IF EXISTS alarm; DROP TABLE IF EXISTS alarm;
DROP TABLE IF EXISTS asset; DROP TABLE IF EXISTS asset;
DROP TABLE IF EXISTS audit_log; DROP TABLE IF EXISTS audit_log;

1
dao/src/test/resources/sql/timescale/drop-all-tables.sql

@ -1,4 +1,5 @@
DROP TABLE IF EXISTS admin_settings; DROP TABLE IF EXISTS admin_settings;
DROP TABLE IF EXISTS entity_alarm;
DROP TABLE IF EXISTS alarm; DROP TABLE IF EXISTS alarm;
DROP TABLE IF EXISTS asset; DROP TABLE IF EXISTS asset;
DROP TABLE IF EXISTS audit_log; DROP TABLE IF EXISTS audit_log;

24
msa/js-executor/docker/Dockerfile

@ -14,20 +14,30 @@
# limitations under the License. # limitations under the License.
# #
FROM thingsboard/base FROM node:16.13.1-bullseye-slim
COPY start-js-executor.sh ${pkg.name}.deb /tmp/ COPY start-js-executor.sh /tmp/
RUN chmod a+x /tmp/*.sh \ RUN chmod a+x /tmp/*.sh \
&& mv /tmp/start-js-executor.sh /usr/bin && mv /tmp/start-js-executor.sh /usr/bin
RUN yes | dpkg -i /tmp/${pkg.name}.deb ENV NODE_ENV production
RUN rm /tmp/${pkg.name}.deb ENV DOCKER_MODE true
RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : WORKDIR ${pkg.installFolder}
RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name} COPY ["src/package.json", "src/yarn.lock", "./"]
USER ${pkg.user} COPY package/linux/conf ./conf
COPY package/linux/conf ./config
COPY src/api ./api
COPY src/queue ./queue
COPY src/server.js ./
RUN chown -R node:node ${pkg.installFolder}
RUN yarn install --production && yarn cache clean --all
USER node
CMD ["start-js-executor.sh"] CMD ["start-js-executor.sh"]

6
msa/js-executor/docker/start-js-executor.sh

@ -20,12 +20,10 @@ echo "Starting '${project.name}' ..."
CONF_FOLDER="${pkg.installFolder}/conf" CONF_FOLDER="${pkg.installFolder}/conf"
mainfile=${pkg.installFolder}/bin/${pkg.name}
configfile=${pkg.name}.conf configfile=${pkg.name}.conf
identity=${pkg.name}
source "${CONF_FOLDER}/${configfile}" source "${CONF_FOLDER}/${configfile}"
cd ${pkg.installFolder}/bin cd ${pkg.installFolder}
exec /bin/sh -c "$mainfile" exec /bin/sh -c "node server.js"

7
msa/js-executor/package.json

@ -1,12 +1,12 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "3.3.2", "version": "3.3.3",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",
"scripts": { "scripts": {
"install": "pkg -t node12-linux-x64,node12-win-x64 --out-path ./target . && node install.js", "pkg": "pkg -t node12-linux-x64,node12-win-x64 --out-path ./target . && node install.js",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js", "start": "nodemon server.js",
"start-prod": "NODE_ENV=production nodemon server.js" "start-prod": "NODE_ENV=production nodemon server.js"
@ -27,9 +27,6 @@
"winston": "^3.3.3", "winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.0" "winston-daily-rotate-file": "^4.5.0"
}, },
"engines": {
"node": ">=12.0.0 <14.0.0"
},
"nyc": { "nyc": {
"exclude": [ "exclude": [
"test", "test",

65
msa/js-executor/pom.xml

@ -42,6 +42,7 @@
<pkg.package.phase>package</pkg.package.phase> <pkg.package.phase>package</pkg.package.phase>
<pkg.linux.dist>${project.build.directory}/package/linux</pkg.linux.dist> <pkg.linux.dist>${project.build.directory}/package/linux</pkg.linux.dist>
<pkg.win.dist>${project.build.directory}/package/windows</pkg.win.dist> <pkg.win.dist>${project.build.directory}/package/windows</pkg.win.dist>
<docker.push-arm-amd-image.phase>pre-integration-test</docker.push-arm-amd-image.phase>
</properties> </properties>
<dependencies> <dependencies>
@ -83,6 +84,15 @@
<arguments>install</arguments> <arguments>install</arguments>
</configuration> </configuration>
</execution> </execution>
<execution>
<id>yarn pkg</id>
<goals>
<goal>yarn</goal>
</goals>
<configuration>
<arguments>run pkg</arguments>
</configuration>
</execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
@ -92,6 +102,61 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-src-api</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/src/api</outputDirectory>
<resources>
<resource>
<directory>api</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-src-queue</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/src/queue</outputDirectory>
<resources>
<resource>
<directory>queue</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-src-root</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/src</outputDirectory>
<resources>
<resource>
<directory>${basedir}</directory>
<includes>
<include>server.js</include>
<include>package.json</include>
<include>yarn.lock</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

63
msa/pom.xml

@ -35,6 +35,7 @@
<dockerfile.skip>true</dockerfile.skip> <dockerfile.skip>true</dockerfile.skip>
<blackBoxTests.skip>true</blackBoxTests.skip> <blackBoxTests.skip>true</blackBoxTests.skip>
<dockerfile-maven.version>1.4.13</dockerfile-maven.version> <dockerfile-maven.version>1.4.13</dockerfile-maven.version>
<docker.push-arm-amd-image.phase>none</docker.push-arm-amd-image.phase>
</properties> </properties>
<modules> <modules>
@ -58,8 +59,67 @@
<module>black-box-tests</module> <module>black-box-tests</module>
</modules> </modules>
</profile> </profile>
<profile>
<id>push-docker-amd-arm-images</id>
<activation>
<property>
<name>push-docker-amd-arm-images</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>push-latest-docker-amd-arm-images</id>
<phase>${docker.push-arm-amd-image.phase}</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${docker.name}:latest</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-version-docker-amd-arm-images</id>
<phase>${docker.push-arm-amd-image.phase}</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${docker.name}:${project.version}</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
<build> <build>
<extensions> <extensions>
<extension> <extension>
@ -78,5 +138,4 @@
</plugins> </plugins>
</pluginManagement> </pluginManagement>
</build> </build>
</project> </project>

1
msa/tb-node/pom.xml

@ -38,6 +38,7 @@
<docker.name>tb-node</docker.name> <docker.name>tb-node</docker.name>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder> <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<docker.push-arm-amd-image.phase>pre-integration-test</docker.push-arm-amd-image.phase>
</properties> </properties>
<dependencies> <dependencies>

144
msa/tb/pom.xml

@ -355,6 +355,150 @@
</plugins> </plugins>
</build> </build>
</profile> </profile>
<profile>
<id>push-docker-amd-arm-images</id>
<activation>
<property>
<name>push-docker-amd-arm-images</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>push-latest-docker-amd-arm-tb-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-tb</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb.docker.name}:latest</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-version-docker-amd-arm-tb-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-tb</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb.docker.name}:${project.version}</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-latest-docker-amd-arm-tb-postgres-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-postgres</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb-postgres.docker.name}:latest</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-version-docker-amd-arm-tb-postgres-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-postgres</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb-postgres.docker.name}:${project.version}</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-latest-docker-amd-arm-tb-cassandra-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-cassandra</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb-cassandra.docker.name}:latest</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>push-version-docker-amd-arm-tb-cassandra-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.build.directory}/docker-cassandra</workingDirectory>
<arguments>
<argument>buildx</argument>
<argument>build</argument>
<argument>-t</argument>
<argument>${docker.repo}/${tb-cassandra.docker.name}:${project.version}</argument>
<argument>--platform=linux/amd64,linux/arm64</argument>
<argument>-o</argument>
<argument>type=registry</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles> </profiles>
<repositories> <repositories>
<repository> <repository>

1
msa/transport/coap/pom.xml

@ -38,6 +38,7 @@
<docker.name>tb-coap-transport</docker.name> <docker.name>tb-coap-transport</docker.name>
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder> <pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<docker.push-arm-amd-image.phase>pre-integration-test</docker.push-arm-amd-image.phase>
</properties> </properties>
<dependencies> <dependencies>

1
msa/transport/http/pom.xml

@ -38,6 +38,7 @@
<docker.name>tb-http-transport</docker.name> <docker.name>tb-http-transport</docker.name>
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder> <pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<docker.push-arm-amd-image.phase>pre-integration-test</docker.push-arm-amd-image.phase>
</properties> </properties>
<dependencies> <dependencies>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save