Browse Source

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

pull/5760/head
Sergey Matvienko 5 years ago
parent
commit
cf9f5150dd
  1. 11
      application/pom.xml
  2. 71
      application/src/main/data/upgrade/3.3.2/schema_update.sql
  3. 41
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 6
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  5. 20
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  6. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  7. 71
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  8. 41
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
  9. 49
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java
  10. 18
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  11. 50
      application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java
  12. 4
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  13. 9
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  14. 84
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  15. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  16. 27
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  17. 30
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  18. 84
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  19. 191
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  20. 34
      application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java
  21. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java
  22. 4
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  23. 23
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  24. 13
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  25. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  26. 35
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  27. 1
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  28. 8
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  29. 58
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  30. 18
      application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java
  31. 95
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  32. 81
      application/src/main/resources/thingsboard.yml
  33. 257
      application/src/test/java/org/thingsboard/server/actors/ActorSystemContextTest.java
  34. 4
      application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java
  35. 42
      application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java
  36. 47
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  37. 196
      application/src/test/java/org/thingsboard/server/controller/BaseRpcControllerTest.java
  38. 7
      application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java
  39. 174
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  40. 15
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  41. 3
      application/src/test/java/org/thingsboard/server/controller/sql/ComponentDescriptorControllerSqlTest.java
  42. 17
      application/src/test/java/org/thingsboard/server/controller/sql/RpcControllerTest.java
  43. 16
      application/src/test/java/org/thingsboard/server/edge/EdgeSqlTestSuite.java
  44. 16
      application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
  45. 7
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  46. 15
      application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java
  47. 16
      application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
  48. 13
      application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java
  49. 14
      application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java
  50. 12
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java
  51. 11
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java
  52. 20
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java
  53. 226
      application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/sql/BasicMqttCredentialsTest.java
  54. 23
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java
  55. 31
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  56. 2
      common/actor/pom.xml
  57. 2
      common/cache/pom.xml
  58. 2
      common/cluster-api/pom.xml
  59. 9
      common/cluster-api/src/main/proto/queue.proto
  60. 2
      common/coap-server/pom.xml
  61. 2
      common/dao-api/pom.xml
  62. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  63. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
  64. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  65. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  66. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  67. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  68. 2
      common/data/pom.xml
  69. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  70. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java
  71. 4
      common/data/src/main/java/org/thingsboard/server/common/data/TbProperty.java
  72. 40
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/EntityAlarm.java
  73. 2
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java
  74. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java
  75. 1
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
  76. 2
      common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java
  77. 1
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  78. 45
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainOutputLabelsUsage.java
  79. 44
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainUpdateResult.java
  80. 28
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNodeUpdateResult.java
  81. 2
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java
  82. 2
      common/edge-api/pom.xml
  83. 2
      common/message/pom.xml
  84. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  85. 74
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  86. 95
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingCtx.java
  87. 47
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java
  88. 3
      common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java
  89. 1
      common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimitsException.java
  90. 19
      common/message/src/main/proto/tbmsg.proto
  91. 2
      common/pom.xml
  92. 2
      common/queue/pom.xml
  93. 5
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  94. 4
      common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java
  95. 2
      common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java
  96. 54
      common/queue/src/test/java/org/thingsboard/server/queue/memory/InMemoryStorageTest.java
  97. 2
      common/stats/pom.xml
  98. 2
      common/transport/coap/pom.xml
  99. 2
      common/transport/http/pom.xml
  100. 2
      common/transport/lwm2m/pom.xml

11
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>
@ -311,8 +311,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>

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';

41
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -36,6 +36,7 @@ import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.EntityId;
@ -82,7 +83,6 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
@ -95,6 +95,7 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -333,30 +334,42 @@ public class ActorSystemContext {
@Getter
private long maxConcurrentSessionsPerDevice;
@Value("${actors.session.sync.timeout}")
@Value("${actors.session.sync.timeout:10000}")
@Getter
private long syncSessionTimeout;
@Value("${actors.rule.chain.error_persist_frequency}")
@Value("${actors.rule.chain.error_persist_frequency:3000}")
@Getter
private long ruleChainErrorPersistFrequency;
@Value("${actors.rule.node.error_persist_frequency}")
@Value("${actors.rule.node.error_persist_frequency:3000}")
@Getter
private long ruleNodeErrorPersistFrequency;
@Value("${actors.statistics.enabled}")
@Value("${actors.statistics.enabled:true}")
@Getter
private boolean statisticsEnabled;
@Value("${actors.statistics.persist_frequency}")
@Value("${actors.statistics.persist_frequency:3600000}")
@Getter
private long statisticsPersistFrequency;
@Value("${edges.enabled}")
@Value("${edges.enabled:true}")
@Getter
private boolean edgesEnabled;
@Value("${cache.type:caffeine}")
@Getter
private String cacheType;
@Getter
private boolean localCacheType;
@PostConstruct
public void init() {
this.localCacheType = "caffeine".equals(cacheType);
}
@Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
public void printStats() {
if (statisticsEnabled) {
@ -368,31 +381,31 @@ public class ActorSystemContext {
}
}
@Value("${actors.tenant.create_components_on_init}")
@Value("${actors.tenant.create_components_on_init:true}")
@Getter
private boolean tenantComponentsInitEnabled;
@Value("${actors.rule.allow_system_mail_service}")
@Value("${actors.rule.allow_system_mail_service:true}")
@Getter
private boolean allowSystemMailService;
@Value("${actors.rule.allow_system_sms_service}")
@Value("${actors.rule.allow_system_sms_service:true}")
@Getter
private boolean allowSystemSmsService;
@Value("${transport.sessions.inactivity_timeout}")
@Value("${transport.sessions.inactivity_timeout:300000}")
@Getter
private long sessionInactivityTimeout;
@Value("${transport.sessions.report_timeout}")
@Value("${transport.sessions.report_timeout:3000}")
@Getter
private long sessionReportTimeout;
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}")
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
@Getter
private boolean debugPerTenantEnabled;
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration}")
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration:50000:3600}")
@Getter
private String debugPerTenantLimitsConfiguration;

6
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -871,6 +871,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
void restoreSessions() {
if (systemContext.isLocalCacheType()) {
return;
}
log.debug("[{}] Restoring sessions from cache", deviceId);
DeviceSessionsCacheEntry sessionsDump = null;
try {
@ -905,6 +908,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
private void dumpSessions() {
if (systemContext.isLocalCacheType()) {
return;
}
log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
List<SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size());
sessions.forEach((uuid, sessionMD) -> {

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.TbMsg;
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.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -134,6 +135,25 @@ class DefaultTbContext implements TbContext {
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
public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer<Throwable> onFailure) {
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:
processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
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:
processor.onPartitionChangeMsg((PartitionChangeMsg) msg);
break;

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

@ -66,6 +66,7 @@ import java.util.stream.Collectors;
@Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private static final String NA_RELATION_TYPE = "";
private final TbActorRef parent;
private final TbActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
@ -201,33 +202,58 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
TbMsg msg = envelope.getMsg();
log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg);
if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) {
try {
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()));
}
onTellNext(msg, true);
} else {
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) {
try {
checkActive(envelope.getMsg());
@ -356,4 +382,5 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;
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)
@ToString
public final class RuleChainToRuleChainMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg {
public final class RuleChainToRuleChainMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleChainId target;
@Getter
private final RuleChainId source;
@Getter
private final String fromRelationType;
public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) {
super(tbMsg);
this.target = target;
super(tbMsg, target);
this.source = source;
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
public MsgType getMsgType() {
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.TbEntityTypeActorIdPredicate;
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.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState;
@ -168,6 +170,8 @@ public class TenantActor extends RuleChainManagerActor {
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg, true);
break;
case RULE_CHAIN_INPUT_MSG:
case RULE_CHAIN_OUTPUT_MSG:
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg);
break;

9
application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java

@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.server.ResponseStatusException;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId;
@ -187,10 +188,15 @@ public class RpcV2Controller extends AbstractRpcController {
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("DeviceId", strDeviceId);
try {
if (rpcStatus.equals(RpcStatus.DELETED)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "RpcStatus: DELETED");
}
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
final DeferredResult<ResponseEntity> response = new DeferredResult<>();
accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
@ -219,7 +225,7 @@ public class RpcV2Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(
public void deleteRpc(
@ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(RPC_ID) String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc);
@ -235,6 +241,7 @@ public class RpcV2Controller extends AbstractRpcController {
}
rpcService.deleteRpc(getTenantId(), rpcId);
rpc.setStatus(RpcStatus.DELETED);
TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null);

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.MediaType;
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.RequestBody;
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.EntityType;
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.edge.Edge;
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.RuleChainImportResult;
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.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
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.queue.util.TbCoreComponent;
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.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -137,6 +143,9 @@ public class RuleChainController extends BaseController {
+ MARKDOWN_CODE_BLOCK_END
+ "\n\n Expected result JSON contains \"output\" and \"error\".";
@Autowired
protected TbRuleChainService tbRuleChainService;
@Autowired
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)",
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')")
@ -310,7 +357,10 @@ public class RuleChainController extends BaseController {
@ResponseBody
public RuleChainMetaData saveRuleChainMetaData(
@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 {
TenantId tenantId = getTenantId();
if (debugPerTenantEnabled) {
@ -322,20 +372,36 @@ public class RuleChainController extends BaseController {
}
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()));
if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
updatedRuleChains.forEach(updatedRuleChain -> {
tbClusterService.broadcastEntityStateChangeEvent(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED);
});
}
logEntityAction(ruleChain.getId(), ruleChain,
null,
ActionType.UPDATED, null, ruleChainMetaData);
logEntityAction(ruleChain.getId(), ruleChain, null, ActionType.UPDATED, null, ruleChainMetaData);
for (RuleChain updatedRuleChain : updatedRuleChains) {
RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId()));
logEntityAction(updatedRuleChain.getId(), updatedRuleChain, null, ActionType.UPDATED, null, updatedRuleChainMetaData);
}
if (RuleChainType.EDGE.equals(ruleChain.getType())) {
sendEntityNotificationMsg(ruleChain.getTenantId(),
ruleChain.getId(), EdgeEventActionType.UPDATED);
sendEntityNotificationMsg(ruleChain.getTenantId(), ruleChain.getId(), EdgeEventActionType.UPDATED);
updatedRuleChains.forEach(updatedRuleChain -> {
sendEntityNotificationMsg(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), EdgeEventActionType.UPDATED);
});
}
return savedRuleChainMetaData;
@ -681,7 +747,7 @@ public class RuleChainController extends BaseController {
}
@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')")
@RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody

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

@ -210,6 +210,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ...");
case "3.3.1":
log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ...");
case "3.3.2":
log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.2");
dataUpdateService.updateData("3.3.2");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;

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

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

30
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.Statement;
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.ASSIGNED_CUSTOMERS;
@ -469,6 +470,18 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e);
}
break;
case "3.3.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;");
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
@ -476,10 +489,25 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
private void loadSql(Path sqlFile, Connection conn) throws Exception {
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);
}
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) {
boolean isOldSchema = true;
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.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.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
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.AlarmSeverity;
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.kv.BaseReadTsKvQuery;
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.query.DynamicValue;
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.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
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.entityview.EntityViewService;
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.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantService;
@ -76,6 +85,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private TenantService tenantService;
@Autowired
private RelationService relationService;
@Autowired
private RuleChainService ruleChainService;
@ -125,6 +137,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params();
break;
case "3.3.2":
log.info("Updating data from version 3.3.2 to 3.3.3 ...");
updateNestedRuleChains();
break;
default:
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 =
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);
}

2
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java

@ -73,7 +73,7 @@ public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingF
throw new AuthenticationServiceException("Invalid login request payload");
}
if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isBlank(loginRequest.getPassword())) {
if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isEmpty(loginRequest.getPassword())) {
throw new AuthenticationServiceException("Username or Password not provided");
}

4
application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java

@ -27,6 +27,7 @@ import org.passay.PasswordData;
import org.passay.PasswordValidator;
import org.passay.Rule;
import org.passay.RuleResult;
import org.passay.WhitespaceRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@ -174,6 +175,9 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
if (isPositiveInteger(passwordPolicy.getMinimumSpecialCharacters())) {
passwordRules.add(new CharacterRule(EnglishCharacterData.Special, passwordPolicy.getMinimumSpecialCharacters()));
}
if (passwordPolicy.getAllowWhitespaces() != null && !passwordPolicy.getAllowWhitespaces()) {
passwordRules.add(new WhitespaceRule());
}
PasswordValidator validator = new PasswordValidator(passwordRules);
PasswordData passwordData = new PasswordData(password);
RuleResult result = validator.validate(passwordData);

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

@ -222,7 +222,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
}
return subscriptionUpdate;
});
}, true);
if (entityId.getEntityType() == EntityType.DEVICE) {
updateDeviceInactivityTimeout(tenantId, entityId, ts);
}
@ -256,7 +256,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
}
return subscriptionUpdate;
});
}, true);
if (entityId.getEntityType() == EntityType.DEVICE) {
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) {
updateDeviceInactivityTimeout(tenantId, entityId, attributes);
@ -333,14 +333,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
}
return subscriptionUpdate;
});
}, false);
callback.onSuccess();
}
private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId,
Function<TbSubscription, T> castFunction,
Predicate<T> filterFunction,
Function<T, List<TsKvEntry>> processFunction) {
Function<T, List<TsKvEntry>> processFunction,
boolean ignoreEmptyUpdates) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (entitySubscriptions != null) {
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);
} else {
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) {
return toProto(subscription, updates, true);
}
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates, boolean ignoreEmptyUpdates) {
TbSubscriptionUpdateProto.Builder builder = TbSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId());
@ -487,14 +492,16 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
boolean hasData = false;
for (Object v : value) {
Object[] array = (Object[]) v;
dataBuilder.addTs((long) array[0]);
TbSubscriptionUpdateTsValue.Builder tsValueBuilder = TbSubscriptionUpdateTsValue.newBuilder();
tsValueBuilder.setTs((long) array[0]);
String strVal = (String) array[1];
if (strVal != null) {
hasData = true;
dataBuilder.addValue(strVal);
tsValueBuilder.setValue(strVal);
}
dataBuilder.addTsValue(tsValueBuilder.build());
}
if (hasData) {
if (!ignoreEmptyUpdates || hasData) {
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.query.AlarmDataQuery;
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.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
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.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -68,7 +65,6 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@ -83,8 +79,6 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@Slf4j
@ -133,6 +127,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription;
@Value("${server.ws.max_alarm_queries_per_refresh_interval:3}")
private int maxAlarmQueriesPerRefreshInterval;
private ExecutorService wsCallBackExecutor;
private boolean tsInSqlDB;
@ -282,7 +278,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
if (adq.getPageLink().getTimeWindow() > 0) {
TbAlarmDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
finalCtx::cleanupOldAlarms, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx::checkAndResetInvocationCounter, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
}
}
@ -345,7 +341,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
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());
sessionSubs.put(cmd.getCmdId(), 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<>();
}
@Override
public void fetchData() {
this.data = findEntityData();
}
@ -71,6 +72,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
return result;
}
@Override
protected synchronized void update() {
long start = System.currentTimeMillis();
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 maxAlarmQueriesPerRefreshInterval;
@Getter
@Setter
private PageData<AlarmData> alarms;
@ -75,18 +77,30 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
@Setter
private boolean tooManyEntities;
private int alarmInvocationAttempts;
public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
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);
this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription;
this.maxAlarmQueriesPerRefreshInterval = maxAlarmQueriesPerRefreshInterval;
this.alarmService = alarmService;
this.entitiesMap = new LinkedHashMap<>();
this.alarmsMap = new HashMap<>();
}
public void fetchAlarms() {
alarmInvocationAttempts++;
log.trace("[{}] Fetching alarms: {}", cmdId, alarmInvocationAttempts);
if (alarmInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) {
doFetchAlarms();
}
}
private void doFetchAlarms() {
AlarmDataUpdate update;
if (!entitiesMap.isEmpty()) {
long start = System.currentTimeMillis();
@ -103,6 +117,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
}
public void fetchData() {
resetInvocationCounter();
log.trace("[{}] Fetching data: {}", cmdId, alarmInvocationAttempts);
super.fetchData();
entitiesMap.clear();
tooManyEntities = data.hasNext();
@ -222,7 +238,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
}
}
if (shouldRefresh) {
fetchAlarms();
doFetchAlarms();
}
}
@ -256,8 +272,19 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
return true;
}
public synchronized void checkAndResetInvocationCounter() {
boolean fetchNeeded = this.alarmInvocationAttempts > maxAlarmQueriesPerRefreshInterval;
resetInvocationCounter();
if (fetchNeeded) {
fetchAlarms();
} else {
cleanupOldAlarms();
}
}
@Override
protected synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
resetInvocationCounter();
entitiesMap.clear();
tooManyEntities = data.hasNext();
for (EntityData entityData : data.getData()) {
@ -295,6 +322,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
subsToAdd.forEach(localSubscriptionService::addSubscription);
}
private void resetInvocationCounter() {
alarmInvocationAttempts = 0;
}
@Override
protected EntityDataQuery buildEntityDataQuery() {
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);
}
@Override
public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
List<Integer> subIdsToCancel = 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.TbSubscriptionProto;
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.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
@ -171,10 +172,11 @@ public class TbSubscriptionUtils {
Map<String, List<Object>> data = new TreeMap<>();
proto.getDataList().forEach(v -> {
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];
value[0] = v.getTs(i);
value[1] = v.getValue(i);
TbSubscriptionUpdateTsValue tsValue = v.getTsValue(i);
value[0] = tsValue.getTs();
value[1] = tsValue.hasValue() ? tsValue.getValue() : null;
values.add(value);
}
});

58
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -20,8 +20,10 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
@ -45,7 +47,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import javax.annotation.Nullable;
@ -118,28 +119,46 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, true);
}
@Override
public void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, false);
}
private void doSaveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback, boolean saveLatest) {
checkInternalEntity(entityId);
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() {
@Override
public void onSuccess(Integer result) {
if (!sysTenant && result != null && result > 0) {
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
});
if (saveLatest) {
saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
} else {
saveWithoutLatestAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
}
} else {
callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
}
}
@NotNull
private FutureCallback<Integer> getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) {
return new FutureCallback<>() {
@Override
public void onSuccess(Integer result) {
if (!sysTenant && result != null && result > 0) {
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback) {
saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback);
@ -148,6 +167,15 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
}
private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
}
private void addCallbacks(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback, ListenableFuture<Integer> saveFuture) {
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) {

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.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
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.OtaPackageInfo;
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.TenantProfile;
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.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.
*/
@ -181,71 +184,89 @@ public class DefaultTransportApiService implements TransportApiService {
//TODO: Make async and enable caching
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId);
if (credentials != null && credentials.getCredentialsType() == credentialsType) {
return getDeviceInfo(credentials.getDeviceId(), credentials);
return getDeviceInfo(credentials);
} else {
return getEmptyTransportApiResponseFuture();
}
}
private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = null;
if (!StringUtils.isEmpty(mqtt.getUserName())) {
credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
DeviceCredentials credentials;
if (StringUtils.isEmpty(mqtt.getUserName())) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
if (credentials != null) {
if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
return getDeviceInfo(credentials.getDeviceId(), credentials);
} else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
if (!checkMqttCredentials(mqtt, credentials)) {
credentials = null;
}
} else {
return getDeviceInfo(credentials);
} else {
return getEmptyTransportApiResponseFuture();
}
} else {
credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(
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();
} 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) {
return getDeviceInfo(credentials.getDeviceId(), credentials);
} else {
return getEmptyTransportApiResponseFuture();
switch (credentials.getCredentialsType()) {
case ACCESS_TOKEN:
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) {
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 (!checkMqttCredentials(clientCred, deviceCredentials)) {
return null;
} else {
if (VALID.equals(validateMqttCredentials(clientCred, deviceCredentials))) {
return deviceCredentials;
}
}
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);
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())) {
return false;
return BasicCredentialsValidationResult.HASH_MISMATCH;
}
if (!StringUtils.isEmpty(dbCred.getPassword())) {
if (StringUtils.isEmpty(clientCred.getPassword())) {
return false;
return BasicCredentialsValidationResult.PASSWORD_MISMATCH;
} else {
if (!dbCred.getPassword().equals(clientCred.getPassword())) {
return false;
}
return dbCred.getPassword().equals(clientCred.getPassword()) ? VALID : BasicCredentialsValidationResult.PASSWORD_MISMATCH;
}
}
return true;
return VALID;
}
private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) {
@ -437,10 +458,10 @@ public class DefaultTransportApiService implements TransportApiService {
.build());
}
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, credentials.getDeviceId()), device -> {
if (device == null) {
log.trace("[{}] Failed to lookup device by id", deviceId);
log.trace("[{}] Failed to lookup device by id", credentials.getDeviceId());
return getEmptyTransportApiResponse();
}
try {
@ -458,7 +479,7 @@ public class DefaultTransportApiService implements TransportApiService {
return TransportApiResponseMsg.newBuilder()
.setValidateCredResponseMsg(builder.build()).build();
} 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();
}
}, MoreExecutors.directExecutor());

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

@ -70,6 +70,7 @@ server:
dynamic_page_link:
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}"
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_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}"
@ -211,7 +212,7 @@ cassandra:
init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}"
max_requests_per_connection_local: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_LOCAL:32768}"
max_requests_per_connection_remote: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_REMOTE:32768}"
# Credential parameters #
# Credential parameters
credentials: "${CASSANDRA_USE_CREDENTIALS:false}"
# Specify your username
username: "${CASSANDRA_USERNAME:}"
@ -379,50 +380,50 @@ cache:
caffeine:
specs:
relations:
timeToLiveInMinutes: 1440
maxSize: 10000 # maxSize: 0 means the cache is disabled
timeToLiveInMinutes: "${CACHE_SPECS_RELATIONS_TTL:1440}"
maxSize: "${CACHE_SPECS_RELATIONS_MAX_SIZE:10000}" # maxSize: 0 means the cache is disabled
deviceCredentials:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_CREDENTIALS_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICE_CREDENTIALS_MAX_SIZE:10000}"
devices:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_DEVICES_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICES_MAX_SIZE:10000}"
sessions:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_SESSIONS_TTL:1440}"
maxSize: "${CACHE_SPECS_SESSIONS_MAX_SIZE:10000}"
assets:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_ASSETS_TTL:1440}"
maxSize: "${CACHE_SPECS_ASSETS_MAX_SIZE:10000}"
entityViews:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_VIEWS_TTL:1440}"
maxSize: "${CACHE_SPECS_ENTITY_VIEWS_MAX_SIZE:10000}"
claimDevices:
timeToLiveInMinutes: 1440
maxSize: 1000
timeToLiveInMinutes: "${CACHE_SPECS_CLAIM_DEVICES_TTL:1440}"
maxSize: "${CACHE_SPECS_CLAIM_DEVICES_MAX_SIZE:1000}"
securitySettings:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_SECURITY_SETTINGS_TTL:1440}"
maxSize: "${CACHE_SPECS_SECURITY_SETTINGS_MAX_SIZE:10000}"
tenantProfiles:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_TENANT_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_TENANT_PROFILES_MAX_SIZE:10000}"
deviceProfiles:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}"
attributes:
timeToLiveInMinutes: 1440
maxSize: 100000
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
tokensOutdatageTime:
timeToLiveInMinutes: 20000
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_TOKENS_OUTDATAGE_TIME_TTL:20000}"
maxSize: "${CACHE_SPECS_TOKENS_OUTDATAGE_TIME_MAX_SIZE:10000}"
otaPackages:
timeToLiveInMinutes: 60
maxSize: 10
timeToLiveInMinutes: "${CACHE_SPECS_OTA_PACKAGES_TTL:60}"
maxSize: "${CACHE_SPECS_OTA_PACKAGES_MAX_SIZE:10}"
otaPackagesData:
timeToLiveInMinutes: 60
maxSize: 10
timeToLiveInMinutes: "${CACHE_SPECS_OTA_PACKAGES_DATA_TTL:60}"
maxSize: "${CACHE_SPECS_OTA_PACKAGES_DATA_MAX_SIZE:10}"
edges:
timeToLiveInMinutes: 1440
maxSize: 10000
timeToLiveInMinutes: "${CACHE_SPECS_EDGES_TTL:1440}"
maxSize: "${CACHE_SPECS_EDGES_MAX_SIZE:10000}"
redis:
# standalone or cluster
@ -595,7 +596,7 @@ js:
remote:
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}"
# Maximum time in seconds for black listed function to stay in 1:the list.
# Maximum time in seconds for black listed function to stay in the list.
max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}"
stats:
enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}"
@ -816,9 +817,11 @@ transport:
downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}"
ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}"
# Use redis for Security and Registration stores
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:
enabled: "${SNMP_ENABLED:true}"
response_processing:
@ -1030,8 +1033,8 @@ queue:
# For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries.
pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}" # Max allowed time in seconds for pause between retries.
- name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
@ -1047,8 +1050,8 @@ queue:
# For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries.
- name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
@ -1064,8 +1067,8 @@ queue:
# For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries.
transport:
# For high priority notifications that require minimum latency and processing time
notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"

257
application/src/test/java/org/thingsboard/server/actors/ActorSystemContextTest.java

@ -0,0 +1,257 @@
/**
* 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;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeStateService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.session.DeviceSessionCacheService;
import org.thingsboard.server.service.sms.SmsExecutorService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ActorSystemContext.class)
@EnableConfigurationProperties
@TestPropertySource(properties = {
"cache.type=caffeine",
})
public class ActorSystemContextTest {
@Autowired
ActorSystemContext ctx;
@MockBean
private TbApiUsageStateService apiUsageStateService;
@MockBean
private TbApiUsageClient apiUsageClient;
@MockBean
private TbServiceInfoProvider serviceInfoProvider;
@MockBean
private ActorService actorService;
@MockBean
private ComponentDiscoveryService componentService;
@MockBean
private DataDecodingEncodingService encodingService;
@MockBean
private DeviceService deviceService;
@MockBean
private TbTenantProfileCache tenantProfileCache;
@MockBean
private TbDeviceProfileCache deviceProfileCache;
@MockBean
private AssetService assetService;
@MockBean
private DashboardService dashboardService;
@MockBean
private TenantService tenantService;
@MockBean
private TenantProfileService tenantProfileService;
@MockBean
private CustomerService customerService;
@MockBean
private UserService userService;
@MockBean
private RuleChainService ruleChainService;
@MockBean
private RuleNodeStateService ruleNodeStateService;
@MockBean
private PartitionService partitionService;
@MockBean
private TbClusterService clusterService;
@MockBean
private TimeseriesService tsService;
@MockBean
private AttributesService attributesService;
@MockBean
private EventService eventService;
@MockBean
private RelationService relationService;
@MockBean
private AuditLogService auditLogService;
@MockBean
private EntityViewService entityViewService;
@MockBean
private TelemetrySubscriptionService tsSubService;
@MockBean
private AlarmSubscriptionService alarmService;
@MockBean
private JsInvokeService jsSandbox;
@MockBean
private MailExecutorService mailExecutor;
@MockBean
private SmsExecutorService smsExecutor;
@MockBean
private DbCallbackExecutorService dbCallbackExecutor;
@MockBean
private ExternalCallExecutorService externalCallExecutorService;
@MockBean
private SharedEventLoopGroupService sharedEventLoopGroupService;
@MockBean
private MailService mailService;
@MockBean
private SmsService smsService;
@MockBean
private SmsSenderFactory smsSenderFactory;
@MockBean
private ClaimDevicesService claimDevicesService;
@MockBean
private JsInvokeStats jsInvokeStats;
@MockBean
private DeviceStateService deviceStateService;
@MockBean
private DeviceSessionCacheService deviceSessionCacheService;
@MockBean
private TbCoreToTransportService tbCoreToTransportService;
@MockBean
private TbRuleEngineDeviceRpcService tbRuleEngineDeviceRpcService;
@MockBean
private TbCoreDeviceRpcService tbCoreDeviceRpcService;
@MockBean
private EdgeService edgeService;
@MockBean
private EdgeEventService edgeEventService;
@MockBean
private EdgeRpcService edgeRpcService;
@MockBean
private ResourceService resourceService;
@MockBean
private OtaPackageService otaPackageService;
@MockBean
private TbRpcService tbRpcService;
@MockBean
private CassandraCluster cassandraCluster;
@MockBean
private CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor;
@MockBean
private CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor;
@MockBean
private RedisTemplate<String, Object> redisTemplate;
@Test
void givenCaffeineCache_whenInit_thenIsLocalCacheTrue() {
assertThat(ctx.getCacheType()).isEqualTo("caffeine");
assertThat(ctx.isLocalCacheType()).as("caffeine is the local cache type").isTrue();
}
}

4
application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTestSuite.java → application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java

@ -29,11 +29,11 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = CaffeineCacheDefaultConfigurationTestSuite.class, loader = SpringBootContextLoader.class)
@ContextConfiguration(classes = CaffeineCacheDefaultConfigurationTest.class, loader = SpringBootContextLoader.class)
@ComponentScan({"org.thingsboard.server.cache"})
@EnableConfigurationProperties
@Slf4j
public class CaffeineCacheDefaultConfigurationTestSuite {
public class CaffeineCacheDefaultConfigurationTest {
@Autowired
CaffeineCacheConfiguration caffeineCacheConfiguration;

42
application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.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.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.thingsboard.server.queue.memory.InMemoryStorage;
@Slf4j
public abstract class AbstractInMemoryStorageTest {
@Before
public void setUpInMemoryStorage() {
log.info("set up InMemoryStorage");
cleanupInMemStorage();
}
@After
public void tearDownInMemoryStorage() {
log.info("tear down InMemoryStorage");
cleanupInMemStorage();
}
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

47
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -22,7 +22,6 @@ import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
@ -59,18 +58,20 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.HasId;
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.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.service.mail.TestMailService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
@ -81,6 +82,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@ -93,7 +95,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@Slf4j
public abstract class AbstractWebTest {
public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected ObjectMapper mapper = new ObjectMapper();
@ -132,6 +134,9 @@ public abstract class AbstractWebTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private TenantProfileService tenantProfileService;
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
@ -161,8 +166,9 @@ public abstract class AbstractWebTest {
}
@Before
public void setup() throws Exception {
log.info("Executing setup");
public void setupWebTest() throws Exception {
log.info("Executing web test setup");
if (this.mockMvc == null) {
this.mockMvc = webAppContextSetup(webApplicationContext)
.apply(springSecurity()).build();
@ -197,16 +203,38 @@ public abstract class AbstractWebTest {
logout();
log.info("Executed setup");
log.info("Executed web test setup");
}
@After
public void teardown() throws Exception {
log.info("Executing teardown");
public void teardownWebTest() throws Exception {
log.info("Executing web test teardown");
loginSysAdmin();
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
log.info("Executed teardown");
verifyNoTenantsLeft();
tenantProfileService.deleteTenantProfiles(TenantId.SYS_TENANT_ID);
log.info("Executed web test teardown");
}
void verifyNoTenantsLeft() throws Exception {
List<Tenant> loadedTenants = new ArrayList<>();
PageLink pageLink = new PageLink(10);
PageData<Tenant> pageData;
do {
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>() {
}, pageLink);
loadedTenants.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
assertThat(loadedTenants).as("All tenants expected to be deleted, but some tenants left in the database").isEmpty();
}
protected void loginSysAdmin() throws Exception {
@ -570,6 +598,7 @@ public abstract class AbstractWebTest {
protected Edge constructEdge(String name, String type) {
return constructEdge(tenantId, name, type);
}
protected Edge constructEdge(TenantId tenantId, String name, String type) {
Edge edge = new Edge();
edge.setTenantId(tenantId);

196
application/src/test/java/org/thingsboard/server/controller/BaseRpcControllerTest.java

@ -0,0 +1,196 @@
/**
* 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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.security.Authority;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseRpcControllerTest extends AbstractControllerTest {
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() 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("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
private Device createDefaultDevice() {
Device device = new Device();
device.setName("My device");
device.setType("default");
return device;
}
private ObjectNode createDefaultRpc() {
ObjectNode rpc = JacksonUtil.newObjectNode();
rpc.put("method", "setGpio");
ObjectNode params = JacksonUtil.newObjectNode();
params.put("pin", 7);
params.put("value", 1);
rpc.set("params", params);
rpc.put("persistent", true);
rpc.put("timeout", 5000);
return rpc;
}
private Rpc getRpcById(String rpcId) throws Exception {
return doGet("/api/rpc/persistent/" + rpcId, Rpc.class);
}
private MvcResult removeRpcById(String rpcId) throws Exception {
return doDelete("/api/rpc/persistent/" + rpcId).andReturn();
}
@Test
public void testSaveRpc() throws Exception {
Device device = createDefaultDevice();
Device savedDevice = doPost("/api/device", device, Device.class);
ObjectNode rpc = createDefaultRpc();
String result = doPostAsync(
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(),
JacksonUtil.toString(rpc),
String.class,
status().isOk()
);
String rpcId = JacksonUtil.fromString(result, JsonNode.class)
.get("rpcId")
.asText();
Rpc savedRpc = getRpcById(rpcId);
Assert.assertNotNull(savedRpc);
Assert.assertEquals(savedDevice.getId(), savedRpc.getDeviceId());
}
@Test
public void testDeleteRpc() throws Exception {
Device device = createDefaultDevice();
Device savedDevice = doPost("/api/device", device, Device.class);
ObjectNode rpc = createDefaultRpc();
String result = doPostAsync(
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(),
JacksonUtil.toString(rpc),
String.class,
status().isOk()
);
String rpcId = JacksonUtil.fromString(result, JsonNode.class)
.get("rpcId")
.asText();
Rpc savedRpc = getRpcById(rpcId);
MvcResult mvcResult = removeRpcById(savedRpc.getId().getId().toString());
MvcResult res = doGet("/api/rpc/persistent/" + rpcId)
.andExpect(status().isNotFound())
.andReturn();
JsonNode deleteResponse = JacksonUtil.fromString(res.getResponse().getContentAsString(), JsonNode.class);
Assert.assertEquals(404, deleteResponse.get("status").asInt());
String url = "/api/rpc/persistent/device/" + savedDevice.getUuidId().toString()
+ "?" + "page=0" + "&" +
"pageSize=" + Integer.MAX_VALUE + "&" +
"rpcStatus=" + RpcStatus.DELETED.name();
MvcResult byDeviceResult = doGet(url).andReturn();
JsonNode byDeviceResponse = JacksonUtil.fromString(byDeviceResult.getResponse().getContentAsString(), JsonNode.class);
Assert.assertEquals(500, byDeviceResponse.get("status").asInt());
}
@Test
public void testGetRpcsByDeviceId() throws Exception {
Device device = createDefaultDevice();
Device savedDevice = doPost("/api/device", device, Device.class);
ObjectNode rpc = createDefaultRpc();
String result = doPostAsync(
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(),
JacksonUtil.toString(rpc),
String.class,
status().isOk()
);
String rpcId = JacksonUtil.fromString(result, JsonNode.class)
.get("rpcId")
.asText();
String url = "/api/rpc/persistent/device/" + savedDevice.getId().getId()
+ "?" + "page=0" + "&" +
"pageSize=" + Integer.MAX_VALUE + "&" +
"rpcStatus=" + RpcStatus.QUEUED;
MvcResult byDeviceResult = doGetAsync(url).andReturn();
List<Rpc> byDeviceRpcs = JacksonUtil.fromString(
byDeviceResult
.getResponse()
.getContentAsString(),
new TypeReference<PageData<Rpc>>() {}
).getData();
boolean found = byDeviceRpcs.stream().anyMatch(r ->
r.getUuidId().toString().equals(rpcId)
&& r.getDeviceId().equals(savedDevice.getId())
);
Assert.assertTrue(found);
}
}

7
application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java

@ -47,13 +47,6 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController
@Autowired
private TenantProfileService tenantProfileService;
@After
@Override
public void teardown() throws Exception {
super.teardown();
tenantProfileService.deleteTenantProfiles(TenantId.SYS_TENANT_ID);
}
@Test
public void testSaveTenantProfile() throws Exception {
loginSysAdmin();

174
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -36,6 +36,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@ -50,15 +51,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
public void testSaveUser() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName("Joe");
user.setLastName("Downs");
@ -100,24 +96,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveUserWithViolationOfFiledValidation() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName(RandomStringUtils.randomAlphabetic(300));
user.setLastName("Downs");
@ -130,14 +118,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
@Test
public void testUpdateUserFromDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setTenantId(tenantId);
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
@ -147,24 +131,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doPost("/api/user", tenantAdmin, User.class, status().isForbidden());
deleteDifferentTenant();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testResetPassword() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName("Joe");
user.setLastName("Downs");
@ -205,24 +181,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk());
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindUserById() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName("Joe");
user.setLastName("Downs");
@ -231,24 +199,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertNotNull(foundUser);
Assert.assertEquals(savedUser, foundUser);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveUserWithSameEmail() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = TENANT_ADMIN_EMAIL;
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName("Joe");
user.setLastName("Downs");
@ -256,24 +216,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doPost("/api/user", user)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("User with email '" + email + "' already present in database")));
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveUserWithInvalidEmail() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant_thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName("Joe");
user.setLastName("Downs");
@ -281,32 +233,21 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doPost("/api/user", user)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format '" + email + "'")));
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveUserWithEmptyEmail() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setFirstName("Joe");
user.setLastName("Downs");
doPost("/api/user", user)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("User email should be specified")));
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
@ -328,15 +269,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
public void testDeleteUser() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org";
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
user.setTenantId(tenantId);
user.setEmail(email);
user.setFirstName("Joe");
user.setLastName("Downs");
@ -350,17 +286,15 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doGet("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isNotFound());
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindTenantAdmins() throws Exception {
loginSysAdmin();
//here created a new tenant despite already created on AbstractWebTest and then delete the tenant properly on the last line
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
tenant.setTitle("My tenant with many admins");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
@ -380,7 +314,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
PageData<User> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
new TypeReference<PageData<User>>() {
}, pageLink);
loadedTenantAdmins.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -390,14 +325,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
Collections.sort(tenantAdmins, idComparator);
Collections.sort(loadedTenantAdmins, idComparator);
Assert.assertEquals(tenantAdmins, loadedTenantAdmins);
assertThat(tenantAdmins).as("admins list size").hasSameSizeAs(loadedTenantAdmins);
assertThat(tenantAdmins).as("admins list content").isEqualTo(loadedTenantAdmins);
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
pageLink = new PageLink(33);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
}
@ -407,13 +344,6 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId();
String email1 = "testEmail1";
List<User> tenantAdminsEmail1 = new ArrayList<>();
@ -447,7 +377,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
PageData<User> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
new TypeReference<PageData<User>>() {
}, pageLink);
loadedTenantAdminsEmail1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -463,7 +394,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
pageLink = new PageLink(16, 0, email2);
do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
new TypeReference<PageData<User>>() {
}, pageLink);
loadedTenantAdminsEmail2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -481,8 +413,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
}
pageLink = new PageLink(4, 0, email1);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
@ -492,25 +425,17 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
}
pageLink = new PageLink(4, 0, email2);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<PageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindCustomerUsers() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(tenantId);
@ -540,7 +465,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
PageData<User> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
new TypeReference<PageData<User>>() {
}, pageLink);
loadedCustomerUsers.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -554,23 +480,12 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doDelete("/api/customer/" + customerId.getId().toString())
.andExpect(status().isOk());
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindCustomerUsersByEmail() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(tenantId);
@ -619,7 +534,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
PageData<User> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
new TypeReference<PageData<User>>() {
}, pageLink);
loadedCustomerUsersEmail1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -635,7 +551,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
pageLink = new PageLink(16, 0, email2);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
new TypeReference<PageData<User>>() {
}, pageLink);
loadedCustomerUsersEmail2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -653,8 +570,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
}
pageLink = new PageLink(4, 0, email1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
@ -664,18 +582,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
}
pageLink = new PageLink(4, 0, email2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>(){}, pageLink);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<PageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
doDelete("/api/customer/" + customerId.getId().toString())
.andExpect(status().isOk());
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
}

15
application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java

@ -16,14 +16,10 @@
package org.thingsboard.server.controller;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
// "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
@ -31,17 +27,12 @@ import java.util.Arrays;
// "org.thingsboard.server.controller.sql.TbResourceControllerSqlTest",
// "org.thingsboard.server.controller.sql.DeviceProfileControllerSqlTest",
"org.thingsboard.server.controller.sql.*Test",
})
})
public class ControllerSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

3
application/src/test/java/org/thingsboard/server/controller/sql/ComponentDescriptorControllerSqlTest.java

@ -18,9 +18,6 @@ package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseComponentDescriptorControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
/**
* Created by Valerii Sosliuk on 6/28/2017.
*/
@DaoSqlTest
public class ComponentDescriptorControllerSqlTest extends BaseComponentDescriptorControllerTest {
}

17
dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java → application/src/test/java/org/thingsboard/server/controller/sql/RpcControllerTest.java

@ -13,18 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
package org.thingsboard.server.controller.sql;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters;
import org.junit.runner.RunWith;
import org.thingsboard.server.controller.BaseRpcControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
"org.thingsboard.server.dao.service.psql.*SqlTest",
"org.thingsboard.server.dao.service.attributes.psql.*SqlTest",
"org.thingsboard.server.dao.service.event.psql.*SqlTest",
"org.thingsboard.server.dao.service.timeseries.psql.*SqlTest"
})
public class PostgreSqlDaoServiceTestSuite {
@DaoSqlTest
public class RpcControllerTest extends BaseRpcControllerTest {
}

16
application/src/test/java/org/thingsboard/server/edge/EdgeSqlTestSuite.java

@ -16,26 +16,18 @@
package org.thingsboard.server.edge;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.edge.sql.*Test"})
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.edge.sql.*Test",
})
public class EdgeSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

16
application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java

@ -16,28 +16,20 @@
package org.thingsboard.server.rules;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.rules.flow.sql.*Test",
"org.thingsboard.server.rules.lifecycle.sql.*Test"})
"org.thingsboard.server.rules.lifecycle.sql.*Test",
})
public class RuleEngineSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

7
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -42,11 +42,14 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@ -132,6 +135,8 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis())));
await("total inMemory queue lag is empty").atMost(30, TimeUnit.SECONDS)
.until(() -> InMemoryStorage.getInstance().getLagTotal() == 0);
Thread.sleep(1000);
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
@ -139,7 +144,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system
actorSystem.tell(qMsg);
Mockito.verify(tbMsgCallback, Mockito.timeout(3000)).onSuccess();
Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess();
PageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);

15
application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java

@ -16,28 +16,19 @@
package org.thingsboard.server.service;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.service.resource.sql.*Test",
})
})
public class ServiceSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

16
application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java

@ -16,29 +16,21 @@
package org.thingsboard.server.system;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
/**
* Created by Valerii Sosliuk on 6/27/2017.
*/
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.system.sql.*SqlTest"})
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.system.sql.*SqlTest",
})
public class SystemSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}

13
application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java

@ -21,22 +21,16 @@ import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomCassandraCQLUnit;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.transport.*.telemetry.timeseries.nosql.*Test"})
"org.thingsboard.server.transport.*.telemetry.timeseries.nosql.*Test",
})
public class TransportNoSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"nosql-test.properties");
@ClassRule
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
@ -47,7 +41,8 @@ public class TransportNoSqlTestSuite {
"cassandra-test.yaml", 30000l);
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

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

@ -16,14 +16,10 @@
package org.thingsboard.server.transport;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.transport.*.rpc.sql.*Test",
@ -33,18 +29,14 @@ import java.util.Arrays;
"org.thingsboard.server.transport.*.attributes.request.sql.*Test",
"org.thingsboard.server.transport.*.claim.sql.*Test",
"org.thingsboard.server.transport.*.provision.sql.*Test",
"org.thingsboard.server.transport.*.credentials.sql.*Test",
"org.thingsboard.server.transport.lwm2m.sql.*Test"
})
public class TransportSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

12
application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java

@ -24,7 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@ -70,15 +70,15 @@ public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends
" }\n" +
"}";
@After
public void afterTest() throws Exception {
processAfterTest();
@Before
@Override
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server proto", CoapDeviceType.DEFAULT,
TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED);
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", CoapDeviceType.DEFAULT,
TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED);
processTestRequestAttributesValuesFromTheServer();
}

11
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java

@ -20,7 +20,7 @@ import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType;
@ -39,14 +39,14 @@ import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractCoapAttributesProtoIntegrationTest extends AbstractCoapAttributesIntegrationTest {
@After
public void afterTest() throws Exception {
processAfterTest();
@Before
@Override
public void beforeTest() throws Exception {
processBeforeTest("Test Post Attributes device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
}
@Test
public void testPushAttributes() throws Exception {
super.processBeforeTest("Test Post Attributes device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
@ -90,7 +90,6 @@ public abstract class AbstractCoapAttributesProtoIntegrationTest extends Abstrac
@Test
public void testPushAttributesWithExplicitPresenceProtoKeys() throws Exception {
super.processBeforeTest("Test Post Attributes device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;

20
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java

@ -20,7 +20,7 @@ import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadCo
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@ -41,14 +40,15 @@ import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends AbstractCoapTimeseriesIntegrationTest {
@After
public void afterTest() throws Exception {
processAfterTest();
@Before
@Override
public void beforeTest() throws Exception {
//do nothing, processBeforeTest will be invoked in particular test methods with different parameters
}
@Test
public void testPushTelemetry() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
@ -117,7 +117,7 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac
" }\n" +
" }\n" +
"}";
super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
@ -167,12 +167,12 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac
.setField(postTelemetryMsgDescriptor.findFieldByName("values"), valuesMsg)
.build();
processTestPostTelemetry(postTelemetryMsg.toByteArray(), Arrays.asList("key1", "key2", "key3", "key4", "key5"), true, false);
processTestPostTelemetry(postTelemetryMsg.toByteArray(), Arrays.asList("key1", "key2", "key3", "key4", "key5"), true, false);
}
@Test
public void testPushTelemetryWithExplicitPresenceProtoKeys() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
@ -239,7 +239,7 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac
" }\n" +
" }\n" +
"}";
super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;

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;
}
}

23
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java

@ -20,7 +20,7 @@ import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
@ -42,27 +42,28 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac
private static final String POST_DATA_ATTRIBUTES_TOPIC = "proto/attributes";
@After
public void afterTest() throws Exception {
processAfterTest();
@Before
@Override
public void beforeTest() throws Exception {
//do nothing, processBeforeTest will be invoked in particular test methods with different parameters
}
@Test
public void testPushAttributes() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
DynamicMessage postAttributesMsg = getDefaultDynamicMessage();
processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false);
}
@Test
public void testPushAttributesWithEnabledJsonBackwardCompatibility() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC, true, false);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC, true, false);
processJsonPayloadAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes());
}
@Test
public void testPushAttributesWithExplicitPresenceProtoKeys() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
DynamicSchema attributesSchema = getDynamicSchema();
DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject");
@ -95,27 +96,27 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac
@Test
public void testPushAttributesOnShortTopic() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
DynamicMessage postAttributesMsg = getDefaultDynamicMessage();
processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false);
}
@Test
public void testPushAttributesOnShortJsonTopic() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
processJsonPayloadAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes());
}
@Test
public void testPushAttributesOnShortProtoTopic() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
DynamicMessage postAttributesMsg = getDefaultDynamicMessage();
processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false);
}
@Test
public void testPushAttributesGateway() throws Exception {
super.processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, null);
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, null);
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";

31
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java

@ -21,15 +21,15 @@ import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -45,21 +45,22 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry";
@After
public void afterTest() throws Exception {
processAfterTest();
@Before
@Override
public void beforeTest() throws Exception {
//do nothing, processBeforeTest will be invoked in particular test methods with different parameters
}
@Test
public void testPushTelemetry() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
DynamicMessage postTelemetryMsg = getDefaultDynamicMessage();
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false);
}
@Test
public void testPushTelemetryWithEnabledJsonBackwardCompatibility() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true, false);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true, false);
processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false);
}
@ -90,7 +91,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" +
" }\n" +
"}";
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
DynamicSchema telemetrySchema = getDynamicSchema(schemaStr);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -135,7 +136,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test
public void testPushTelemetryWithExplicitPresenceProtoKeys() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
DynamicSchema telemetrySchema = getDynamicSchema(DEVICE_TELEMETRY_PROTO_SCHEMA);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -192,7 +193,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" +
" }\n" +
"}";
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
DynamicSchema telemetrySchema = getDynamicSchema(schemaStr);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -232,27 +233,27 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test
public void testPushTelemetryOnShortTopic() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
DynamicMessage postTelemetryMsg = getDefaultDynamicMessage();
processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false);
}
@Test
public void testPushTelemetryOnShortJsonTopic() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
processJsonPayloadTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false);
}
@Test
public void testPushTelemetryOnShortProtoTopic() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
DynamicMessage postTelemetryMsg = getDefaultDynamicMessage();
processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false);
}
@Test
public void testPushTelemetryGateway() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
@ -266,7 +267,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test
public void testGatewayConnect() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false);
String deviceName = "Device A";
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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

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

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);
}

3
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.security.DeviceCredentials;
import java.util.List;
public interface DeviceCredentialsService {
DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId);
@ -32,4 +34,5 @@ public interface DeviceCredentialsService {
void formatCredentials(DeviceCredentials deviceCredentials);
void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials);
}

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);
void deleteEntityRelations(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery 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;
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.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.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainType;
import java.util.List;
@ -78,6 +82,8 @@ public interface RelationService {
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
// 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.RuleChainImportResult;
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.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import java.util.List;
@ -42,7 +44,7 @@ public interface RuleChainService {
boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId);
boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId);
@ -88,4 +90,7 @@ public interface RuleChainService {
PageData<RuleChain> findAutoAssignToEdgeRuleChainsByTenantId(TenantId tenantId, PageLink pageLink);
List<RuleNode> findRuleNodesByTenantIdAndType(TenantId tenantId, String name, String toString);
RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java

@ -41,6 +41,8 @@ public interface TimeseriesService {
ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntry, long ttl);
ListenableFuture<Integer> saveWithoutLatest(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntry, long ttl);
ListenableFuture<List<Void>> saveLatest(TenantId tenantId, EntityId entityId, List<TsKvEntry> tsKvEntry);
ListenableFuture<List<Void>> remove(TenantId tenantId, EntityId entityId, List<DeleteTsKvQuery> queries);

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DashboardId;
@ -39,6 +40,7 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn
@ApiModel
@Data
@ToString(exclude = {"image", "profileDataBytes"})
@EqualsAndHashCode(callSuper = true)
@Slf4j
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
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@ToString(callSuper = true, exclude = "image")
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. ")

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
* limitations under the License.
*/
package org.thingsboard.server.queue.kafka;
package org.thingsboard.server.common.data;
import lombok.Data;
@ -21,7 +21,7 @@ import lombok.Data;
* Created by ashvayka on 25.09.18.
*/
@Data
public class TbKafkaProperty {
public class TbProperty {
private String key;
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/kv/BaseReadTsKvQuery.java

@ -16,8 +16,10 @@
package org.thingsboard.server.common.data.kv;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
private final long interval;

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 {
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 {
COMMON,
ALARM,
DASHBOARD,
RULE_CHAIN,
RULE_NODE,

2
common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java

@ -16,5 +16,5 @@
package org.thingsboard.server.common.data.rpc;
public enum RpcStatus {
QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, EXPIRED, FAILED
QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, EXPIRED, FAILED, DELETED
}

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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 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);
}
}

28
dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java → common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNodeUpdateResult.java

@ -13,21 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
package org.thingsboard.server.common.data.rule;
import org.springframework.test.context.TestPropertySource;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
/**
* Created by igor on 3/13/18.
*/
@Data
public class RuleNodeUpdateResult {
private final RuleNode oldRuleNode;
private final RuleNode newRuleNode;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@TestPropertySource(locations = {"classpath:application-test.properties", "classpath:psql-test.properties"})
public @interface DaoPostgreSqlTest {
}

2
common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java

@ -35,6 +35,8 @@ public class UserPasswordPolicy implements Serializable {
private Integer minimumDigits;
@ApiModelProperty(position = 1, value = "Minimum number of special in the password." )
private Integer minimumSpecialCharacters;
@ApiModelProperty(position = 1, value = "Allow whitespaces")
private Boolean allowWhitespaces = true;
@ApiModelProperty(position = 1, value = "Password expiration period (days). Force expiration of the password." )
private Integer passwordExpirationPeriodDays;

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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,
/**
* 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.
*/

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 RuleNodeId ruleNodeId;
@Getter(value = AccessLevel.NONE)
private final AtomicInteger ruleNodeExecCounter;
public int getAndIncrementRuleNodeCounter() {
return ruleNodeExecCounter.getAndIncrement();
}
@JsonIgnore
//This field is not serialized because we use queues and there is no need to do it
private final TbMsgProcessingCtx ctx;
//This field is not serialized because we use queues and there is no need to do it
@JsonIgnore
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) {
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) {
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) {
@ -80,7 +81,8 @@ public final class TbMsg implements Serializable {
}
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
@ -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) {
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) {
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) {
@ -104,46 +108,48 @@ public final class TbMsg implements Serializable {
// For Tests only
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) {
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) {
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) {
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) {
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) {
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) {
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
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(),
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,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) {
RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) {
this.id = id;
this.queueName = queueName != null ? queueName : ServiceQueue.MAIN;
if (ts > 0) {
@ -167,7 +173,7 @@ public final class TbMsg implements Serializable {
this.data = data;
this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId;
this.ruleNodeExecCounter = new AtomicInteger(ruleNodeExecCounter);
this.ctx = ctx != null ? ctx : new TbMsgProcessingCtx();
if (callback != null) {
this.callback = callback;
} else {
@ -209,7 +215,8 @@ public final class TbMsg implements Serializable {
builder.setDataType(msg.getDataType().ordinal());
builder.setData(msg.getData());
builder.setRuleNodeExecCounter(msg.ruleNodeExecCounter.get());
builder.setCtx(msg.ctx.toProto());
return builder.build().toByteArray();
}
@ -231,8 +238,17 @@ public final class TbMsg implements Serializable {
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()];
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) {
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) {
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) {
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() {
@ -262,4 +280,12 @@ public final class TbMsg implements Serializable {
public String getQueueName() {
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 {
private static final long serialVersionUID = -1776681087370749776L;
public static final String UNKNOWN = "Unknown";
@Getter
private final String ruleChainName;
@ -45,7 +46,7 @@ public class RuleNodeException extends RuleEngineException {
this.ruleChainId = ruleNode.getRuleChainId();
this.ruleNodeId = ruleNode.getId();
} else {
ruleNodeName = "Unknown";
ruleNodeName = UNKNOWN;
ruleChainId = new RuleChainId(RuleChainId.NULL_UUID);
ruleNodeId = new RuleNodeId(RuleNodeId.NULL_UUID);
}

1
common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimitsException.java

@ -26,6 +26,7 @@ public class TbRateLimitsException extends RuntimeException {
private final EntityType entityType;
public TbRateLimitsException(EntityType entityType) {
super(entityType.name() + " rate limits reached!");
this.entityType = entityType;
}
}

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_outer_classname = "MsgProtos";
// Stores message metadata as map of strings
message TbMsgMetaDataProto {
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 {
string id = 1;
string type = 2;
@ -39,14 +53,17 @@ message TbMsgProto {
TbMsgMetaDataProto metaData = 11;
//Transaction Data (12) was removed in 2.5
// Transaction Data (12) was removed in 2.5
int32 dataType = 13;
string data = 14;
int64 ts = 15;
// Will be removed in 3.4. Moved to processing context
int32 ruleNodeExecCounter = 16;
int64 customerIdMSB = 17;
int64 customerIdLSB = 18;
TbMsgProcessingCtxProto ctx = 19;
}

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TbProperty;
import java.util.Collections;
import java.util.List;
@ -104,10 +105,10 @@ public class TbKafkaSettings {
private String securityProtocol;
@Setter
private List<TbKafkaProperty> other;
private List<TbProperty> other;
@Setter
private Map<String, List<TbKafkaProperty>> consumerPropertiesPerTopic = Collections.emptyMap();
private Map<String, List<TbProperty>> consumerPropertiesPerTopic = Collections.emptyMap();
public Properties toAdminProps() {
Properties props = toProps();

4
common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java

@ -42,6 +42,10 @@ public final class InMemoryStorage {
});
}
public int getLagTotal() {
return storage.values().stream().map(BlockingQueue::size).reduce(0, Integer::sum);
}
public static InMemoryStorage getInstance() {
if (instance == null) {
synchronized (InMemoryStorage.class) {

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()) {
log.info("Reporting API usage statistics for {} tenants and customers", report.size());
log.debug("Reporting API usage statistics for {} tenants and customers", report.size());
}
}

54
common/queue/src/test/java/org/thingsboard/server/queue/memory/InMemoryStorageTest.java

@ -0,0 +1,54 @@
/**
* 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.queue.memory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.queue.TbQueueMsg;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
public class InMemoryStorageTest {
InMemoryStorage storage = InMemoryStorage.getInstance();
@Before
public void setUp() {
storage.cleanup();
}
@After
public void tearDown() {
storage.cleanup();
}
@Test
public void givenStorage_whenGetLagTotal_thenReturnInteger() throws InterruptedException {
assertThat(storage.getLagTotal()).isEqualTo(0);
storage.put("main", mock(TbQueueMsg.class));
assertThat(storage.getLagTotal()).isEqualTo(1);
storage.put("main", mock(TbQueueMsg.class));
assertThat(storage.getLagTotal()).isEqualTo(2);
storage.put("hp", mock(TbQueueMsg.class));
assertThat(storage.getLagTotal()).isEqualTo(3);
storage.get("main");
assertThat(storage.getLagTotal()).isEqualTo(1);
storage.cleanup();
assertThat(storage.getLagTotal()).isEqualTo(0);
}
}

2
common/stats/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

2
common/transport/lwm2m/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard.common</groupId>
<version>3.3.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.common.transport</groupId>

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

Loading…
Cancel
Save