Browse Source

Added ability to provision device using protobuf

pull/3518/head
zbeacon 6 years ago
parent
commit
8c9dda1b4d
  1. 3
      application/src/main/data/json/demo/rule_chains/root_rule_chain.json
  2. 3
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  3. 7
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  4. 7
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  5. 11
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  7. 28
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  8. 2
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  9. 215
      application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java
  10. 2
      application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java
  11. 8
      application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java
  12. 111
      application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java
  13. 150
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java
  14. 51
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java
  15. 201
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java
  16. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java
  17. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java
  18. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java
  19. 23
      application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java
  20. 170
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java
  21. 51
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java
  22. 149
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java
  23. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java
  24. 23
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java
  25. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java
  26. 24
      application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java
  27. 194
      application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java
  28. 57
      application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java
  29. 115
      application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java
  30. 24
      application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java
  31. 24
      application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java
  32. 24
      application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java
  33. 23
      application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java
  34. 137
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java
  35. 236
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  36. 66
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java
  37. 114
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java
  38. 4
      application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java
  39. 23
      application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java
  40. 24
      application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java
  41. 4
      application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java
  42. 163
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java
  43. 175
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java
  44. 56
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java
  45. 75
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java
  46. 23
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlIntegrationTest.java
  47. 24
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java
  48. 24
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java
  49. 23
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java
  50. 23
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java
  51. 24
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java
  52. 290
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java
  53. 82
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java
  54. 115
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  55. 6
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java
  56. 23
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java
  57. 23
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java
  58. 7
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java
  59. 27
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java
  60. 27
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java
  61. 2
      application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
  62. 1
      application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java
  63. 2
      application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
  64. 21
      common/data/src/main/java/org/thingsboard/server/common/data/TransportPayloadType.java
  65. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java
  66. 71
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java
  67. 3
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
  68. 9
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java
  69. 55
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  70. 192
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  71. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java
  72. 219
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  73. 28
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  74. 17
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
  75. 472
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
  76. 8
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java
  77. 1
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java
  78. 13
      common/transport/transport-api/pom.xml
  79. 7
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  80. 170
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java
  81. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  82. 106
      common/transport/transport-api/src/main/proto/transport.proto
  83. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  84. 2
      dao/src/test/resources/sql-test.properties
  85. 3
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
  86. 2
      msa/js-executor/package.json
  87. 19
      msa/js-executor/queue/kafkaTemplate.js
  88. 10
      msa/js-executor/yarn.lock
  89. 2
      msa/tb/README.md
  90. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  91. 252
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  92. 72
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceDataSnapshot.java
  93. 245
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileAlarmState.java
  94. 82
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java
  95. 91
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
  96. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  97. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java
  98. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  99. 15
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  100. 1
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml

3
application/src/main/data/json/demo/rule_chains/root_rule_chain.json

@ -43,7 +43,8 @@
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE" "scope": "CLIENT_SCOPE",
"notifyDevice": "false"
} }
}, },
{ {

3
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -31,7 +31,8 @@
"name": "Save Client Attributes", "name": "Save Client Attributes",
"debugMode": false, "debugMode": false,
"configuration": { "configuration": {
"scope": "CLIENT_SCOPE" "scope": "CLIENT_SCOPE",
"notifyDevice": "false"
} }
}, },
{ {

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

@ -226,6 +226,11 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
@Override @Override
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) { public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) {
onAttributesUpdate(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId, onLocalTelemetrySubUpdate(entityId,
s -> { s -> {
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) { if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
@ -254,7 +259,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L)); deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
} }
} }
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope)) { } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
, null); , null);

7
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -17,13 +17,12 @@ package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import java.util.List; import java.util.List;
@ -37,9 +36,13 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback); void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback);
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty); void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
} }

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

@ -171,9 +171,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override @Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) { public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes); ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
addMainCallback(saveFuture, callback); addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes)); addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
} }
@Override @Override
@ -236,11 +241,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
, System.currentTimeMillis())), callback); , System.currentTimeMillis())), callback);
} }
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) { if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) { if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, TbCallback.EMPTY); subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
} else { } else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!"); log.warn("Possible misconfiguration because subscriptionManagerService is null!");
} }

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

@ -237,6 +237,8 @@ public class DefaultTransportApiService implements TransportApiService {
device.setName(requestMsg.getDeviceName()); device.setName(requestMsg.getDeviceName());
device.setType(requestMsg.getDeviceType()); device.setType(requestMsg.getDeviceType());
device.setCustomerId(gateway.getCustomerId()); device.setCustomerId(gateway.getCustomerId());
DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
device.setDeviceProfileId(deviceProfile.getId());
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
deviceStateService.onDeviceAdded(device); deviceStateService.onDeviceAdded(device);

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

@ -227,6 +227,10 @@ public abstract class AbstractWebTest {
login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
} }
protected void loginUser(String userName, String password) throws Exception {
login(userName, password);
}
private Tenant savedDifferentTenant; private Tenant savedDifferentTenant;
protected void loginDifferentTenant() throws Exception { protected void loginDifferentTenant() throws Exception {
@ -252,15 +256,27 @@ public abstract class AbstractWebTest {
protected User createUserAndLogin(User user, String password) throws Exception { protected User createUserAndLogin(User user, String password) throws Exception {
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
logout(); logout();
JsonNode activateRequest = getActivateRequest(password);
JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
validateAndSetJwtToken(tokenInfo, user.getEmail());
return savedUser;
}
protected User createUser(User user, String password) throws Exception {
User savedUser = doPost("/api/user", user, User.class);
JsonNode activateRequest = getActivateRequest(password);
ResultActions resultActions = doPost("/api/noauth/activate", activateRequest);
resultActions.andExpect(status().isOk());
return savedUser;
}
private JsonNode getActivateRequest(String password) throws Exception {
doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
.andExpect(status().isSeeOther()) .andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
JsonNode activateRequest = new ObjectMapper().createObjectNode() return new ObjectMapper().createObjectNode()
.put("activateToken", TestMailService.currentActivateToken) .put("activateToken", TestMailService.currentActivateToken)
.put("password", password); .put("password", password);
JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
validateAndSetJwtToken(tokenInfo, user.getEmail());
return savedUser;
} }
protected void login(String username, String password) throws Exception { protected void login(String username, String password) throws Exception {
@ -443,6 +459,10 @@ public abstract class AbstractWebTest {
return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass); return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass);
} }
protected <T> T doPostClaimAsync(String urlTemplate, Object content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
}
protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception { protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass); return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass);
} }

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

@ -34,7 +34,7 @@ public class ControllerSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), 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/hsql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");

215
application/src/test/java/org/thingsboard/server/mqtt/AbstractMqttIntegrationTest.java

@ -0,0 +1,215 @@
/**
* Copyright © 2016-2020 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.mqtt;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
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.MqttMessage;
import org.junit.Assert;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest {
protected static final String MQTT_URL = "tcp://localhost:1883";
private static final AtomicInteger atomicInteger = new AtomicInteger(2);
protected Tenant savedTenant;
protected User tenantAdmin;
protected Device savedDevice;
protected String accessToken;
protected Device savedGateway;
protected String gatewayAccessToken;
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) 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");
Device device = new Device();
device.setName(deviceName);
device.setType("default");
Device gateway = new Device();
gateway.setName(gatewayName);
gateway.setType("default");
ObjectNode additionalInfo = mapper.createObjectNode();
additionalInfo.put("gateway", true);
gateway.setAdditionalInfo(additionalInfo);
if (payloadType != null) {
DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic);
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
device.setType(savedDeviceProfile.getName());
device.setDeviceProfileId(savedDeviceProfile.getId());
gateway.setType(savedDeviceProfile.getName());
gateway.setDeviceProfileId(savedDeviceProfile.getId());
}
savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
savedGateway = doPost("/api/device", gateway, Device.class);
DeviceCredentials gatewayCredentials =
doGet("/api/device/" + savedGateway.getId().getId().toString() + "/credentials", DeviceCredentials.class);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
assertEquals(savedGateway.getId(), gatewayCredentials.getDeviceId());
gatewayAccessToken = gatewayCredentials.getCredentialsId();
assertNotNull(gatewayAccessToken);
}
protected void processAfterTest() throws Exception {
loginSysAdmin();
if (savedTenant != null) {
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
}
}
protected MqttAsyncClient getMqttAsyncClient(String accessToken) throws MqttException {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
return client;
}
protected void publishMqttMsg(MqttAsyncClient client, byte[] payload, String topic) throws MqttException {
MqttMessage message = new MqttMessage();
message.setPayload(payload);
client.publish(topic, message);
}
protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "3.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "4", TransportProtos.KeyValueType.LONG_V);
TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
keyValueProtos.add(strKeyValueProto);
keyValueProtos.add(boolKeyValueProto);
keyValueProtos.add(dblKeyValueProto);
keyValueProtos.add(longKeyValueProto);
keyValueProtos.add(jsonKeyValueProto);
return keyValueProtos;
}
protected TransportProtos.KeyValueProto getKeyValueProto(String key, String strValue, TransportProtos.KeyValueType type) {
TransportProtos.KeyValueProto.Builder keyValueProtoBuilder = TransportProtos.KeyValueProto.newBuilder();
keyValueProtoBuilder.setKey(key);
keyValueProtoBuilder.setType(type);
switch (type) {
case BOOLEAN_V:
keyValueProtoBuilder.setBoolV(Boolean.parseBoolean(strValue));
break;
case LONG_V:
keyValueProtoBuilder.setLongV(Long.parseLong(strValue));
break;
case DOUBLE_V:
keyValueProtoBuilder.setDoubleV(Double.parseDouble(strValue));
break;
case STRING_V:
keyValueProtoBuilder.setStringV(strValue);
break;
case JSON_V:
keyValueProtoBuilder.setJsonV(strValue);
break;
}
return keyValueProtoBuilder.build();
}
protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(transportPayloadType.name());
deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.MQTT);
deviceProfile.setDescription(transportPayloadType.name() + " Test");
DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration();
transportConfiguration.setTransportPayloadType(transportPayloadType);
if (!StringUtils.isEmpty(telemetryTopic)) {
transportConfiguration.setDeviceTelemetryTopic(telemetryTopic);
}
if (!StringUtils.isEmpty(attributesTopic)) {
transportConfiguration.setDeviceAttributesTopic(attributesTopic);
}
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfileData.setConfiguration(configuration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setDefault(false);
deviceProfile.setDefaultRuleChainId(null);
return deviceProfile;
}
protected TransportProtos.PostAttributeMsg getPostAttributeMsg(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> kvProtos = getKvProtos(expectedKeys);
TransportProtos.PostAttributeMsg.Builder builder = TransportProtos.PostAttributeMsg.newBuilder();
builder.addAllKv(kvProtos);
return builder.build();
}
}

2
application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java

@ -33,7 +33,7 @@ public class MqttNoSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-entities-hsql.sql", "sql/system-data.sql"), Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql", "sql/hsql/drop-all-tables.sql",
"nosql-test.properties"); "nosql-test.properties");

8
application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java

@ -27,13 +27,17 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class) @RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({ @ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.mqtt.rpc.sql.*Test", "org.thingsboard.server.mqtt.rpc.sql.*Test",
"org.thingsboard.server.mqtt.telemetry.sql.*Test" "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test",
"org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
"org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
"org.thingsboard.server.mqtt.attributes.request.sql.*Test",
"org.thingsboard.server.mqtt.claim.sql.*Test"
}) })
public class MqttSqlTestSuite { public class MqttSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), 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/hsql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");

111
application/src/test/java/org/thingsboard/server/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java

@ -0,0 +1,111 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@Slf4j
public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," +
"\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
}
protected void processAfterTest() throws Exception {
super.processAfterTest();
}
protected List<TransportProtos.TsKvProto> getTsKvProtoList() {
TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V);
TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V);
List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>();
tsKvProtoList.add(tsKvProtoAttribute1);
tsKvProtoList.add(tsKvProtoAttribute2);
tsKvProtoList.add(tsKvProtoAttribute3);
tsKvProtoList.add(tsKvProtoAttribute4);
tsKvProtoList.add(tsKvProtoAttribute5);
return tsKvProtoList;
}
protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) {
TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder();
TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType);
tsKvProtoBuilder.setKv(keyValueProto);
return tsKvProtoBuilder.build();
}
protected TestMqttCallback getTestMqttCallback() {
CountDownLatch latch = new CountDownLatch(1);
return new TestMqttCallback(latch);
}
protected static class TestMqttCallback implements MqttCallback {
private final CountDownLatch latch;
private Integer qoS;
private byte[] payloadBytes;
TestMqttCallback(CountDownLatch latch) {
this.latch = latch;
}
public int getQoS() {
return qoS;
}
public byte[] getPayloadBytes() {
return payloadBytes;
}
public CountDownLatch getLatch() {
return latch;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
}

150
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java

@ -0,0 +1,150 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
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.device.profile.MqttTopics;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttAttributesRequestIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server", "Gateway Test Request attribute values from the server", null, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer();
}
protected void processTestRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
postAttributesAndSubscribeToTopic(savedDevice, client);
Thread.sleep(1000);
TestMqttCallback callback = getTestMqttCallback();
client.setCallback(callback);
validateResponse(client, callback.getLatch(), callback);
}
protected void processTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
postGatewayDeviceClientAttributes(client);
Thread.sleep(1000);
Device savedDevice = doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class);
assertNotNull(savedDevice);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
Thread.sleep(1000);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value());
TestMqttCallback clientAttributesCallback = getTestMqttCallback();
client.setCallback(clientAttributesCallback);
validateClientResponseGateway(client, clientAttributesCallback);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
client.setCallback(sharedAttributesCallback);
validateSharedResponseGateway(client, sharedAttributesCallback);
}
protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes()));
client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
}
protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes()));
}
protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
}

51
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server json", "Gateway Test Request attribute values from the server json", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer();
}
}

201
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java

@ -0,0 +1,201 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
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.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
processTestRequestAttributesValuesFromTheServer();
}
@Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
processTestGatewayRequestAttributesValuesFromTheServer();
}
protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
byte[] payload = postAttributeMsg.toByteArray();
client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(payload));
client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
}
protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
attributesMsgBuilder.setDeviceName("Gateway Device Request Attributes");
attributesMsgBuilder.setMsg(postAttributeMsg);
TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build();
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
gatewayAttributeMsgBuilder.addMsg(attributesMsg);
byte[] bytes = gatewayAttributeMsgBuilder.build().toByteArray();
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes));
}
protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
attributesRequestBuilder.setClientKeys(keys);
attributesRequestBuilder.setSharedKeys(keys);
TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(attributesRequest.toByteArray());
client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg();
TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(true);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualResponseMsg.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
}
protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(false);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
TransportProtos.GetAttributeResponseMsg expectedResponseMsg = expectedGatewayAttributeResponseMsg.getResponseMsg();
TransportProtos.GetAttributeResponseMsg actualResponseMsg = actualGatewayAttributeResponseMsg.getResponseMsg();
assertEquals(expectedResponseMsg.getRequestId(), actualResponseMsg.getRequestId());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualResponseMsg.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
}
private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String keys, boolean client) {
return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder()
.setClient(client)
.addAllKeys(Arrays.asList(keys.split(",")))
.setDeviceName("Gateway Device Request Attributes")
.setId(1).build();
}
private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() {
TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
result.addAllClientAttributeList(tsKvProtoList);
result.addAllSharedAttributeList(tsKvProtoList);
result.setRequestId(1);
return result.build();
}
private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(boolean client) {
TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
if (client) {
getAttributeResponseMsgBuilder.addAllClientAttributeList(tsKvProtoList);
} else {
getAttributeResponseMsgBuilder.addAllSharedAttributeList(tsKvProtoList);
}
getAttributeResponseMsgBuilder.setRequestId(1);
TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build();
gatewayAttributeResponseMsg.setDeviceName("Gateway Device Request Attributes");
gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg);
return gatewayAttributeResponseMsg.build();
}
protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "42.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "73", TransportProtos.KeyValueType.LONG_V);
TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
keyValueProtos.add(strKeyValueProto);
keyValueProtos.add(boolKeyValueProto);
keyValueProtos.add(dblKeyValueProto);
keyValueProtos.add(longKeyValueProto);
keyValueProtos.add(jsonKeyValueProto);
return keyValueProtos;
}
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/nosql/MqttAttributesRequestNoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesRequestNoSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestJsonSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestJsonSqlIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestProtoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestProtoSqlIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/attributes/request/sql/MqttAttributesRequestSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.request.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest;
@DaoSqlTest
public class MqttAttributesRequestSqlIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
}

170
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java

@ -0,0 +1,170 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
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.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest {
private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}";
private static String getResponseGatewayAttributesUpdatedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\"," +
"\"data\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
}
private static String getResponseGatewayAttributesDeletedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\",\"data\":{\"deleted\":[\"attribute5\"]}}";
}
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates();
}
protected void processTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(2000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateDeleteAttributesResponse(onDeleteCallback);
}
protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
}
protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
}
protected void processGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback);
Device device = new Device();
device.setName("Gateway Device Subscribe to attribute updates");
device.setType("default");
byte[] connectPayloadBytes = getConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Thread.sleep(1000);
Device savedDevice = doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class);
assertNotNull(savedDevice);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(2000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateGatewayUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateGatewayDeleteAttributesResponse(onDeleteCallback);
}
protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(getResponseGatewayAttributesUpdatedPayload(), s);
}
protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(s, getResponseGatewayAttributesDeletedPayload());
}
protected byte[] getConnectPayloadBytes() {
String connectPayload = "{\"device\": \"Gateway Device Subscribe to attribute updates\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
return connectPayload.getBytes();
}
}

51
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates;
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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates();
}
}

149
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java

@ -0,0 +1,149 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates;
import com.google.protobuf.InvalidProtocolBufferException;
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.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processTestSubscribeToAttributesUpdates();
}
@Test
public void testSubscribeToAttributesUpdatesFromTheServerGateway() throws Exception {
processGatewayTestSubscribeToAttributesUpdates();
}
protected void validateUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected void validateGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
List<TransportProtos.KeyValueProto> actualSharedUpdatedList = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedUpdatedList = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg().getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
}
protected void validateGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");
TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates");
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg actualGatewayAttributeUpdateNotificationMsg = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeUpdateNotificationMsg.getDeviceName(), actualGatewayAttributeUpdateNotificationMsg.getDeviceName());
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = expectedGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected byte[] getConnectPayloadBytes() {
TransportApiProtos.ConnectMsg connectProto = getConnectProto();
return connectProto.toByteArray();
}
private TransportApiProtos.ConnectMsg getConnectProto() {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName("Gateway Device Subscribe to attribute updates");
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/nosql/MqttAttributesUpdatesNoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesUpdatesNoSqlIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesSqlIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlJsonIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesSqlJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/attributes/updates/sql/MqttAttributesUpdatesSqlProtoIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.attributes.updates.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest;
import org.thingsboard.server.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest;
@DaoSqlTest
public class MqttAttributesUpdatesSqlProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest {
}

194
application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimDeviceTest.java

@ -0,0 +1,194 @@
/**
* Copyright © 2016-2020 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.mqtt.claim;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegrationTest {
protected static final String CUSTOMER_USER_PASSWORD = "customerUser123!";
protected User customerAdmin;
protected Customer savedCustomer;
@Before
public void beforeTest() throws Exception {
super.processBeforeTest("Test Claim device", "Test Claim gateway", null, null, null);
createCustomerAndUser();
}
protected void createCustomerAndUser() throws Exception {
Customer customer = new Customer();
customer.setTenantId(savedTenant.getId());
customer.setTitle("Test Claiming Customer");
savedCustomer = doPost("/api/customer", customer, Customer.class);
assertNotNull(savedCustomer);
assertEquals(savedTenant.getId(), savedCustomer.getTenantId());
User user = new User();
user.setAuthority(Authority.CUSTOMER_USER);
user.setTenantId(savedTenant.getId());
user.setCustomerId(savedCustomer.getId());
user.setEmail("customer@thingsboard.org");
customerAdmin = createUser(user, CUSTOMER_USER_PASSWORD);
assertNotNull(customerAdmin);
assertEquals(customerAdmin.getCustomerId(), savedCustomer.getId());
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testClaimingDevice() throws Exception {
processTestClaimingDevice(false);
}
@Test
public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestClaimingDevice(true);
}
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload", true);
}
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
byte[] payloadBytes;
byte[] failurePayloadBytes;
if (emptyPayload) {
payloadBytes = "{}".getBytes();
failurePayloadBytes = "{\"durationMs\":1}".getBytes();
} else {
payloadBytes = "{\"secretKey\":\"value\", \"durationMs\":60000}".getBytes();
failurePayloadBytes = "{\"secretKey\":\"value\", \"durationMs\":1}".getBytes();
}
validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
}
protected void validateClaimResponse(boolean emptyPayload, MqttAsyncClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception {
client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes));
Thread.sleep(2000);
loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
ClaimRequest claimRequest;
if (!emptyPayload) {
claimRequest = new ClaimRequest("value");
} else {
claimRequest = new ClaimRequest(null);
}
ClaimResponse claimResponse = doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.FAILURE);
client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(payloadBytes));
Thread.sleep(2000);
ClaimResult claimResult = doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResult.class, status().isOk());
assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS);
Device claimedDevice = claimResult.getDevice();
assertNotNull(claimedDevice);
assertNotNull(claimedDevice.getCustomerId());
assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId());
claimResponse = doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.CLAIMED);
}
protected void validateGatewayClaimResponse(String deviceName, boolean emptyPayload, MqttAsyncClient client, byte[] failurePayloadBytes, byte[] payloadBytes) throws Exception {
client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes));
Thread.sleep(2000);
Device savedDevice = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
assertNotNull(savedDevice);
loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
ClaimRequest claimRequest;
if (!emptyPayload) {
claimRequest = new ClaimRequest("value");
} else {
claimRequest = new ClaimRequest(null);
}
ClaimResponse claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.FAILURE);
client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(payloadBytes));
Thread.sleep(2000);
ClaimResult claimResult = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResult.class, status().isOk());
assertEquals(claimResult.getResponse(), ClaimResponse.SUCCESS);
Device claimedDevice = claimResult.getDevice();
assertNotNull(claimedDevice);
assertNotNull(claimedDevice.getCustomerId());
assertEquals(customerAdmin.getCustomerId(), claimedDevice.getCustomerId());
claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.CLAIMED);
}
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
byte[] failurePayloadBytes;
byte[] payloadBytes;
String failurePayload;
String payload;
if (emptyPayload) {
failurePayload = "{\"" + deviceName + "\": " + "{\"durationMs\":1}" + "}";
payload = "{\"" + deviceName + "\": " + "{}" + "}";
} else {
failurePayload = "{\"" + deviceName + "\": " + "{\"secretKey\":\"value\", \"durationMs\":1}" + "}";
payload = "{\"" + deviceName + "\": " + "{\"secretKey\":\"value\", \"durationMs\":60000}" + "}";
}
payloadBytes = payload.getBytes();
failurePayloadBytes = failurePayload.getBytes();
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
}
}

57
application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2020 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.mqtt.claim;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
@Slf4j
public abstract class AbstractMqttClaimJsonDeviceTest extends AbstractMqttClaimDeviceTest {
@Before
public void beforeTest() throws Exception {
super.processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.JSON, null, null);
createCustomerAndUser();
}
@After
public void afterTest() throws Exception {
super.afterTest();
}
@Test
public void testClaimingDevice() throws Exception {
processTestClaimingDevice(false);
}
@Test
public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestClaimingDevice(true);
}
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device Json", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload Json", true);
}
}

115
application/src/test/java/org/thingsboard/server/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java

@ -0,0 +1,115 @@
/**
* Copyright © 2016-2020 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.mqtt.claim;
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.TransportPayloadType;
import org.thingsboard.server.gen.transport.TransportApiProtos;
@Slf4j
public abstract class AbstractMqttClaimProtoDeviceTest extends AbstractMqttClaimDeviceTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.PROTOBUF, null, null);
createCustomerAndUser();
}
@After
public void afterTest() throws Exception { super.afterTest(); }
@Test
public void testClaimingDevice() throws Exception {
processTestClaimingDevice(false);
}
@Test
public void testClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestClaimingDevice(true);
}
@Test
public void testGatewayClaimingDevice() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device Proto", false);
}
@Test
public void testGatewayClaimingDeviceWithoutSecretAndDuration() throws Exception {
processTestGatewayClaimingDevice("Test claiming gateway device empty payload Proto", true);
}
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
byte[] payloadBytes;
if (emptyPayload) {
payloadBytes = getClaimDevice(0, emptyPayload).toByteArray();
} else {
payloadBytes = getClaimDevice(60000, emptyPayload).toByteArray();
}
byte[] failurePayloadBytes = getClaimDevice(1, emptyPayload).toByteArray();
validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
}
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
byte[] failurePayloadBytes;
byte[] payloadBytes;
if (emptyPayload) {
payloadBytes = getGatewayClaimMsg(deviceName, 0, emptyPayload).toByteArray();
} else {
payloadBytes = getGatewayClaimMsg(deviceName, 60000, emptyPayload).toByteArray();
}
failurePayloadBytes = getGatewayClaimMsg(deviceName, 1, emptyPayload).toByteArray();
validateGatewayClaimResponse(deviceName, emptyPayload, client, failurePayloadBytes, payloadBytes);
}
private TransportApiProtos.GatewayClaimMsg getGatewayClaimMsg(String deviceName, long duration, boolean emptyPayload) {
TransportApiProtos.GatewayClaimMsg.Builder gatewayClaimMsgBuilder = TransportApiProtos.GatewayClaimMsg.newBuilder();
TransportApiProtos.ClaimDeviceMsg.Builder claimDeviceMsgBuilder = TransportApiProtos.ClaimDeviceMsg.newBuilder();
TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
if (!emptyPayload) {
claimDeviceBuilder.setSecretKey("value");
}
if (duration > 0) {
claimDeviceBuilder.setDurationMs(duration);
}
TransportApiProtos.ClaimDevice claimDevice = claimDeviceBuilder.build();
claimDeviceMsgBuilder.setClaimRequest(claimDevice);
claimDeviceMsgBuilder.setDeviceName(deviceName);
TransportApiProtos.ClaimDeviceMsg claimDeviceMsg = claimDeviceMsgBuilder.build();
gatewayClaimMsgBuilder.addMsg(claimDeviceMsg);
return gatewayClaimMsgBuilder.build();
}
private TransportApiProtos.ClaimDevice getClaimDevice(long duration, boolean emptyPayload) {
TransportApiProtos.ClaimDevice.Builder claimDeviceBuilder = TransportApiProtos.ClaimDevice.newBuilder();
if (!emptyPayload) {
claimDeviceBuilder.setSecretKey("value");
}
if (duration > 0) {
claimDeviceBuilder.setSecretKey("value");
claimDeviceBuilder.setDurationMs(duration);
}
return claimDeviceBuilder.build();
}
}

24
application/src/test/java/org/thingsboard/server/mqtt/claim/nosql/MqttClaimDeviceNoSqlTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.claim.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
@DaoNoSqlTest
public class MqttClaimDeviceNoSqlTest extends AbstractMqttClaimDeviceTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceJsonSqlTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceJsonSqlTest extends AbstractMqttClaimJsonDeviceTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceProtoSqlTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimJsonDeviceTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimProtoDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceProtoSqlTest extends AbstractMqttClaimProtoDeviceTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/claim/sql/MqttClaimDeviceSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.claim.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.claim.AbstractMqttClaimDeviceTest;
@DaoSqlTest
public class MqttClaimDeviceSqlTest extends AbstractMqttClaimDeviceTest {
}

137
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java

@ -0,0 +1,137 @@
/**
* Copyright © 2016-2020 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.mqtt.rpc;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.InvalidProtocolBufferException;
import com.nimbusds.jose.util.StandardCharset;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
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.controller.AbstractControllerTest;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.service.security.AccessValidator;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Valerii Sosliuk
*/
@Slf4j
public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", null, null, null);
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpcDeviceOffline() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
@Test
public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
}
@Test
public void testServerMqttTwoWayRpcDeviceOffline() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
@Test
public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
@Test
public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC");
}
@Test
public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC");
}
}

236
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -16,6 +16,10 @@
package org.thingsboard.server.mqtt.rpc; package org.thingsboard.server.mqtt.rpc;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.InvalidProtocolBufferException;
import com.nimbusds.jose.util.StandardCharset;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -23,15 +27,28 @@ import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.*; import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
import java.util.Arrays; import java.util.Arrays;
@ -47,74 +64,88 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Valerii Sosliuk * @author Valerii Sosliuk
*/ */
@Slf4j @Slf4j
public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractControllerTest { public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest {
private static final String MQTT_URL = "tcp://localhost:1883"; protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
private static final Long TIME_TO_HANDLE_REQUEST = 500L;
private Tenant savedTenant; protected Long asyncContextTimeoutToUseRpcPlugin;
private User tenantAdmin;
private Long asyncContextTimeoutToUseRpcPlugin;
private static final AtomicInteger atomicInteger = new AtomicInteger(2); protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
asyncContextTimeoutToUseRpcPlugin = 10000L;
}
protected void processOneWayRpcTest() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
@Before CountDownLatch latch = new CountDownLatch(1);
public void beforeTest() throws Exception { TestMqttCallback callback = new TestMqttCallback(client, latch);
loginSysAdmin(); client.setCallback(callback);
asyncContextTimeoutToUseRpcPlugin = 10000L; client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Tenant tenant = new Tenant(); Thread.sleep(2000);
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User(); String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); String deviceId = savedDevice.getId().getId().toString();
tenantAdmin.setTenantId(savedTenant.getId()); String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org"); Assert.assertTrue(StringUtils.isEmpty(result));
tenantAdmin.setFirstName("Joe"); latch.await(3, TimeUnit.SECONDS);
tenantAdmin.setLastName("Downs"); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
createUserAndLogin(tenantAdmin, "testPassword1"); protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
} }
@After protected void processTwoWayRpcTest() throws Exception {
public void afterTest() throws Exception { MqttAsyncClient client = getMqttAsyncClient(accessToken);
loginSysAdmin(); client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, 1);
if (savedTenant != null) {
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); CountDownLatch latch = new CountDownLatch(1);
} TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
Thread.sleep(2000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"value1\":\"A\",\"value2\":\"B\"}";
latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
} }
@Test protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
public void testServerMqttOneWayRpc() throws Exception { MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
Device device = new Device();
device.setName("Test One-Way Server-Side RPC"); String payload = "{\"device\":\"" + deviceName + "\"}";
device.setType("default"); byte[] payloadBytes = payload.getBytes();
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId(); validateTwoWayRpcGateway(deviceName, client, payloadBytes);
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId); }
MqttConnectOptions options = new MqttConnectOptions(); protected void validateOneWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
options.setUserName(accessToken); publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
client.connect(options).waitForCompletion();
Thread.sleep(2000);
Device savedDevice = getDeviceByName(deviceName);
assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch); TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribe("v1/devices/me/rpc/request/+", MqttQoS.AT_MOST_ONCE.value()); client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(2000); Thread.sleep(2000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertTrue(StringUtils.isEmpty(result)); Assert.assertTrue(StringUtils.isEmpty(result));
@ -122,100 +153,49 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
@Test protected void validateTwoWayRpcGateway(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception {
public void testServerMqttOneWayRpcDeviceOffline() throws Exception { publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device device = new Device();
device.setName("Test One-Way Server-Side RPC Device Offline");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409), Thread.sleep(2000);
asyncContextTimeoutToUseRpcPlugin);
}
@Test Device savedDevice = getDeviceByName(deviceName);
public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception { assertNotNull(savedDevice);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, CountDownLatch latch = new CountDownLatch(1);
status().isNotFound()); TestMqttCallback callback = new TestMqttCallback(client, latch);
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); client.setCallback(callback);
}
@Test client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
public void testServerMqttTwoWayRpc() throws Exception {
Device device = new Device();
device.setName("Test Two-Way Server-Side RPC");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
client.subscribe("v1/devices/me/rpc/request/+", 1);
client.setCallback(new TestMqttCallback(client, new CountDownLatch(1)));
Thread.sleep(2000); Thread.sleep(2000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertEquals("{\"value1\":\"A\",\"value2\":\"B\"}", result); latch.await(3, TimeUnit.SECONDS);
} String expected = "{\"success\":true}";
assertEquals(expected, result);
@Test assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
public void testServerMqttTwoWayRpcDeviceOffline() throws Exception {
Device device = new Device();
device.setName("Test Two-Way Server-Side RPC Device Offline");
device.setType("default");
Device savedDevice = getSavedDevice(device);
DeviceCredentials deviceCredentials = getDeviceCredentials(savedDevice);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
@Test
public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception {
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}";
String nonExistentDeviceId = Uuids.timeBased().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class,
status().isNotFound());
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }
private Device getSavedDevice(Device device) throws Exception { private Device getDeviceByName(String deviceName) throws Exception {
return doPost("/api/device", device, Device.class); return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
} }
private DeviceCredentials getDeviceCredentials(Device savedDevice) throws Exception { protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
return doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
message.setPayload(DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8));
} else {
JsonNode requestMsgNode = JacksonUtil.toJsonNode(new String(mqttMessage.getPayload(), StandardCharset.UTF_8));
String deviceName = requestMsgNode.get("device").asText();
int requestId = requestMsgNode.get("data").get("id").asInt();
message.setPayload(("{\"device\": \"" + deviceName + "\", \"id\": " + requestId + ", \"data\": {\"success\": true}}").getBytes(StandardCharset.UTF_8));
}
return message;
} }
private static class TestMqttCallback implements MqttCallback { private class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client; private final MqttAsyncClient client;
private final CountDownLatch latch; private final CountDownLatch latch;
@ -237,11 +217,9 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
@Override @Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload())); log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
MqttMessage message = new MqttMessage();
String responseTopic = requestTopic.replace("request", "response"); String responseTopic = requestTopic.replace("request", "response");
message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes("UTF-8"));
qoS = mqttMessage.getQos(); qoS = mqttMessage.getQos();
client.publish(responseTopic, message); client.publish(responseTopic, processMessageArrived(requestTopic, mqttMessage));
latch.countDown(); latch.countDown();
} }

66
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java

@ -0,0 +1,66 @@
/**
* Copyright © 2016-2020 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.mqtt.rpc;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
@Slf4j
public abstract class AbstractMqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.JSON, null, null);
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
@Test
public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC Json");
}
@Test
public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Json");
}
protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
}

114
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java

@ -0,0 +1,114 @@
/**
* Copyright © 2016-2020 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.mqtt.rpc;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Slf4j
public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null);
}
@After
public void afterTest() throws Exception {
super.processAfterTest();
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
@Test
public void testGatewayServerMqttOneWayRpc() throws Exception {
processOneWayRpcTestGateway("Gateway Device OneWay RPC Proto");
}
@Test
public void testGatewayServerMqttTwoWayRpc() throws Exception {
processTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto");
}
protected void processTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateTwoWayRpcGateway(deviceName, client, payloadBytes);
}
protected void processOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
}
private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setPayload(DEVICE_RESPONSE)
.setRequestId(0)
.build();
message.setPayload(toDeviceRpcResponseMsg.toByteArray());
} else {
TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
String deviceName = msg.getDeviceName();
int requestId = msg.getRpcRequestMsg().getRequestId();
TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.newBuilder()
.setDeviceName(deviceName)
.setId(requestId)
.setData("{\"success\": true}")
.build();
message.setPayload(gatewayRpcResponseMsg.toByteArray());
}
return message;
}
}

4
application/src/test/java/org/thingsboard/server/mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java

@ -16,11 +16,11 @@
package org.thingsboard.server.mqtt.rpc.nosql; package org.thingsboard.server.mqtt.rpc.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest;
/** /**
* Created by Valerii Sosliuk on 8/22/2017. * Created by Valerii Sosliuk on 8/22/2017.
*/ */
@DaoNoSqlTest @DaoNoSqlTest
public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { public class MqttServerSideRpcNoSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest {
} }

23
application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcJsonSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest;
@DaoSqlTest
public class MqttServerSideRpcJsonSqlIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcProtoSqlIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcProtoIntegrationTest;
@DaoSqlTest
public class MqttServerSideRpcProtoSqlIntegrationTest extends AbstractMqttServerSideRpcProtoIntegrationTest {
}

4
application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java

@ -16,11 +16,11 @@
package org.thingsboard.server.mqtt.rpc.sql; package org.thingsboard.server.mqtt.rpc.sql;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest;
/** /**
* Created by Valerii Sosliuk on 8/22/2017. * Created by Valerii Sosliuk on 8/22/2017.
*/ */
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcSqlIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { public class MqttServerSideRpcSqlIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest {
} }

163
application/src/test/java/org/thingsboard/server/mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java

@ -1,163 +0,0 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import java.net.URI;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Valerii Sosliuk
*/
@Slf4j
public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractControllerTest {
private static final String MQTT_URL = "tcp://localhost:1883";
private Device savedDevice;
private String accessToken;
@Before
public void beforeTest() throws Exception {
loginTenantAdmin();
Device device = new Device();
device.setName("Test device");
device.setType("default");
savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
}
@Test
public void testPushMqttRpcData() throws Exception {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options);
Thread.sleep(3000);
MqttMessage message = new MqttMessage();
message.setPayload("{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4}".getBytes());
client.publish("v1/devices/me/telemetry", message);
String deviceId = savedDevice.getId().getId().toString();
Thread.sleep(2000);
List<String> actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", List.class);
Set<String> actualKeySet = new HashSet<>(actualKeys);
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4");
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, actualKeySet);
String getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet);
Map<String, List<Map<String, String>>> values = doGetAsync(getTelemetryValuesUrl, Map.class);
assertEquals("value1", values.get("key1").get(0).get("value"));
assertEquals("true", values.get("key2").get(0).get("value"));
assertEquals("3.0", values.get("key3").get(0).get("value"));
assertEquals("4", values.get("key4").get(0).get("value"));
}
// @Test - Unstable
public void testMqttQoSLevel() throws Exception {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
client.connect(options).waitForCompletion(5000);
client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
String payload = "{\"key\":\"uniqueValue\"}";
// TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.
// MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)
// MqttClient <- SUB_ACK <- Transport
Thread.sleep(5000);
doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());
latch.await(10, TimeUnit.SECONDS);
assertEquals(payload, callback.getPayload());
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
private static class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private volatile Integer qoS;
private volatile String payload;
String getPayload() {
return payload;
}
TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
log.error("Client connection lost", throwable);
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
payload = new String(mqttMessage.getPayload());
qoS = mqttMessage.getQos();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
}

175
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java

@ -0,0 +1,175 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes;
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.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", null, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testPushMqttAttributes() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
}
@Test
public void testPushMqttAttributesGateway() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
String deviceName2 = "Device B";
String payload = getGatewayAttributesJsonPayload(deviceName1, deviceName2);
processGatewayAttributesTest(expectedKeys, payload.getBytes(), deviceName1, deviceName2);
}
protected void processAttributesTest(String topic, List<String> expectedKeys, byte[] payload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
publishMqttMsg(client, payload, topic);
DeviceId deviceId = savedDevice.getId();
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 2000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", List.class);
if (actualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, actualKeySet);
String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet);
List<Map<String, Object>> values = doGetAsync(getAttributesValuesUrl, List.class);
assertAttributesValues(values, expectedKeySet);
String deleteAttributesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
doDelete(deleteAttributesUrl);
}
protected void processGatewayAttributesTest(List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
publishMqttMsg(client, payload, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC);
Thread.sleep(2000);
Device firstDevice = doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class);
assertNotNull(firstDevice);
Device secondDevice = doGet("/api/tenant/devices?deviceName=" + secondDeviceName, Device.class);
assertNotNull(secondDevice);
List<String> firstDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/attributes/CLIENT_SCOPE", List.class);
Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys);
List<String> secondDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/attributes/CLIENT_SCOPE", List.class);
Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, firstDeviceActualKeySet);
assertEquals(expectedKeySet, secondDeviceActualKeySet);
String getAttributesValuesUrlFirstDevice = getAttributesValuesUrl(firstDevice.getId(), firstDeviceActualKeySet);
String getAttributesValuesUrlSecondDevice = getAttributesValuesUrl(firstDevice.getId(), secondDeviceActualKeySet);
List<Map<String, Object>> firstDeviceValues = doGetAsync(getAttributesValuesUrlFirstDevice, List.class);
List<Map<String, Object>> secondDeviceValues = doGetAsync(getAttributesValuesUrlSecondDevice, List.class);
assertAttributesValues(firstDeviceValues, expectedKeySet);
assertAttributesValues(secondDeviceValues, expectedKeySet);
}
protected void assertAttributesValues(List<Map<String, Object>> deviceValues, Set<String> expectedKeySet) {
for (Map<String, Object> map : deviceValues) {
String key = (String) map.get("key");
Object value = map.get("value");
assertTrue(expectedKeySet.contains(key));
switch (key) {
case "key1":
assertEquals("value1", value);
break;
case "key2":
assertEquals(true, value);
break;
case "key3":
assertEquals(3.0, value);
break;
case "key4":
assertEquals(4, value);
break;
case "key5":
assertNotNull(value);
assertEquals(3, ((LinkedHashMap) value).size());
assertEquals(42, ((LinkedHashMap) value).get("someNumber"));
assertEquals(Arrays.asList(1, 2, 3), ((LinkedHashMap) value).get("someArray"));
LinkedHashMap<String, String> someNestedObject = (LinkedHashMap) ((LinkedHashMap) value).get("someNestedObject");
assertEquals("value", someNestedObject.get("key"));
break;
}
}
}
protected String getGatewayAttributesJsonPayload(String deviceA, String deviceB) {
return "{\"" + deviceA + "\": " + PAYLOAD_VALUES_STR + ", \"" + deviceB + "\": " + PAYLOAD_VALUES_STR + "}";
}
private String getAttributesValuesUrl(DeviceId deviceId, Set<String> actualKeySet) {
return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/attributes/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet);
}
}

56
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes;
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 java.util.Arrays;
import java.util.List;
@Slf4j
public abstract class AbstractMqttAttributesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest {
private static final String POST_DATA_ATTRIBUTES_TOPIC = "data/attributes";
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.JSON, null, POST_DATA_ATTRIBUTES_TOPIC);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testPushMqttAttributes() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
}
@Test
public void testPushMqttAttributesGateway() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
String deviceName2 = "Device B";
String payload = getGatewayAttributesJsonPayload(deviceName1, deviceName2);
processGatewayAttributesTest(expectedKeys, payload.getBytes(), deviceName1, deviceName2);
}
}

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

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes;
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.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Slf4j
public abstract class AbstractMqttAttributesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest {
private static final String POST_DATA_ATTRIBUTES_TOPIC = "proto/attributes";
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testPushMqttAttributes() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys);
processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, expectedKeys, msg.toByteArray());
}
@Test
public void testPushMqttAttributesGateway() throws Exception {
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
String deviceName2 = "Device B";
TransportApiProtos.AttributesMsg firstDeviceAttributesMsgProto = getDeviceAttributesMsgProto(deviceName1, expectedKeys);
TransportApiProtos.AttributesMsg secondDeviceAttributesMsgProto = getDeviceAttributesMsgProto(deviceName2, expectedKeys);
gatewayAttributesMsgProtoBuilder.addAllMsg(Arrays.asList(firstDeviceAttributesMsgProto, secondDeviceAttributesMsgProto));
TransportApiProtos.GatewayAttributesMsg gatewayAttributesMsg = gatewayAttributesMsgProtoBuilder.build();
processGatewayAttributesTest(expectedKeys, gatewayAttributesMsg.toByteArray(), deviceName1, deviceName2);
}
private TransportApiProtos.AttributesMsg getDeviceAttributesMsgProto(String deviceName, List<String> expectedKeys) {
TransportApiProtos.AttributesMsg.Builder deviceAttributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
TransportProtos.PostAttributeMsg msg = getPostAttributeMsg(expectedKeys);
deviceAttributesMsgBuilder.setDeviceName(deviceName);
deviceAttributesMsgBuilder.setMsg(msg);
return deviceAttributesMsgBuilder.build();
}
}

23
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesNoSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlJsonIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesNoSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/nosql/MqttAttributesNoSqlProtoIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest;
@DaoNoSqlTest
public class MqttAttributesNoSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest;
@DaoSqlTest
public class MqttAttributesSqlIntegrationTest extends AbstractMqttAttributesIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlJsonIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
@DaoSqlTest
public class MqttAttributesSqlJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest {
}

24
application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/sql/MqttAttributesSqlProtoIntegrationTest.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.attributes.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest;
import org.thingsboard.server.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest;
@DaoSqlTest
public class MqttAttributesSqlProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest {
}

290
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java

@ -0,0 +1,290 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage;
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.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqttIntegrationTest {
protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", null, null, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testPushMqttTelemetry() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
}
@Test
public void testPushMqttTelemetryWithTs() throws Exception {
String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}";
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true);
}
@Test
public void testPushMqttTelemetryGateway() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
String deviceName2 = "Device B";
String payload = getGatewayTelemetryJsonPayload(deviceName1, deviceName2, "10000", "20000");
processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2);
}
@Test
public void testGatewayConnect() throws Exception {
String payload = "{\"device\":\"Device A\"}";
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
publishMqttMsg(client, payload.getBytes(), MqttTopics.GATEWAY_CONNECT_TOPIC);
Thread.sleep(2000);
String deviceName = "Device A";
Device device = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
assertNotNull(device);
}
protected void processTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, boolean withTs) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
publishMqttMsg(client, payload, topic);
String deviceId = savedDevice.getId().getId().toString();
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 2000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", List.class);
if (actualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, actualKeySet);
String getTelemetryValuesUrl;
if (withTs) {
getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=15000&keys=" + String.join(",", actualKeySet);
} else {
getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet);
}
Map<String, List<Map<String, String>>> values = doGetAsync(getTelemetryValuesUrl, Map.class);
if (withTs) {
assertTs(values, expectedKeys, 10000, 0);
}
assertValues(values, 0);
}
protected void processGatewayTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
publishMqttMsg(client, payload, topic);
Thread.sleep(2000);
Device firstDevice = doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class);
assertNotNull(firstDevice);
Device secondDevice = doGet("/api/tenant/devices?deviceName=" + secondDeviceName, Device.class);
assertNotNull(secondDevice);
List<String> firstDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/timeseries", List.class);
Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys);
List<String> secondDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/timeseries", List.class);
Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys);
assertEquals(expectedKeySet, firstDeviceActualKeySet);
assertEquals(expectedKeySet, secondDeviceActualKeySet);
String getTelemetryValuesUrlFirstDevice = getTelemetryValuesUrl(firstDevice.getId(), firstDeviceActualKeySet);
String getTelemetryValuesUrlSecondDevice = getTelemetryValuesUrl(firstDevice.getId(), secondDeviceActualKeySet);
Map<String, List<Map<String, String>>> firstDeviceValues = doGetAsync(getTelemetryValuesUrlFirstDevice, Map.class);
Map<String, List<Map<String, String>>> secondDeviceValues = doGetAsync(getTelemetryValuesUrlSecondDevice, Map.class);
assertGatewayDeviceData(firstDeviceValues, expectedKeys);
assertGatewayDeviceData(secondDeviceValues, expectedKeys);
}
protected String getGatewayTelemetryJsonPayload(String deviceA, String deviceB, String firstTsValue, String secondTsValue) {
String payload = "[{\"ts\": " + firstTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}, " +
"{\"ts\": " + secondTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}]";
return "{\"" + deviceA + "\": " + payload + ", \"" + deviceB + "\": " + payload + "}";
}
private String getTelemetryValuesUrl(DeviceId deviceId, Set<String> actualKeySet) {
return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=25000&keys=" + String.join(",", actualKeySet);
}
private void assertGatewayDeviceData(Map<String, List<Map<String, String>>> deviceValues, List<String> expectedKeys) {
assertEquals(2, deviceValues.get(expectedKeys.get(0)).size());
assertEquals(2, deviceValues.get(expectedKeys.get(1)).size());
assertEquals(2, deviceValues.get(expectedKeys.get(2)).size());
assertEquals(2, deviceValues.get(expectedKeys.get(3)).size());
assertEquals(2, deviceValues.get(expectedKeys.get(4)).size());
assertTs(deviceValues, expectedKeys, 20000, 0);
assertTs(deviceValues, expectedKeys, 10000, 1);
assertValues(deviceValues, 0);
assertValues(deviceValues, 1);
}
private void assertValues(Map<String, List<Map<String, String>>> deviceValues, int arrayIndex) {
for (Map.Entry<String, List<Map<String, String>>> entry : deviceValues.entrySet()) {
String key = entry.getKey();
List<Map<String, String>> tsKv = entry.getValue();
String value = tsKv.get(arrayIndex).get("value");
switch (key) {
case "key1":
assertEquals("value1", value);
break;
case "key2":
assertEquals("true", value);
break;
case "key3":
assertEquals("3.0", value);
break;
case "key4":
assertEquals("4", value);
break;
case "key5":
assertEquals("{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", value);
break;
}
}
}
private void assertTs(Map<String, List<Map<String, String>>> deviceValues, List<String> expectedKeys, int ts, int arrayIndex) {
assertEquals(ts, deviceValues.get(expectedKeys.get(0)).get(arrayIndex).get("ts"));
assertEquals(ts, deviceValues.get(expectedKeys.get(1)).get(arrayIndex).get("ts"));
assertEquals(ts, deviceValues.get(expectedKeys.get(2)).get(arrayIndex).get("ts"));
assertEquals(ts, deviceValues.get(expectedKeys.get(3)).get(arrayIndex).get("ts"));
assertEquals(ts, deviceValues.get(expectedKeys.get(4)).get(arrayIndex).get("ts"));
}
// @Test - Unstable
public void testMqttQoSLevel() throws Exception {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId);
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
client.connect(options).waitForCompletion(5000);
client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
String payload = "{\"key\":\"uniqueValue\"}";
// TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.
// MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)
// MqttClient <- SUB_ACK <- Transport
Thread.sleep(5000);
doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());
latch.await(10, TimeUnit.SECONDS);
assertEquals(payload, callback.getPayload());
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}
private static class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private volatile Integer qoS;
private volatile String payload;
String getPayload() {
return payload;
}
TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
log.error("Client connection lost", throwable);
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
payload = new String(mqttMessage.getPayload());
qoS = mqttMessage.getQos();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
}

82
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java

@ -0,0 +1,82 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries;
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.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Slf4j
public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
private static final String POST_DATA_TELEMETRY_TOPIC = "data/telemetry";
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testPushMqttTelemetry() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
}
@Test
public void testPushMqttTelemetryWithTs() throws Exception {
String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}";
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true);
}
@Test
public void testPushMqttTelemetryGateway() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
String deviceName2 = "Device B";
String payload = getGatewayTelemetryJsonPayload(deviceName1, deviceName2, "10000", "20000");
processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2);
}
@Test
public void testGatewayConnect() throws Exception {
String payload = "{\"device\":\"Device A\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
publishMqttMsg(client, payload.getBytes(), MqttTopics.GATEWAY_CONNECT_TOPIC);
Thread.sleep(2000);
String deviceName = "Device A";
Device device = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
assertNotNull(device);
}
}

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

@ -0,0 +1,115 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries;
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.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@Slf4j
public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry";
@Before
public void beforeTest() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null);
}
@After
public void afterTest() throws Exception {
processAfterTest();
}
@Test
public void testPushMqttTelemetry() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 0);
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), false);
}
@Test
public void testPushMqttTelemetryWithTs() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
TransportProtos.TsKvListProto tsKvListProto = getTsKvListProto(expectedKeys, 10000);
processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, tsKvListProto.toByteArray(), true);
}
@Test
public void testPushMqttTelemetryGateway() throws Exception {
TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
String deviceName2 = "Device B";
TransportApiProtos.TelemetryMsg deviceATelemetryMsgProto = getDeviceTelemetryMsgProto(deviceName1, expectedKeys, 10000, 20000);
TransportApiProtos.TelemetryMsg deviceBTelemetryMsgProto = getDeviceTelemetryMsgProto(deviceName2, expectedKeys, 10000, 20000);
gatewayTelemetryMsgProtoBuilder.addAllMsg(Arrays.asList(deviceATelemetryMsgProto, deviceBTelemetryMsgProto));
TransportApiProtos.GatewayTelemetryMsg gatewayTelemetryMsg = gatewayTelemetryMsgProtoBuilder.build();
processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, gatewayTelemetryMsg.toByteArray(), deviceName1, deviceName2);
}
@Test
public void testGatewayConnect() throws Exception {
String deviceName = "Device A";
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
publishMqttMsg(client, connectMsgProto.toByteArray(), MqttTopics.GATEWAY_CONNECT_TOPIC);
Thread.sleep(2000);
Device device = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
assertNotNull(device);
}
private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setDeviceType(TransportPayloadType.PROTOBUF.name());
return builder.build();
}
private TransportApiProtos.TelemetryMsg getDeviceTelemetryMsgProto(String deviceName, List<String> expectedKeys, long firstTs, long secondTs) {
TransportApiProtos.TelemetryMsg.Builder deviceTelemetryMsgBuilder = TransportApiProtos.TelemetryMsg.newBuilder();
TransportProtos.TsKvListProto tsKvListProto1 = getTsKvListProto(expectedKeys, firstTs);
TransportProtos.TsKvListProto tsKvListProto2 = getTsKvListProto(expectedKeys, secondTs);
TransportProtos.PostTelemetryMsg.Builder msg = TransportProtos.PostTelemetryMsg.newBuilder();
msg.addAllTsKvList(Arrays.asList(tsKvListProto1, tsKvListProto2));
deviceTelemetryMsgBuilder.setDeviceName(deviceName);
deviceTelemetryMsgBuilder.setMsg(msg);
return deviceTelemetryMsgBuilder.build();
}
private TransportProtos.TsKvListProto getTsKvListProto(List<String> expectedKeys, long ts) {
List<TransportProtos.KeyValueProto> kvProtos = getKvProtos(expectedKeys);
TransportProtos.TsKvListProto.Builder builder = TransportProtos.TsKvListProto.newBuilder();
builder.addAllKv(kvProtos);
builder.setTs(ts);
return builder.build();
}
}

6
application/src/test/java/org/thingsboard/server/mqtt/telemetry/nosql/MqttTelemetryNoSqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.mqtt.telemetry.nosql; package org.thingsboard.server.mqtt.telemetry.timeseries.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest;
/** /**
* Created by Valerii Sosliuk on 8/22/2017. * Created by Valerii Sosliuk on 8/22/2017.
*/ */
@DaoNoSqlTest @DaoNoSqlTest
public class MqttTelemetryNoSqlIntegrationTest extends AbstractMqttTelemetryIntegrationTest { public class MqttTimeseriesNoSqlIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
} }

23
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlJsonIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest;
@DaoNoSqlTest
public class MqttTimeseriesNoSqlJsonIntegrationTest extends AbstractMqttTimeseriesJsonIntegrationTest {
}

23
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlProtoIntegrationTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest;
@DaoNoSqlTest
public class MqttTimeseriesNoSqlProtoIntegrationTest extends AbstractMqttTimeseriesProtoIntegrationTest {
}

7
application/src/test/java/org/thingsboard/server/mqtt/telemetry/sql/MqttTelemetrySqlIntegrationTest.java → application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java

@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.mqtt.telemetry.sql; package org.thingsboard.server.mqtt.telemetry.timeseries.sql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.telemetry.AbstractMqttTelemetryIntegrationTest; import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest;
/** /**
* Created by Valerii Sosliuk on 8/22/2017. * Created by Valerii Sosliuk on 8/22/2017.
*/ */
@DaoSqlTest @DaoSqlTest
public class MqttTelemetrySqlIntegrationTest extends AbstractMqttTelemetryIntegrationTest { public class MqttTimeseriesSqlIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
} }

27
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest;
import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest;
/**
* Created by Valerii Sosliuk on 8/22/2017.
*/
@DaoSqlTest
public class MqttTimeseriesSqlJsonIntegrationTest extends AbstractMqttTimeseriesJsonIntegrationTest {
}

27
application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2020 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.mqtt.telemetry.timeseries.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest;
import org.thingsboard.server.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest;
/**
* Created by Valerii Sosliuk on 8/22/2017.
*/
@DaoSqlTest
public class MqttTimeseriesSqlProtoIntegrationTest extends AbstractMqttTimeseriesProtoIntegrationTest {
}

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

@ -32,7 +32,7 @@ public class RuleEngineSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), 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/hsql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");

1
application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java

@ -16,7 +16,6 @@
package org.thingsboard.server.rules.flow.sql; package org.thingsboard.server.rules.flow.sql;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest;
import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest;
/** /**

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

@ -33,7 +33,7 @@ public class SystemSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), 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/hsql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");

21
common/data/src/main/java/org/thingsboard/server/common/data/TransportPayloadType.java

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2020 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;
public enum TransportPayloadType {
JSON,
PROTOBUF
}

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java

@ -16,11 +16,14 @@
package org.thingsboard.server.common.data.device.profile; package org.thingsboard.server.common.data.device.profile;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
@Data @Data
public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
private TransportPayloadType transportPayloadType = TransportPayloadType.JSON;
private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC; private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC;
private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC;

71
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java

@ -20,32 +20,57 @@ package org.thingsboard.server.common.data.device.profile;
*/ */
public class MqttTopics { public class MqttTopics {
private static final String REQUEST = "/request";
private static final String RESPONSE = "/response";
private static final String RPC = "/rpc";
private static final String CONNECT = "/connect";
private static final String DISCONNECT = "/disconnect";
private static final String TELEMETRY = "/telemetry";
private static final String ATTRIBUTES = "/attributes";
private static final String CLAIM = "/claim";
private static final String SUB_TOPIC = "+";
private static final String PROVISION = "/provision";
private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
private static final String DEVICE_RPC_RESPONSE = RPC + RESPONSE;
private static final String DEVICE_RPC_REQUEST = RPC + REQUEST;
private static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/";
private static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/";
private static final String PROVISION_RESPONSE = PROVISION + RESPONSE;
// V1_JSON topics
public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String DEVICE_RPC_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/rpc/response/";
public static final String DEVICE_RPC_RESPONSE_SUB_TOPIC = DEVICE_RPC_RESPONSE_TOPIC + "+";
public static final String DEVICE_RPC_REQUESTS_TOPIC = BASE_DEVICE_API_TOPIC + "/rpc/request/";
public static final String DEVICE_RPC_REQUESTS_SUB_TOPIC = DEVICE_RPC_REQUESTS_TOPIC + "+";
public static final String DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC + "/attributes/response/";
public static final String DEVICE_ATTRIBUTES_RESPONSES_TOPIC = DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + "+";
public static final String DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC + "/attributes/request/";
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + "/telemetry";
public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + "/claim";
public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + "/attributes";
public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + "/provision";
public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + "/provision/response";
public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway"; public static final String DEVICE_RPC_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + DEVICE_RPC_RESPONSE;
public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/connect"; public static final String DEVICE_RPC_RESPONSE_SUB_TOPIC = DEVICE_RPC_RESPONSE_TOPIC + SUB_TOPIC;
public static final String GATEWAY_DISCONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + "/disconnect"; public static final String DEVICE_RPC_REQUESTS_TOPIC = BASE_DEVICE_API_TOPIC + DEVICE_RPC_REQUEST;
public static final String GATEWAY_ATTRIBUTES_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes"; public static final String DEVICE_RPC_REQUESTS_SUB_TOPIC = DEVICE_RPC_REQUESTS_TOPIC + SUB_TOPIC;
public static final String GATEWAY_TELEMETRY_TOPIC = BASE_GATEWAY_API_TOPIC + "/telemetry"; public static final String DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC + DEVICE_ATTRIBUTES_RESPONSE;
public static final String GATEWAY_CLAIM_TOPIC = BASE_GATEWAY_API_TOPIC + "/claim"; public static final String DEVICE_ATTRIBUTES_RESPONSES_TOPIC = DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + SUB_TOPIC;
public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + "/rpc"; public static final String DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC + DEVICE_ATTRIBUTES_REQUEST;
public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/request"; public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRY;
public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/attributes/response"; public static final String DEVICE_CLAIM_TOPIC = BASE_DEVICE_API_TOPIC + CLAIM;
public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision"; public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES;
public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + "/provision/response"; public static final String DEVICE_PROVISION_REQUEST_TOPIC = BASE_DEVICE_API_TOPIC + PROVISION;
public static final String DEVICE_PROVISION_RESPONSE_TOPIC = BASE_DEVICE_API_TOPIC + PROVISION_RESPONSE;
// V1_JSON gateway topics
public static final String BASE_GATEWAY_API_TOPIC = "v1/gateway";
public static final String GATEWAY_CONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + CONNECT;
public static final String GATEWAY_DISCONNECT_TOPIC = BASE_GATEWAY_API_TOPIC + DISCONNECT;
public static final String GATEWAY_ATTRIBUTES_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES;
public static final String GATEWAY_TELEMETRY_TOPIC = BASE_GATEWAY_API_TOPIC + TELEMETRY;
public static final String GATEWAY_CLAIM_TOPIC = BASE_GATEWAY_API_TOPIC + CLAIM;
public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + RPC;
public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST;
public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE;
public static final String GATEWAY_PROVISION_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION;
public static final String GATEWAY_PROVISION_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + PROVISION_RESPONSE;
private MqttTopics() { private MqttTopics() {
} }

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

@ -20,6 +20,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -31,6 +32,8 @@ import java.util.concurrent.ConcurrentHashMap;
@NoArgsConstructor @NoArgsConstructor
public final class TbMsgMetaData implements Serializable { public final class TbMsgMetaData implements Serializable {
public static final TbMsgMetaData EMPTY = new TbMsgMetaData(Collections.emptyMap());
private final Map<String, String> data = new ConcurrentHashMap<>(); private final Map<String, String> data = new ConcurrentHashMap<>();
public TbMsgMetaData(Map<String, String> data) { public TbMsgMetaData(Map<String, String> data) {

9
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java

@ -24,7 +24,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor;
/** /**
* Created by ashvayka on 04.10.18. * Created by ashvayka on 04.10.18.
@ -40,7 +41,11 @@ public class MqttTransportContext extends TransportContext {
@Getter @Getter
@Autowired @Autowired
private MqttTransportAdaptor adaptor; private JsonMqttAdaptor jsonMqttAdaptor;
@Getter
@Autowired
private ProtoMqttAdaptor protoMqttAdaptor;
@Getter @Getter
@Value("${transport.mqtt.netty.max_payload_size}") @Value("${transport.mqtt.netty.max_payload_size}")

55
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.mqtt; package org.thingsboard.server.transport.mqtt;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonSyntaxException;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.mqtt.MqttConnAckMessage; import io.netty.handler.codec.mqtt.MqttConnAckMessage;
@ -34,7 +35,6 @@ import io.netty.handler.codec.mqtt.MqttSubscribeMessage;
import io.netty.handler.codec.mqtt.MqttTopicSubscription; import io.netty.handler.codec.mqtt.MqttTopicSubscription;
import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.GenericFutureListener;
@ -53,7 +53,6 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
@ -95,7 +94,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private final UUID sessionId; private final UUID sessionId;
private final MqttTransportContext context; private final MqttTransportContext context;
private final MqttTransportAdaptor adaptor;
private final TransportService transportService; private final TransportService transportService;
private final SslHandler sslHandler; private final SslHandler sslHandler;
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap; private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
@ -108,10 +106,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
this.sessionId = UUID.randomUUID(); this.sessionId = UUID.randomUUID();
this.context = context; this.context = context;
this.transportService = context.getTransportService(); this.transportService = context.getTransportService();
this.adaptor = context.getAdaptor();
this.sslHandler = sslHandler; this.sslHandler = sslHandler;
this.mqttQoSMap = new ConcurrentHashMap<>(); this.mqttQoSMap = new ConcurrentHashMap<>();
this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap); this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context);
} }
@Override @Override
@ -153,9 +150,19 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
int msgId = mqttMsg.variableHeader().packetId(); int msgId = mqttMsg.variableHeader().packetId();
try { try {
if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) { if (topicName.equals(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC)) {
TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = adaptor.convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg); try {
transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg)); TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
} catch (Exception e) {
if (e.getCause().toString().contains("JsonSyntaxException")) {
TransportProtos.ProvisionDeviceRequestMsg provisionRequestMsg = deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToProvisionRequestMsg(deviceSessionCtx, mqttMsg);
transportService.process(provisionRequestMsg, new DeviceProvisionCallback(ctx, msgId, provisionRequestMsg));
log.trace("[{}][{}] Processing provision publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId);
} else {
throw e;
}
}
} else { } else {
throw new RuntimeException("Unsupported topic for provisioning requests!"); throw new RuntimeException("Unsupported topic for provisioning requests!");
} }
@ -250,23 +257,24 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
try { try {
MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { } else if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg));
} else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
} else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) {
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = adaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
} else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) {
TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = adaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg));
} else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) {
TransportProtos.ClaimDeviceMsg claimDeviceMsg = adaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg));
} else { } else {
transportService.reportActivity(deviceSessionCtx.getSessionInfo()); transportService.reportActivity(deviceSessionCtx.getSessionInfo());
@ -279,7 +287,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) {
return new TransportServiceCallback<Void>() { return new TransportServiceCallback<Void>() {
@Override @Override
@ -316,7 +323,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ctx.writeAndFlush(createMqttPubAckMsg(msgId)); ctx.writeAndFlush(createMqttPubAckMsg(msgId));
} }
try { try {
adaptor.convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
} }
@ -354,10 +361,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
break; break;
} }
case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC: case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_RPC_TOPIC:
case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC: case MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC:
case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
registerSubQoS(topic, grantedQoSList, reqQoS); registerSubQoS(topic, grantedQoSList, reqQoS);
@ -565,7 +572,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
if (infoNode != null) { if (infoNode != null) {
JsonNode gatewayNode = infoNode.get("gateway"); JsonNode gatewayNode = infoNode.get("gateway");
if (gatewayNode != null && gatewayNode.asBoolean()) { if (gatewayNode != null && gatewayNode.asBoolean()) {
gatewaySessionHandler = new GatewaySessionHandler(context, deviceSessionCtx, sessionId); gatewaySessionHandler = new GatewaySessionHandler(deviceSessionCtx, sessionId);
} }
} }
} catch (IOException e) { } catch (IOException e) {
@ -619,7 +626,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
@Override @Override
public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
try { try {
adaptor.convertToPublish(deviceSessionCtx, response).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, response).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
} }
@ -628,7 +635,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
@Override @Override
public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) { public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) {
try { try {
adaptor.convertToPublish(deviceSessionCtx, notification).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, notification).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device attributes update to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes update to MQTT msg", sessionId, e);
} }
@ -644,17 +651,17 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to device", sessionId);
try { try {
adaptor.convertToPublish(deviceSessionCtx, rpcRequest).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, rpcRequest).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device RPC commandto MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
} }
} }
@Override @Override
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg rpcResponse) { public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg rpcResponse) {
log.trace("[{}] Received RPC command to device", sessionId); log.trace("[{}] Received RPC command to server", sessionId);
try { try {
adaptor.convertToPublish(deviceSessionCtx, rpcResponse).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, rpcResponse).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e);
} }

192
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
@ -47,12 +48,13 @@ import java.util.UUID;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
@Component("JsonMqttAdaptor") @Component
@Slf4j @Slf4j
public class JsonMqttAdaptor implements MqttTransportAdaptor { public class JsonMqttAdaptor implements MqttTransportAdaptor {
protected static final Charset UTF8 = StandardCharsets.UTF_8;
private static final Gson GSON = new Gson(); private static final Gson GSON = new Gson();
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
@Override @Override
@ -76,106 +78,68 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
} }
@Override @Override
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { public TransportProtos.ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
String topicName = inbound.variableHeader().topicName(); String payload = validatePayload(ctx.getSessionId(), inbound.payload(), true);
try {
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
result.setRequestId(Integer.valueOf(topicName.substring(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX.length())));
String payload = inbound.payload().toString(UTF8);
JsonElement requestBody = new JsonParser().parse(payload);
Set<String> clientKeys = toStringSet(requestBody, "clientKeys");
Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys");
if (clientKeys != null) {
result.addAllClientAttributeNames(clientKeys);
}
if (sharedKeys != null) {
result.addAllSharedAttributeNames(sharedKeys);
}
return result.build();
} catch (RuntimeException e) {
log.warn("Failed to decode get attributes request", e);
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
String topicName = inbound.variableHeader().topicName();
try { try {
Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC.length())); return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
String payload = inbound.payload().toString(UTF8); } catch (IllegalStateException | JsonSyntaxException ex) {
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); throw new AdaptorException(ex);
} catch (RuntimeException e) {
log.warn("Failed to decode get attributes request", e);
throw new AdaptorException(e);
} }
} }
@Override @Override
public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
String topicName = inbound.variableHeader().topicName();
String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
try { try {
Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC.length())); return JsonConverter.convertToProvisionRequestMsg(payload);
return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@Override @Override
public TransportProtos.ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
String payload = validatePayload(ctx.getSessionId(), inbound.payload(), true); return processGetAttributeRequestMsg(inbound, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
try { }
return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
} catch (IllegalStateException | JsonSyntaxException ex) { @Override
throw new AdaptorException(ex); public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
} return processToDeviceRpcResponseMsg(inbound, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC);
}
@Override
public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
return processToServerRpcRequestMsg(ctx, inbound, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC);
} }
@Override @Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
if (!StringUtils.isEmpty(responseMsg.getError())) { return processConvertFromAttributeResponseMsg(ctx, responseMsg, MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX);
throw new AdaptorException(responseMsg.getError());
} else {
Integer requestId = responseMsg.getRequestId();
if (requestId >= 0) {
return Optional.of(createMqttPublishMsg(ctx,
MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + requestId,
JsonConverter.toJson(responseMsg)));
}
return Optional.empty();
}
} }
@Override @Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
if (!StringUtils.isEmpty(responseMsg.getError())) { return processConvertFromGatewayAttributeResponseMsg(ctx, deviceName, responseMsg, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
throw new AdaptorException(responseMsg.getError());
} else {
JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg);
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result));
}
} }
@Override @Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException { public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, JsonConverter.toJson(notificationMsg))); return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, JsonConverter.toJson(notificationMsg)));
} }
@Override @Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException { public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg); JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg);
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result)); return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result));
} }
@Override @Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException { public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false))); return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false)));
} }
@Override @Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException { public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest))); return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest)));
} }
@ -190,23 +154,91 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
} }
@Override @Override
public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseMsg) {
return Optional.of(createMqttPublishMsg(ctx,
MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC,
JsonConverter.toGatewayJson(deviceName, responseMsg)));
}
public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
String payload = validatePayload(sessionId, payloadData, false);
try {
return new JsonParser().parse(payload);
} catch (JsonSyntaxException ex) {
log.warn("Payload is in incorrect format: {}", payload);
throw new AdaptorException(ex);
}
}
protected TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(MqttPublishMessage inbound, String topic) throws AdaptorException {
String topicName = inbound.variableHeader().topicName();
try {
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
result.setRequestId(getRequestId(topicName, topic));
String payload = inbound.payload().toString(UTF8);
JsonElement requestBody = new JsonParser().parse(payload);
Set<String> clientKeys = toStringSet(requestBody, "clientKeys");
Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys");
if (clientKeys != null) {
result.addAllClientAttributeNames(clientKeys);
}
if (sharedKeys != null) {
result.addAllSharedAttributeNames(sharedKeys);
}
return result.build();
} catch (RuntimeException e) {
log.warn("Failed to decode get attributes request", e);
throw new AdaptorException(e);
}
}
protected TransportProtos.ToDeviceRpcResponseMsg processToDeviceRpcResponseMsg(MqttPublishMessage inbound, String topic) throws AdaptorException {
String topicName = inbound.variableHeader().topicName();
try {
int requestId = getRequestId(topicName, topic);
String payload = inbound.payload().toString(UTF8);
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
} catch (RuntimeException e) {
log.warn("Failed to decode Rpc response", e);
throw new AdaptorException(e);
}
}
protected TransportProtos.ToServerRpcRequestMsg processToServerRpcRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound, String topic) throws AdaptorException {
String topicName = inbound.variableHeader().topicName();
String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false);
try { try {
return JsonConverter.convertToProvisionRequestMsg(payload); int requestId = getRequestId(topicName, topic);
return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@Override protected Optional<MqttMessage> processConvertFromAttributeResponseMsg(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topic) throws AdaptorException {
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg responseMsg, int requestId) { if (!StringUtils.isEmpty(responseMsg.getError())) {
return Optional.of(createMqttPublishMsg(ctx, throw new AdaptorException(responseMsg.getError());
MqttTopics.GATEWAY_PROVISION_REQUEST_TOPIC, } else {
JsonConverter.toJson(responseMsg, requestId))); int requestId = responseMsg.getRequestId();
if (requestId >= 0) {
return Optional.of(createMqttPublishMsg(ctx,
topic + requestId,
JsonConverter.toJson(responseMsg)));
}
return Optional.empty();
}
}
protected Optional<MqttMessage> processConvertFromGatewayAttributeResponseMsg(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg, String topic) throws AdaptorException {
if (!StringUtils.isEmpty(responseMsg.getError())) {
throw new AdaptorException(responseMsg.getError());
} else {
JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg);
return Optional.of(createMqttPublishMsg(ctx, topic, result));
}
} }
private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { protected MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) {
MqttFixedHeader mqttFixedHeader = MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
@ -224,16 +256,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
} }
} }
public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
String payload = validatePayload(sessionId, payloadData, false);
try {
return new JsonParser().parse(payload);
} catch (JsonSyntaxException ex) {
log.warn("Payload is in incorrect format: {}", payload);
throw new AdaptorException(ex);
}
}
private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException { private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = payloadData.toString(UTF8); String payload = payloadData.toString(UTF8);
if (payload == null) { if (payload == null) {
@ -245,4 +267,8 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
return payload; return payload;
} }
private int getRequestId(String topicName, String topic) {
return Integer.parseInt(topicName.substring(topic.length()));
}
} }

2
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java

@ -69,6 +69,6 @@ public interface MqttTransportAdaptor {
Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse, int requestId) throws AdaptorException; Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
} }

219
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java

@ -0,0 +1,219 @@
/**
* Copyright © 2016-2020 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.adaptors;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageType;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.ProtoConverter;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
import java.util.Optional;
@Component
@Slf4j
public class ProtoMqttAdaptor implements MqttTransportAdaptor {
private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
@Override
public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
byte[] bytes = toBytes(inbound.payload());
try {
return ProtoConverter.convertToTelemetryProto(bytes);
} catch (InvalidProtocolBufferException | IllegalArgumentException e) {
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
byte[] bytes = toBytes(inbound.payload());
try {
return ProtoConverter.validatePostAttributeMsg(bytes);
} catch (InvalidProtocolBufferException | IllegalArgumentException e) {
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.ClaimDeviceMsg convertToClaimDevice(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
byte[] bytes = toBytes(inbound.payload());
try {
return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes);
} catch (InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
byte[] bytes = toBytes(inbound.payload());
String topicName = inbound.variableHeader().topicName();
int requestId = getRequestId(topicName, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
try {
return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId);
} catch (InvalidProtocolBufferException e) {
log.warn("Failed to decode get attributes request", e);
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException {
byte[] bytes = toBytes(mqttMsg.payload());
try {
return TransportProtos.ToDeviceRpcResponseMsg.parseFrom(bytes);
} catch (RuntimeException | InvalidProtocolBufferException e) {
log.warn("Failed to decode Rpc response", e);
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException {
byte[] bytes = toBytes(mqttMsg.payload());
String topicName = mqttMsg.variableHeader().topicName();
try {
int requestId = getRequestId(topicName, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC);
return ProtoConverter.convertToServerRpcRequest(bytes, requestId);
} catch (InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
@Override
public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException {
byte[] bytes = toBytes(mqttMsg.payload());
String topicName = mqttMsg.variableHeader().topicName();
try {
return ProtoConverter.convertToProvisionRequestMsg(bytes);
} catch (InvalidProtocolBufferException ex) {
throw new AdaptorException(ex);
}
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
if (!StringUtils.isEmpty(responseMsg.getError())) {
throw new AdaptorException(responseMsg.getError());
} else {
int requestId = responseMsg.getRequestId();
if (requestId >= 0) {
return Optional.of(createMqttPublishMsg(ctx,
MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + requestId,
responseMsg.toByteArray()));
}
return Optional.empty();
}
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), rpcRequest.toByteArray()));
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), rpcResponse.toByteArray()));
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, notificationMsg.toByteArray()));
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray()));
}
@Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
if (!StringUtils.isEmpty(responseMsg.getError())) {
throw new AdaptorException(responseMsg.getError());
} else {
TransportApiProtos.GatewayAttributeResponseMsg.Builder responseMsgBuilder = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
responseMsgBuilder.setDeviceName(deviceName);
responseMsgBuilder.setResponseMsg(responseMsg);
byte[] payloadBytes = responseMsgBuilder.build().toByteArray();
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, payloadBytes));
}
}
@Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) {
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder builder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setNotificationMsg(notificationMsg);
byte[] payloadBytes = builder.build().toByteArray();
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, payloadBytes));
}
@Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
TransportApiProtos.GatewayDeviceRpcRequestMsg.Builder builder = TransportApiProtos.GatewayDeviceRpcRequestMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setRpcRequestMsg(rpcRequest);
byte[] payloadBytes = builder.build().toByteArray();
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, payloadBytes));
}
@Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseMsg) throws AdaptorException {
TransportApiProtos.GatewayProvisionResponseMsg.Builder builder = TransportApiProtos.GatewayProvisionResponseMsg.newBuilder();
builder.setDeviceName(deviceName);
builder.setProvisionDeviceResponseMsg(responseMsg);
byte[] payloadBytes = builder.build().toByteArray();
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_PROVISION_RESPONSE_TOPIC, payloadBytes));
}
public static byte[] toBytes(ByteBuf inbound) {
byte[] bytes = new byte[inbound.readableBytes()];
int readerIndex = inbound.readerIndex();
inbound.getBytes(readerIndex, bytes);
return bytes;
}
private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadBytes) {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
ByteBuf payload = ALLOCATOR.buffer();
payload.writeBytes(payloadBytes);
return new MqttPublishMessage(mqttFixedHeader, header, payload);
}
private int getRequestId(String topicName, String topic) {
return Integer.parseInt(topicName.substring(topic.length()));
}
}

28
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java

@ -21,9 +21,11 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; 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.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.transport.mqtt.MqttTransportContext;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter;
import org.thingsboard.server.transport.mqtt.util.MqttTopicFilterFactory; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilterFactory;
@ -39,6 +41,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
@Getter @Getter
private ChannelHandlerContext channel; private ChannelHandlerContext channel;
@Getter
private MqttTransportContext context;
private final AtomicInteger msgIdSeq = new AtomicInteger(0); private final AtomicInteger msgIdSeq = new AtomicInteger(0);
@Getter @Getter
@ -47,10 +53,11 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); private volatile MqttTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); private volatile MqttTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
private volatile TransportPayloadType payloadType = TransportPayloadType.JSON;
public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) {
public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap) {
super(sessionId, mqttQoSMap); super(sessionId, mqttQoSMap);
this.context = context;
} }
public void setChannel(ChannelHandlerContext channel) { public void setChannel(ChannelHandlerContext channel) {
@ -61,14 +68,20 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
return msgIdSeq.incrementAndGet(); return msgIdSeq.incrementAndGet();
} }
public boolean isDeviceTelemetryTopic(String topicName) { public boolean isDeviceTelemetryTopic(String topicName) { return telemetryTopicFilter.filter(topicName); }
return telemetryTopicFilter.filter(topicName);
}
public boolean isDeviceAttributesTopic(String topicName) { public boolean isDeviceAttributesTopic(String topicName) {
return attributesTopicFilter.filter(topicName); return attributesTopicFilter.filter(topicName);
} }
public MqttTransportAdaptor getPayloadAdaptor() {
return payloadType.equals(TransportPayloadType.JSON) ? context.getJsonMqttAdaptor() : context.getProtoMqttAdaptor();
}
public boolean isJsonPayloadType() {
return payloadType.equals(TransportPayloadType.JSON);
}
@Override @Override
public void setDeviceProfile(DeviceProfile deviceProfile) { public void setDeviceProfile(DeviceProfile deviceProfile) {
super.setDeviceProfile(deviceProfile); super.setDeviceProfile(deviceProfile);
@ -81,11 +94,13 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
updateTopicFilters(deviceProfile); updateTopicFilters(deviceProfile);
} }
private void updateTopicFilters(DeviceProfile deviceProfile) { private void updateTopicFilters(DeviceProfile deviceProfile) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) && if (transportConfiguration.getType().equals(DeviceTransportType.MQTT) &&
transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration; MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
payloadType = mqttConfig.getTransportPayloadType();
telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic());
attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic());
} else { } else {
@ -93,4 +108,5 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
} }
} }
} }

17
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java

@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import java.util.UUID; import java.util.UUID;
@ -70,7 +69,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
@Override @Override
public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
try { try {
parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush); parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
} }
@ -79,26 +78,26 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
@Override @Override
public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) { public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) {
try { try {
parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush); parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
} }
} }
@Override
public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
parent.deregisterSession(getDeviceInfo().getDeviceName());
}
@Override @Override
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) { public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) {
try { try {
parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(parent::writeAndFlush); parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(parent::writeAndFlush);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
} }
} }
@Override
public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
parent.deregisterSession(getDeviceInfo().getDeviceName());
}
@Override @Override
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) {
// This feature is not supported in the TB IoT Gateway yet. // This feature is not supported in the TB IoT Gateway yet.

472
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java

@ -25,32 +25,38 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonNull; import com.google.gson.JsonNull;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.ProtocolStringList;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.adaptor.ProtoConverter;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.transport.mqtt.MqttTransportContext; import org.thingsboard.server.transport.mqtt.MqttTransportContext;
import org.thingsboard.server.transport.mqtt.MqttTransportHandler; import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -80,8 +86,8 @@ public class GatewaySessionHandler {
private final ChannelHandlerContext channel; private final ChannelHandlerContext channel;
private final DeviceSessionCtx deviceSessionCtx; private final DeviceSessionCtx deviceSessionCtx;
public GatewaySessionHandler(MqttTransportContext context, DeviceSessionCtx deviceSessionCtx, UUID sessionId) { public GatewaySessionHandler(DeviceSessionCtx deviceSessionCtx, UUID sessionId) {
this.context = context; this.context = deviceSessionCtx.getContext();
this.transportService = context.getTransportService(); this.transportService = context.getTransportService();
this.deviceSessionCtx = deviceSessionCtx; this.deviceSessionCtx = deviceSessionCtx;
this.gateway = deviceSessionCtx.getDeviceInfo(); this.gateway = deviceSessionCtx.getDeviceInfo();
@ -93,10 +99,108 @@ public class GatewaySessionHandler {
this.channel = deviceSessionCtx.getChannel(); this.channel = deviceSessionCtx.getChannel();
} }
public void onDeviceConnect(MqttPublishMessage msg) throws AdaptorException { public void onDeviceConnect(MqttPublishMessage mqttMsg) throws AdaptorException {
JsonElement json = getJson(msg); if (isJsonPayloadType()) {
String deviceName = checkDeviceName(getDeviceName(json)); onDeviceConnectJson(mqttMsg);
String deviceType = getDeviceType(json); } else {
onDeviceConnectProto(mqttMsg);
}
}
public void onDeviceDisconnect(MqttPublishMessage mqttMsg) throws AdaptorException {
if (isJsonPayloadType()) {
onDeviceDisconnectJson(mqttMsg);
} else {
onDeviceDisconnectProto(mqttMsg);
}
}
public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException {
int msgId = getMsgId(mqttMsg);
ByteBuf payload = mqttMsg.payload();
if (isJsonPayloadType()) {
onDeviceTelemetryJson(msgId, payload);
} else {
onDeviceTelemetryProto(msgId, payload);
}
}
public void onDeviceClaim(MqttPublishMessage mqttMsg) throws AdaptorException {
int msgId = getMsgId(mqttMsg);
ByteBuf payload = mqttMsg.payload();
if (isJsonPayloadType()) {
onDeviceClaimJson(msgId, payload);
} else {
onDeviceClaimProto(msgId, payload);
}
}
public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException {
int msgId = getMsgId(mqttMsg);
ByteBuf payload = mqttMsg.payload();
if (isJsonPayloadType()) {
onDeviceAttributesJson(msgId, payload);
} else {
onDeviceAttributesProto(msgId, payload);
}
}
public void onDeviceAttributesRequest(MqttPublishMessage mqttMsg) throws AdaptorException {
if (isJsonPayloadType()) {
onDeviceAttributesRequestJson(mqttMsg);
} else {
onDeviceAttributesRequestProto(mqttMsg);
}
}
public void onDeviceRpcResponse(MqttPublishMessage mqttMsg) throws AdaptorException {
int msgId = getMsgId(mqttMsg);
ByteBuf payload = mqttMsg.payload();
if (isJsonPayloadType()) {
onDeviceRpcResponseJson(msgId, payload);
} else {
onDeviceRpcResponseProto(msgId, payload);
}
}
public void onGatewayDisconnect() {
devices.forEach(this::deregisterSession);
}
public String getNodeId() {
return context.getNodeId();
}
public UUID getSessionId() {
return sessionId;
}
public MqttTransportAdaptor getPayloadAdaptor() {
return deviceSessionCtx.getPayloadAdaptor();
}
void deregisterSession(String deviceName) {
GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName);
if (deviceSessionCtx != null) {
deregisterSession(deviceName, deviceSessionCtx);
} else {
log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName);
}
}
void writeAndFlush(MqttMessage mqttMessage) {
channel.writeAndFlush(mqttMessage);
}
int nextMsgId() {
return deviceSessionCtx.nextMsgId();
}
private boolean isJsonPayloadType() {
return deviceSessionCtx.isJsonPayloadType();
}
private void processOnConnect(MqttPublishMessage msg, String deviceName, String deviceType) {
log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName); log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName);
Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<GatewayDeviceSessionCtx>() { Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<GatewayDeviceSessionCtx>() {
@Override @Override
@ -183,28 +287,50 @@ public class GatewaySessionHandler {
return future; return future;
} }
public void onDeviceDisconnect(MqttPublishMessage msg) throws AdaptorException { private int getMsgId(MqttPublishMessage mqttMsg) {
return mqttMsg.variableHeader().packetId();
}
private void onDeviceConnectJson(MqttPublishMessage mqttMsg) throws AdaptorException {
JsonElement json = getJson(mqttMsg);
String deviceName = checkDeviceName(getDeviceName(json));
String deviceType = getDeviceType(json);
processOnConnect(mqttMsg, deviceName, deviceType);
}
private void onDeviceConnectProto(MqttPublishMessage mqttMsg) throws AdaptorException {
try {
TransportApiProtos.ConnectMsg connectProto = TransportApiProtos.ConnectMsg.parseFrom(getBytes(mqttMsg.payload()));
String deviceName = checkDeviceName(connectProto.getDeviceName());
String deviceType = StringUtils.isEmpty(connectProto.getDeviceType()) ? DEFAULT_DEVICE_TYPE : connectProto.getDeviceType();
processOnConnect(mqttMsg, deviceName, deviceType);
} catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
private void onDeviceDisconnectJson(MqttPublishMessage msg) throws AdaptorException {
String deviceName = checkDeviceName(getDeviceName(getJson(msg))); String deviceName = checkDeviceName(getDeviceName(getJson(msg)));
deregisterSession(deviceName); processOnDisconnect(msg, deviceName);
ack(msg);
} }
void deregisterSession(String deviceName) { private void onDeviceDisconnectProto(MqttPublishMessage mqttMsg) throws AdaptorException {
GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName); try {
if (deviceSessionCtx != null) { TransportApiProtos.DisconnectMsg connectProto = TransportApiProtos.DisconnectMsg.parseFrom(getBytes(mqttMsg.payload()));
deregisterSession(deviceName, deviceSessionCtx); String deviceName = checkDeviceName(connectProto.getDeviceName());
} else { processOnDisconnect(mqttMsg, deviceName);
log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName); } catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
} }
} }
public void onGatewayDisconnect() { private void processOnDisconnect(MqttPublishMessage msg, String deviceName) {
devices.forEach(this::deregisterSession); deregisterSession(deviceName);
ack(msg);
} }
public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException { private void onDeviceTelemetryJson(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, payload);
int msgId = mqttMsg.variableHeader().packetId();
if (json.isJsonObject()) { if (json.isJsonObject()) {
JsonObject jsonObj = json.getAsJsonObject(); JsonObject jsonObj = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) { for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
@ -218,7 +344,7 @@ public class GatewaySessionHandler {
} }
try { try {
TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(deviceEntry.getValue().getAsJsonArray()); TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(deviceEntry.getValue().getAsJsonArray());
transportService.process(deviceCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(channel, deviceName, msgId, postTelemetryMsg)); processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId);
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e); log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e);
} }
@ -235,9 +361,47 @@ public class GatewaySessionHandler {
} }
} }
public void onDeviceClaim(MqttPublishMessage mqttMsg) throws AdaptorException { private void onDeviceTelemetryProto(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); try {
int msgId = mqttMsg.variableHeader().packetId(); TransportApiProtos.GatewayTelemetryMsg telemetryMsgProto = TransportApiProtos.GatewayTelemetryMsg.parseFrom(getBytes(payload));
List<TransportApiProtos.TelemetryMsg> deviceMsgList = telemetryMsgProto.getMsgList();
if (!CollectionUtils.isEmpty(deviceMsgList)) {
deviceMsgList.forEach(telemetryMsg -> {
String deviceName = checkDeviceName(telemetryMsg.getDeviceName());
Futures.addCallback(checkDeviceConnected(deviceName),
new FutureCallback<GatewayDeviceSessionCtx>() {
@Override
public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
TransportProtos.PostTelemetryMsg msg = telemetryMsg.getMsg();
try {
TransportProtos.PostTelemetryMsg postTelemetryMsg = ProtoConverter.validatePostTelemetryMsg(msg.toByteArray());
processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId);
} catch (Throwable e) {
log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, msg, e);
}
}
@Override
public void onFailure(Throwable t) {
log.debug("[{}] Failed to process device telemetry command: {}", sessionId, deviceName, t);
}
}, context.getExecutor());
});
} else {
log.debug("[{}] Devices telemetry messages is empty for: [{}]", sessionId, gateway.getDeviceId());
throw new IllegalArgumentException("[" + sessionId + "] Devices telemetry messages is empty for [" + gateway.getDeviceId() + "]");
}
} catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
private void processPostTelemetryMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.PostTelemetryMsg postTelemetryMsg, String deviceName, int msgId) {
transportService.process(deviceCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(channel, deviceName, msgId, postTelemetryMsg));
}
private void onDeviceClaimJson(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, payload);
if (json.isJsonObject()) { if (json.isJsonObject()) {
JsonObject jsonObj = json.getAsJsonObject(); JsonObject jsonObj = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) { for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
@ -252,7 +416,7 @@ public class GatewaySessionHandler {
try { try {
DeviceId deviceId = deviceCtx.getDeviceId(); DeviceId deviceId = deviceCtx.getDeviceId();
TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(deviceId, deviceEntry.getValue()); TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(deviceId, deviceEntry.getValue());
transportService.process(deviceCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(channel, deviceName, msgId, claimDeviceMsg)); processClaimDeviceMsg(deviceCtx, claimDeviceMsg, deviceName, msgId);
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}][{}] Failed to convert claim message: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e); log.warn("[{}][{}] Failed to convert claim message: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e);
} }
@ -269,9 +433,51 @@ public class GatewaySessionHandler {
} }
} }
public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException { private void onDeviceClaimProto(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); try {
int msgId = mqttMsg.variableHeader().packetId(); TransportApiProtos.GatewayClaimMsg claimMsgProto = TransportApiProtos.GatewayClaimMsg.parseFrom(getBytes(payload));
List<TransportApiProtos.ClaimDeviceMsg> claimMsgList = claimMsgProto.getMsgList();
if (!CollectionUtils.isEmpty(claimMsgList)) {
claimMsgList.forEach(claimDeviceMsg -> {
String deviceName = checkDeviceName(claimDeviceMsg.getDeviceName());
Futures.addCallback(checkDeviceConnected(deviceName),
new FutureCallback<GatewayDeviceSessionCtx>() {
@Override
public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
TransportApiProtos.ClaimDevice claimRequest = claimDeviceMsg.getClaimRequest();
if (claimRequest == null) {
throw new IllegalArgumentException("Claim request for device: " + deviceName + " is null!");
}
try {
DeviceId deviceId = deviceCtx.getDeviceId();
TransportProtos.ClaimDeviceMsg claimDeviceMsg = ProtoConverter.convertToClaimDeviceProto(deviceId, claimRequest.toByteArray());
processClaimDeviceMsg(deviceCtx, claimDeviceMsg, deviceName, msgId);
} catch (Throwable e) {
log.warn("[{}][{}] Failed to convert claim message: {}", gateway.getDeviceId(), deviceName, claimRequest, e);
}
}
@Override
public void onFailure(Throwable t) {
log.debug("[{}] Failed to process device claiming command: {}", sessionId, deviceName, t);
}
}, context.getExecutor());
});
} else {
log.debug("[{}] Devices claim messages is empty for: [{}]", sessionId, gateway.getDeviceId());
throw new IllegalArgumentException("[" + sessionId + "] Devices claim messages is empty for [" + gateway.getDeviceId() + "]");
}
} catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
private void processClaimDeviceMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.ClaimDeviceMsg claimDeviceMsg, String deviceName, int msgId) {
transportService.process(deviceCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(channel, deviceName, msgId, claimDeviceMsg));
}
private void onDeviceAttributesJson(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, payload);
if (json.isJsonObject()) { if (json.isJsonObject()) {
JsonObject jsonObj = json.getAsJsonObject(); JsonObject jsonObj = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) { for (Map.Entry<String, JsonElement> deviceEntry : jsonObj.entrySet()) {
@ -284,7 +490,7 @@ public class GatewaySessionHandler {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
} }
TransportProtos.PostAttributeMsg postAttributeMsg = JsonConverter.convertToAttributesProto(deviceEntry.getValue().getAsJsonObject()); TransportProtos.PostAttributeMsg postAttributeMsg = JsonConverter.convertToAttributesProto(deviceEntry.getValue().getAsJsonObject());
transportService.process(deviceCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(channel, deviceName, msgId, postAttributeMsg)); processPostAttributesMsg(deviceCtx, postAttributeMsg, deviceName, msgId);
} }
@Override @Override
@ -298,34 +504,49 @@ public class GatewaySessionHandler {
} }
} }
public void onDeviceRpcResponse(MqttPublishMessage mqttMsg) throws AdaptorException { private void onDeviceAttributesProto(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); try {
int msgId = mqttMsg.variableHeader().packetId(); TransportApiProtos.GatewayAttributesMsg attributesMsgProto = TransportApiProtos.GatewayAttributesMsg.parseFrom(getBytes(payload));
if (json.isJsonObject()) { List<TransportApiProtos.AttributesMsg> attributesMsgList = attributesMsgProto.getMsgList();
JsonObject jsonObj = json.getAsJsonObject(); if (!CollectionUtils.isEmpty(attributesMsgList)) {
String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); attributesMsgList.forEach(attributesMsg -> {
Futures.addCallback(checkDeviceConnected(deviceName), String deviceName = checkDeviceName(attributesMsg.getDeviceName());
new FutureCallback<GatewayDeviceSessionCtx>() { Futures.addCallback(checkDeviceConnected(deviceName),
@Override new FutureCallback<GatewayDeviceSessionCtx>() {
public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { @Override
Integer requestId = jsonObj.get("id").getAsInt(); public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
String data = jsonObj.get("data").toString(); TransportProtos.PostAttributeMsg kvListProto = attributesMsg.getMsg();
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() if (kvListProto == null) {
.setRequestId(requestId).setPayload(data).build(); throw new IllegalArgumentException("Attributes List for device: " + deviceName + " is empty!");
transportService.process(deviceCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(channel, deviceName, msgId, rpcResponseMsg)); }
} try {
TransportProtos.PostAttributeMsg postAttributeMsg = ProtoConverter.validatePostAttributeMsg(kvListProto.toByteArray());
processPostAttributesMsg(deviceCtx, postAttributeMsg, deviceName, msgId);
} catch (Throwable e) {
log.warn("[{}][{}] Failed to process device attributes command: {}", gateway.getDeviceId(), deviceName, kvListProto, e);
}
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.debug("[{}] Failed to process device teleemtry command: {}", sessionId, deviceName, t); log.debug("[{}] Failed to process device attributes command: {}", sessionId, deviceName, t);
} }
}, context.getExecutor()); }, context.getExecutor());
} else { });
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); } else {
log.debug("[{}] Devices attributes keys list is empty for: [{}]", sessionId, gateway.getDeviceId());
throw new IllegalArgumentException("[" + sessionId + "] Devices attributes keys list is empty for [" + gateway.getDeviceId() + "]");
}
} catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
} }
} }
public void onDeviceAttributesRequest(MqttPublishMessage msg) throws AdaptorException { private void processPostAttributesMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.PostAttributeMsg postAttributeMsg, String deviceName, int msgId) {
transportService.process(deviceCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(channel, deviceName, msgId, postAttributeMsg));
}
private void onDeviceAttributesRequestJson(MqttPublishMessage msg) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, msg.payload()); JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, msg.payload());
if (json.isJsonObject()) { if (json.isJsonObject()) {
JsonObject jsonObj = json.getAsJsonObject(); JsonObject jsonObj = json.getAsJsonObject();
@ -342,27 +563,47 @@ public class GatewaySessionHandler {
keys.add(keyObj.getAsString()); keys.add(keyObj.getAsString());
} }
} }
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); TransportProtos.GetAttributeRequestMsg requestMsg = toGetAttributeRequestMsg(requestId, clientScope, keys);
result.setRequestId(requestId); processGetAttributeRequestMessage(msg, deviceName, requestMsg);
} else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
}
}
if (clientScope) { private void onDeviceAttributesRequestProto(MqttPublishMessage mqttMsg) throws AdaptorException {
result.addAllClientAttributeNames(keys); try {
} else { TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = TransportApiProtos.GatewayAttributesRequestMsg.parseFrom(getBytes(mqttMsg.payload()));
result.addAllSharedAttributeNames(keys); String deviceName = checkDeviceName(gatewayAttributesRequestMsg.getDeviceName());
} int requestId = gatewayAttributesRequestMsg.getId();
TransportProtos.GetAttributeRequestMsg requestMsg = result.build(); boolean clientScope = gatewayAttributesRequestMsg.getClient();
int msgId = msg.variableHeader().packetId(); ProtocolStringList keysList = gatewayAttributesRequestMsg.getKeysList();
Set<String> keys = new HashSet<>(keysList);
TransportProtos.GetAttributeRequestMsg requestMsg = toGetAttributeRequestMsg(requestId, clientScope, keys);
processGetAttributeRequestMessage(mqttMsg, deviceName, requestMsg);
} catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
private void onDeviceRpcResponseJson(int msgId, ByteBuf payload) throws AdaptorException {
JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, payload);
if (json.isJsonObject()) {
JsonObject jsonObj = json.getAsJsonObject();
String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString();
Futures.addCallback(checkDeviceConnected(deviceName), Futures.addCallback(checkDeviceConnected(deviceName),
new FutureCallback<GatewayDeviceSessionCtx>() { new FutureCallback<GatewayDeviceSessionCtx>() {
@Override @Override
public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
transportService.process(deviceCtx.getSessionInfo(), requestMsg, getPubAckCallback(channel, deviceName, msgId, requestMsg)); Integer requestId = jsonObj.get("id").getAsInt();
String data = jsonObj.get("data").toString();
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(requestId).setPayload(data).build();
processRpcResponseMsg(deviceCtx, rpcResponseMsg, deviceName, msgId);
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
ack(msg); log.debug("[{}] Failed to process device Rpc response command: {}", sessionId, deviceName, t);
log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t);
} }
}, context.getExecutor()); }, context.getExecutor());
} else { } else {
@ -370,6 +611,64 @@ public class GatewaySessionHandler {
} }
} }
private void onDeviceRpcResponseProto(int msgId, ByteBuf payload) throws AdaptorException {
try {
TransportApiProtos.GatewayRpcResponseMsg gatewayRpcResponseMsg = TransportApiProtos.GatewayRpcResponseMsg.parseFrom(getBytes(payload));
String deviceName = checkDeviceName(gatewayRpcResponseMsg.getDeviceName());
Futures.addCallback(checkDeviceConnected(deviceName),
new FutureCallback<GatewayDeviceSessionCtx>() {
@Override
public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
Integer requestId = gatewayRpcResponseMsg.getId();
String data = gatewayRpcResponseMsg.getData();
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setRequestId(requestId).setPayload(data).build();
processRpcResponseMsg(deviceCtx, rpcResponseMsg, deviceName, msgId);
}
@Override
public void onFailure(Throwable t) {
log.debug("[{}] Failed to process device Rpc response command: {}", sessionId, deviceName, t);
}
}, context.getExecutor());
} catch (RuntimeException | InvalidProtocolBufferException e) {
throw new AdaptorException(e);
}
}
private void processRpcResponseMsg(GatewayDeviceSessionCtx deviceCtx, TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg, String deviceName, int msgId) {
transportService.process(deviceCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(channel, deviceName, msgId, rpcResponseMsg));
}
private void processGetAttributeRequestMessage(MqttPublishMessage mqttMsg, String deviceName, TransportProtos.GetAttributeRequestMsg requestMsg) {
int msgId = getMsgId(mqttMsg);
Futures.addCallback(checkDeviceConnected(deviceName),
new FutureCallback<GatewayDeviceSessionCtx>() {
@Override
public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) {
transportService.process(deviceCtx.getSessionInfo(), requestMsg, getPubAckCallback(channel, deviceName, msgId, requestMsg));
}
@Override
public void onFailure(Throwable t) {
ack(mqttMsg);
log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t);
}
}, context.getExecutor());
}
private TransportProtos.GetAttributeRequestMsg toGetAttributeRequestMsg(int requestId, boolean clientScope, Set<String> keys) {
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
result.setRequestId(requestId);
if (clientScope) {
result.addAllClientAttributeNames(keys);
} else {
result.addAllSharedAttributeNames(keys);
}
return result.build();
}
private ListenableFuture<GatewayDeviceSessionCtx> checkDeviceConnected(String deviceName) { private ListenableFuture<GatewayDeviceSessionCtx> checkDeviceConnected(String deviceName) {
GatewayDeviceSessionCtx ctx = devices.get(deviceName); GatewayDeviceSessionCtx ctx = devices.get(deviceName);
if (ctx == null) { if (ctx == null) {
@ -388,11 +687,11 @@ public class GatewaySessionHandler {
} }
} }
private String getDeviceName(JsonElement json) throws AdaptorException { private String getDeviceName(JsonElement json) {
return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString(); return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString();
} }
private String getDeviceType(JsonElement json) throws AdaptorException { private String getDeviceType(JsonElement json) {
JsonElement type = json.getAsJsonObject().get("type"); JsonElement type = json.getAsJsonObject().get("type");
return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString(); return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString();
} }
@ -401,18 +700,15 @@ public class GatewaySessionHandler {
return JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); return JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload());
} }
private void ack(MqttPublishMessage msg) { private byte[] getBytes(ByteBuf payload) {
if (msg.variableHeader().packetId() > 0) { return ProtoMqttAdaptor.toBytes(payload);
writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().packetId()));
}
}
void writeAndFlush(MqttMessage mqttMessage) {
channel.writeAndFlush(mqttMessage);
} }
public String getNodeId() { private void ack(MqttPublishMessage msg) {
return context.getNodeId(); int msgId = getMsgId(msg);
if (msgId > 0) {
writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msgId));
}
} }
private void deregisterSession(String deviceName, GatewayDeviceSessionCtx deviceSessionCtx) { private void deregisterSession(String deviceName, GatewayDeviceSessionCtx deviceSessionCtx) {
@ -433,25 +729,9 @@ public class GatewaySessionHandler {
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
log.trace("[{}] Failed to publish msg: {}", sessionId, deviceName, msg, e); log.trace("[{}] Failed to publish msg: {} for device: {}", sessionId, msg, deviceName, e);
ctx.close(); ctx.close();
} }
}; };
} }
public MqttTransportContext getContext() {
return context;
}
MqttTransportAdaptor getAdaptor() {
return context.getAdaptor();
}
int nextMsgId() {
return deviceSessionCtx.nextMsgId();
}
public UUID getSessionId() {
return sessionId;
}
} }

8
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java

@ -16,7 +16,14 @@
package org.thingsboard.server.transport.mqtt.session; package org.thingsboard.server.transport.mqtt.session;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
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.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter;
import org.thingsboard.server.transport.mqtt.util.MqttTopicFilterFactory;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -52,5 +59,4 @@ public abstract class MqttDeviceAwareSessionContext extends DeviceAwareSessionCo
return MqttQoS.AT_LEAST_ONCE; return MqttQoS.AT_LEAST_ONCE;
} }
} }
} }

1
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java

@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
@Slf4j @Slf4j
public class MqttTopicFilterFactory { public class MqttTopicFilterFactory {

13
common/transport/transport-api/pom.xml

@ -107,6 +107,19 @@
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>

7
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -439,6 +439,13 @@ public class JsonConverter {
return result; return result;
} }
public static JsonElement toGatewayJson(String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseRequest) {
JsonObject result = new JsonObject();
result.addProperty(DEVICE_PROPERTY, deviceName);
result.add("data", JsonConverter.toJson(responseRequest));
return result;
}
public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) { public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) {
Set<AttributeKvEntry> result = new HashSet<>(); Set<AttributeKvEntry> result = new HashSet<>();
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();

170
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java

@ -0,0 +1,170 @@
/**
* Copyright © 2016-2020 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.transport.adaptor;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class ProtoConverter {
public static final JsonParser JSON_PARSER = new JsonParser();
public static TransportProtos.PostTelemetryMsg convertToTelemetryProto(byte[] payload) throws InvalidProtocolBufferException, IllegalArgumentException {
TransportProtos.TsKvListProto protoPayload = TransportProtos.TsKvListProto.parseFrom(payload);
TransportProtos.PostTelemetryMsg.Builder postTelemetryMsgBuilder = TransportProtos.PostTelemetryMsg.newBuilder();
TransportProtos.TsKvListProto tsKvListProto = validateTsKvListProto(protoPayload);
postTelemetryMsgBuilder.addTsKvList(tsKvListProto);
return postTelemetryMsgBuilder.build();
}
public static TransportProtos.PostTelemetryMsg validatePostTelemetryMsg(byte[] payload) throws InvalidProtocolBufferException, IllegalArgumentException {
TransportProtos.PostTelemetryMsg msg = TransportProtos.PostTelemetryMsg.parseFrom(payload);
TransportProtos.PostTelemetryMsg.Builder postTelemetryMsgBuilder = TransportProtos.PostTelemetryMsg.newBuilder();
List<TransportProtos.TsKvListProto> tsKvListProtoList = msg.getTsKvListList();
if (!CollectionUtils.isEmpty(tsKvListProtoList)) {
List<TransportProtos.TsKvListProto> tsKvListProtos = new ArrayList<>();
tsKvListProtoList.forEach(tsKvListProto -> {
TransportProtos.TsKvListProto transportTsKvListProto = validateTsKvListProto(tsKvListProto);
tsKvListProtos.add(transportTsKvListProto);
});
postTelemetryMsgBuilder.addAllTsKvList(tsKvListProtos);
return postTelemetryMsgBuilder.build();
} else {
throw new IllegalArgumentException("TsKv list is empty!");
}
}
public static TransportProtos.PostAttributeMsg validatePostAttributeMsg(byte[] bytes) throws IllegalArgumentException, InvalidProtocolBufferException {
TransportProtos.PostAttributeMsg proto = TransportProtos.PostAttributeMsg.parseFrom(bytes);
List<TransportProtos.KeyValueProto> kvList = proto.getKvList();
if (!CollectionUtils.isEmpty(kvList)) {
List<TransportProtos.KeyValueProto> keyValueProtos = validateKeyValueProtos(kvList);
TransportProtos.PostAttributeMsg.Builder result = TransportProtos.PostAttributeMsg.newBuilder();
result.addAllKv(keyValueProtos);
return result.build();
} else {
throw new IllegalArgumentException("KeyValue list is empty!");
}
}
public static TransportProtos.ClaimDeviceMsg convertToClaimDeviceProto(DeviceId deviceId, byte[] bytes) throws InvalidProtocolBufferException {
TransportApiProtos.ClaimDevice proto = TransportApiProtos.ClaimDevice.parseFrom(bytes);
String secretKey = proto.getSecretKey() != null ? proto.getSecretKey() : DataConstants.DEFAULT_SECRET_KEY;
long durationMs = proto.getDurationMs();
return buildClaimDeviceMsg(deviceId, secretKey, durationMs);
}
public static TransportProtos.GetAttributeRequestMsg convertToGetAttributeRequestMessage(byte[] bytes, int requestId) throws InvalidProtocolBufferException, RuntimeException {
TransportApiProtos.AttributesRequest proto = TransportApiProtos.AttributesRequest.parseFrom(bytes);
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder();
result.setRequestId(requestId);
String clientKeys = proto.getClientKeys();
String sharedKeys = proto.getSharedKeys();
if (!StringUtils.isEmpty(clientKeys)) {
List<String> clientKeysList = Arrays.asList(clientKeys.split(","));
result.addAllClientAttributeNames(clientKeysList);
}
if (!StringUtils.isEmpty(sharedKeys)) {
List<String> sharedKeysList = Arrays.asList(sharedKeys.split(","));
result.addAllSharedAttributeNames(sharedKeysList);
}
return result.build();
}
public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(byte[] bytes, int requestId) throws InvalidProtocolBufferException {
TransportApiProtos.RpcRequest proto = TransportApiProtos.RpcRequest.parseFrom(bytes);
String method = proto.getMethod();
String params = proto.getParams();
return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(method).setParams(params).build();
}
private static TransportProtos.ClaimDeviceMsg buildClaimDeviceMsg(DeviceId deviceId, String secretKey, long durationMs) {
TransportProtos.ClaimDeviceMsg.Builder result = TransportProtos.ClaimDeviceMsg.newBuilder();
return result
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.setSecretKey(secretKey)
.setDurationMs(durationMs)
.build();
}
private static TransportProtos.TsKvListProto validateTsKvListProto(TransportProtos.TsKvListProto tsKvListProto) {
TransportProtos.TsKvListProto.Builder tsKvListBuilder = TransportProtos.TsKvListProto.newBuilder();
long ts = tsKvListProto.getTs();
if (ts == 0) {
ts = System.currentTimeMillis();
}
tsKvListBuilder.setTs(ts);
List<TransportProtos.KeyValueProto> kvList = tsKvListProto.getKvList();
if (!CollectionUtils.isEmpty(kvList)) {
List<TransportProtos.KeyValueProto> keyValueListProtos = validateKeyValueProtos(kvList);
tsKvListBuilder.addAllKv(keyValueListProtos);
return tsKvListBuilder.build();
} else {
throw new IllegalArgumentException("KeyValue list is empty!");
}
}
public static TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(byte[] bytes) throws InvalidProtocolBufferException {
return TransportProtos.ProvisionDeviceRequestMsg.parseFrom(bytes);
}
private static List<TransportProtos.KeyValueProto> validateKeyValueProtos(List<TransportProtos.KeyValueProto> kvList) {
kvList.forEach(keyValueProto -> {
String key = keyValueProto.getKey();
if (StringUtils.isEmpty(key)) {
throw new IllegalArgumentException("Invalid key value: " + key + "!");
}
TransportProtos.KeyValueType type = keyValueProto.getType();
switch (type) {
case BOOLEAN_V:
case LONG_V:
case DOUBLE_V:
break;
case STRING_V:
if (StringUtils.isEmpty(keyValueProto.getStringV())) {
throw new IllegalArgumentException("Value is empty for key: " + key + "!");
}
break;
case JSON_V:
try {
JSON_PARSER.parse(keyValueProto.getJsonV());
} catch (Exception e) {
throw new IllegalArgumentException("Can't parse value: " + keyValueProto.getJsonV() + " for key: " + key + "!");
}
break;
case UNRECOGNIZED:
throw new IllegalArgumentException("Unsupported keyValueType: " + type + "!");
}
});
return kvList;
}
}

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

@ -393,6 +393,7 @@ public class DefaultTransportService implements TransportService {
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceName", sessionInfo.getDeviceName());
metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("deviceType", sessionInfo.getDeviceType());
metaData.putValue("notifyDevice", "false");
RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); RuleChainId ruleChainId = resolveRuleChainId(sessionInfo);
TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(),
deviceId, metaData, gson.toJson(json), ruleChainId, null); deviceId, metaData, gson.toJson(json), ruleChainId, null);

106
common/transport/transport-api/src/main/proto/transport.proto

@ -0,0 +1,106 @@
/**
* Copyright © 2016-2020 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.
*/
syntax = "proto3";
package transportapi;
option java_package = "org.thingsboard.server.gen.transport";
option java_outer_classname = "TransportApiProtos";
import "queue.proto";
message ClaimDevice {
string secretKey = 1;
int64 durationMs = 2;
}
message AttributesRequest {
string clientKeys = 1;
string sharedKeys = 2;
}
message RpcRequest {
string method = 1;
string params = 2;
}
message DisconnectMsg {
string deviceName = 1;
}
message ConnectMsg {
string deviceName = 1;
string deviceType = 2;
}
message TelemetryMsg {
string deviceName = 1;
transport.PostTelemetryMsg msg = 3;
}
message AttributesMsg {
string deviceName = 1;
transport.PostAttributeMsg msg = 2;
}
message ClaimDeviceMsg {
string deviceName = 1;
ClaimDevice claimRequest = 2;
}
message GatewayTelemetryMsg {
repeated TelemetryMsg msg = 1;
}
message GatewayClaimMsg {
repeated ClaimDeviceMsg msg = 1;
}
message GatewayAttributesMsg {
repeated AttributesMsg msg = 1;
}
message GatewayProvisionResponseMsg {
string deviceName = 1;
transport.ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 2;
}
message GatewayRpcResponseMsg {
string deviceName = 1;
int32 id = 2;
string data = 3;
}
message GatewayAttributeResponseMsg {
string deviceName = 1;
transport.GetAttributeResponseMsg responseMsg = 2;
}
message GatewayAttributeUpdateNotificationMsg {
string deviceName = 1;
transport.AttributeUpdateNotificationMsg notificationMsg = 2;
}
message GatewayDeviceRpcRequestMsg {
string deviceName = 1;
transport.ToDeviceRpcRequestMsg rpcRequestMsg = 2;
}
message GatewayAttributesRequestMsg {
int32 id = 1;
string deviceName = 2;
bool client = 3;
repeated string keys = 4;
}

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

@ -77,7 +77,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
alarmFieldColumnMap.put("originator", "originator_name"); alarmFieldColumnMap.put("originator", "originator_name");
} }
private static final String SELECT_ORIGINATOR_NAME = " CASE" + private static final String SELECT_ORIGINATOR_NAME = " COALESCE(CASE" +
" WHEN a.originator_type = " + EntityType.TENANT.ordinal() + " WHEN a.originator_type = " + EntityType.TENANT.ordinal() +
" THEN (select title from tenant where id = a.originator_id)" + " THEN (select title from tenant where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() + " WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() +
@ -92,7 +92,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" THEN (select name from device where id = a.originator_id)" + " THEN (select name from device where id = a.originator_id)" +
" WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() + " WHEN a.originator_type = " + EntityType.ENTITY_VIEW.ordinal() +
" THEN (select name from entity_view where id = a.originator_id)" + " THEN (select name from entity_view where id = a.originator_id)" +
" END as originator_name"; " END, 'Deleted') as originator_name";
private static final String FIELDS_SELECTION = "select a.id as id," + private static final String FIELDS_SELECTION = "select a.id as id," +
" a.created_time as created_time," + " a.created_time as created_time," +

2
dao/src/test/resources/sql-test.properties

@ -13,7 +13,7 @@ spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
spring.datasource.username=sa spring.datasource.username=sa
spring.datasource.password= spring.datasource.password=
spring.datasource.url=jdbc:hsqldb:file:/tmp/testDb;sql.enforce_size=false spring.datasource.url=jdbc:hsqldb:file:target/tmp/testDb;sql.enforce_size=false
spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver
spring.datasource.hikari.maximumPoolSize = 50 spring.datasource.hikari.maximumPoolSize = 50

3
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java

@ -177,6 +177,9 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8);
sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); sharedAttributes.addProperty("sharedAttr", sharedAttributeValue);
// Subscribe for attribute update event
mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get();
ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() ResponseEntity sharedAttributesResponse = restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE",
mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, mapper.readTree(sharedAttributes.toString()), ResponseEntity.class,

2
msa/js-executor/package.json

@ -19,7 +19,7 @@
"azure-sb": "^0.11.1", "azure-sb": "^0.11.1",
"config": "^3.3.1", "config": "^3.3.1",
"js-yaml": "^3.14.0", "js-yaml": "^3.14.0",
"kafkajs": "^1.12.0", "kafkajs": "^1.14.0",
"long": "^4.0.0", "long": "^4.0.0",
"uuid-parse": "^1.1.0", "uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2", "uuid-random": "^1.3.2",

19
msa/js-executor/queue/kafkaTemplate.js

@ -27,20 +27,10 @@ let kafkaAdmin;
let consumer; let consumer;
let producer; let producer;
const topics = [];
const configEntries = []; const configEntries = [];
function KafkaProducer() { function KafkaProducer() {
this.send = async (responseTopic, scriptId, rawResponse, headers) => { this.send = async (responseTopic, scriptId, rawResponse, headers) => {
if (!topics.includes(responseTopic)) {
let createResponseTopicResult = await createTopic(responseTopic, 1);
topics.push(responseTopic);
if (createResponseTopicResult) {
logger.info('Created new topic: %s', requestTopic);
}
}
return producer.send( return producer.send(
{ {
topic: responseTopic, topic: responseTopic,
@ -99,10 +89,13 @@ function KafkaProducer() {
} }
} }
let createRequestTopicResult = await createTopic(requestTopic, partitions); let topics = await kafkaAdmin.listTopics();
if (createRequestTopicResult) { if (!topics.includes(requestTopic)) {
logger.info('Created new topic: %s', requestTopic); let createRequestTopicResult = await createTopic(requestTopic, partitions);
if (createRequestTopicResult) {
logger.info('Created new topic: %s', requestTopic);
}
} }
consumer = kafkaClient.consumer({groupId: 'js-executor-group'}); consumer = kafkaClient.consumer({groupId: 'js-executor-group'});

10
msa/js-executor/yarn.lock

@ -1665,12 +1665,10 @@ jws@^4.0.0:
jwa "^2.0.0" jwa "^2.0.0"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
kafkajs@^1.12.0: kafkajs@^1.14.0:
version "1.12.0" version "1.14.0"
resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.12.0.tgz#50ad336baee95f3324af8ae8df6fadc96e07c613" resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.14.0.tgz#3d998a77bfde54dc502e8e88690eedf0b21a1ed6"
integrity sha512-Izkd9iFRgeeKaHEgVpGQH08ygzCbHSxTbnu8W3G3uiNaVjGibUTmTwjv1Qf2M8NORXcPfzwVyg6bBlVj4SKr9g== integrity sha512-W+WCekiooY5rJP3Me5N3gWcQ8O6uG6lw0vv9t+sI+WqXKjKwj2+CWIXJy241x+ITE+1M1D19ABSiL2J8lKja5A==
dependencies:
long "^4.0.0"
keyv@^3.0.0: keyv@^3.0.0:
version "3.1.0" version "3.1.0"

2
msa/tb/README.md

@ -37,7 +37,7 @@ Where:
> **NOTE**: **Windows** users should use docker managed volume instead of host's dir. Create docker volume (for ex. `mytb-data`) before executing `docker run` command: > **NOTE**: **Windows** users should use docker managed volume instead of host's dir. Create docker volume (for ex. `mytb-data`) before executing `docker run` command:
> ``` > ```
> $ docker create volume mytb-data > $ docker volume create mytb-data
> ``` > ```
> After you can execute docker run command using `mytb-data` volume instead of `~/.mytb-data`. > After you can execute docker run command using `mytb-data` volume instead of `~/.mytb-data`.
> In order to get access to necessary resources from external IP/Host on **Windows** machine, please execute the following commands: > In order to get access to necessary resources from external IP/Host on **Windows** machine, please execute the following commands:

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java

@ -16,7 +16,6 @@
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -24,7 +23,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Created by ashvayka on 02.04.18. * Created by ashvayka on 02.04.18.
@ -37,6 +35,8 @@ public interface RuleEngineTelemetryService {
void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback); void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback); void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback); void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback);

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

@ -0,0 +1,252 @@
/**
* Copyright © 2016-2020 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.rule.engine.profile;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
@Data
public class AlarmRuleState {
private final AlarmSeverity severity;
private final AlarmRule alarmRule;
private final long requiredDurationInMs;
private long lastEventTs;
private long duration;
public AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule) {
this.severity = severity;
this.alarmRule = alarmRule;
if (alarmRule.getCondition().getDurationValue() > 0) {
requiredDurationInMs = alarmRule.getCondition().getDurationUnit().toMillis(alarmRule.getCondition().getDurationValue());
} else {
requiredDurationInMs = 0;
}
}
public boolean eval(DeviceDataSnapshot data) {
if (requiredDurationInMs > 0) {
boolean eval = eval(alarmRule.getCondition(), data);
if (eval) {
if (lastEventTs > 0) {
if (data.getTs() > lastEventTs) {
duration += data.getTs() - lastEventTs;
lastEventTs = data.getTs();
}
} else {
lastEventTs = data.getTs();
duration = 0;
}
return duration > requiredDurationInMs;
} else {
lastEventTs = 0;
duration = 0;
return false;
}
} else {
return eval(alarmRule.getCondition(), data);
}
}
public boolean eval(long ts) {
if (requiredDurationInMs > 0 && lastEventTs > 0 && ts > lastEventTs) {
duration += ts - lastEventTs;
return duration > requiredDurationInMs;
} else {
return false;
}
}
private boolean eval(AlarmCondition condition, DeviceDataSnapshot data) {
boolean eval = true;
for (KeyFilter keyFilter : condition.getCondition()) {
EntityKeyValue value = data.getValue(keyFilter.getKey());
if (value == null) {
return false;
}
eval = eval && eval(value, keyFilter.getPredicate());
}
//TODO: use condition duration;
return eval;
}
private boolean eval(EntityKeyValue value, KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
return evalStrPredicate(value, (StringFilterPredicate) predicate);
case NUMERIC:
return evalNumPredicate(value, (NumericFilterPredicate) predicate);
case COMPLEX:
return evalComplexPredicate(value, (ComplexFilterPredicate) predicate);
case BOOLEAN:
return evalBoolPredicate(value, (BooleanFilterPredicate) predicate);
default:
return false;
}
}
private boolean evalComplexPredicate(EntityKeyValue ekv, ComplexFilterPredicate predicate) {
switch (predicate.getOperation()) {
case OR:
for (KeyFilterPredicate kfp : predicate.getPredicates()) {
if (eval(ekv, kfp)) {
return true;
}
}
return false;
case AND:
for (KeyFilterPredicate kfp : predicate.getPredicates()) {
if (!eval(ekv, kfp)) {
return false;
}
}
return true;
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalBoolPredicate(EntityKeyValue ekv, BooleanFilterPredicate predicate) {
Boolean value;
switch (ekv.getDataType()) {
case LONG:
value = ekv.getLngValue() > 0;
break;
case DOUBLE:
value = ekv.getDblValue() > 0;
break;
case BOOLEAN:
value = ekv.getBoolValue();
break;
case STRING:
try {
value = Boolean.parseBoolean(ekv.getStrValue());
break;
} catch (RuntimeException e) {
return false;
}
case JSON:
try {
value = Boolean.parseBoolean(ekv.getJsonValue());
break;
} catch (RuntimeException e) {
return false;
}
default:
return false;
}
if (value == null) {
return false;
}
switch (predicate.getOperation()) {
case EQUAL:
return value.equals(predicate.getValue().getDefaultValue());
case NOT_EQUAL:
return !value.equals(predicate.getValue().getDefaultValue());
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) {
Double value;
switch (ekv.getDataType()) {
case LONG:
value = ekv.getLngValue().doubleValue();
break;
case DOUBLE:
value = ekv.getDblValue();
break;
case BOOLEAN:
value = ekv.getBoolValue() ? 1.0 : 0.0;
break;
case STRING:
try {
value = Double.parseDouble(ekv.getStrValue());
break;
} catch (RuntimeException e) {
return false;
}
case JSON:
try {
value = Double.parseDouble(ekv.getJsonValue());
break;
} catch (RuntimeException e) {
return false;
}
default:
return false;
}
if (value == null) {
return false;
}
Double predicateValue = predicate.getValue().getDefaultValue();
switch (predicate.getOperation()) {
case NOT_EQUAL:
return !value.equals(predicateValue);
case EQUAL:
return value.equals(predicateValue);
case GREATER:
return value > predicateValue;
case GREATER_OR_EQUAL:
return value >= predicateValue;
case LESS:
return value < predicateValue;
case LESS_OR_EQUAL:
return value <= predicateValue;
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) {
String val;
String predicateValue;
if (predicate.isIgnoreCase()) {
val = ekv.getStrValue().toLowerCase();
predicateValue = predicate.getValue().getDefaultValue().toLowerCase();
} else {
val = ekv.getStrValue();
predicateValue = predicate.getValue().getDefaultValue();
}
switch (predicate.getOperation()) {
case CONTAINS:
return val.contains(predicateValue);
case EQUAL:
return val.equals(predicateValue);
case STARTS_WITH:
return val.startsWith(predicateValue);
case ENDS_WITH:
return val.endsWith(predicateValue);
case NOT_EQUAL:
return !val.equals(predicateValue);
case NOT_CONTAINS:
return !val.contains(predicateValue);
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
}

72
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceDataSnapshot.java

@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.profile;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -26,24 +27,79 @@ import java.util.concurrent.ConcurrentHashMap;
public class DeviceDataSnapshot { public class DeviceDataSnapshot {
private volatile boolean ready; private volatile boolean ready;
@Getter @Setter @Getter
@Setter
private long ts; private long ts;
private final Set<EntityKey> keys;
private final Map<EntityKey, EntityKeyValue> values = new ConcurrentHashMap<>(); private final Map<EntityKey, EntityKeyValue> values = new ConcurrentHashMap<>();
public DeviceDataSnapshot(Set<EntityKey> entityKeySet) { public DeviceDataSnapshot(Set<EntityKey> entityKeysToFetch) {
entityKeySet.forEach(key -> values.put(key, new EntityKeyValue())); this.keys = entityKeysToFetch;
this.ready = false; }
void removeValue(EntityKey key) {
switch (key.getType()) {
case ATTRIBUTE:
values.remove(key);
values.remove(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE));
values.remove(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE));
values.remove(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE));
break;
case CLIENT_ATTRIBUTE:
case SHARED_ATTRIBUTE:
case SERVER_ATTRIBUTE:
values.remove(key);
values.remove(getAttrKey(key, EntityKeyType.ATTRIBUTE));
break;
default:
values.remove(key);
}
} }
void putValue(EntityKey key, EntityKeyValue value) { void putValue(EntityKey key, EntityKeyValue value) {
values.put(key, value); switch (key.getType()) {
case ATTRIBUTE:
putIfKeyExists(key, value);
putIfKeyExists(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE), value);
putIfKeyExists(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE), value);
putIfKeyExists(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE), value);
break;
case CLIENT_ATTRIBUTE:
case SHARED_ATTRIBUTE:
case SERVER_ATTRIBUTE:
putIfKeyExists(key, value);
putIfKeyExists(getAttrKey(key, EntityKeyType.ATTRIBUTE), value);
break;
default:
putIfKeyExists(key, value);
}
}
private void putIfKeyExists(EntityKey key, EntityKeyValue value) {
if (keys.contains(key)) {
values.put(key, value);
}
} }
EntityKeyValue getValue(EntityKey key) { EntityKeyValue getValue(EntityKey key) {
return values.get(key); if (EntityKeyType.ATTRIBUTE.equals(key.getType())) {
EntityKeyValue value = values.get(key);
if (value == null) {
value = values.get(getAttrKey(key, EntityKeyType.CLIENT_ATTRIBUTE));
if (value == null) {
value = values.get(getAttrKey(key, EntityKeyType.SHARED_ATTRIBUTE));
if (value == null) {
value = values.get(getAttrKey(key, EntityKeyType.SERVER_ATTRIBUTE));
}
}
}
return value;
} else {
return values.get(key);
}
} }
boolean isReady() { private EntityKey getAttrKey(EntityKey key, EntityKeyType clientAttribute) {
return ready; return new EntityKey(clientAttribute, key.getKey());
} }
} }

245
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceProfileAlarmState.java

@ -23,34 +23,29 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
@Data @Data
class DeviceProfileAlarmState { class DeviceProfileAlarmState {
private final EntityId originator; private final EntityId originator;
private DeviceProfileAlarm alarmDefinition; private DeviceProfileAlarm alarmDefinition;
private volatile Map<AlarmSeverity, AlarmRule> createRulesSortedBySeverityDesc; private volatile List<AlarmRuleState> createRulesSortedBySeverityDesc;
private volatile AlarmRuleState clearState;
private volatile Alarm currentAlarm; private volatile Alarm currentAlarm;
private volatile boolean initialFetchDone; private volatile boolean initialFetchDone;
private volatile TbMsgMetaData lastMsgMetaData;
private volatile String lastMsgQueueName;
public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition) { public DeviceProfileAlarmState(EntityId originator, DeviceProfileAlarm alarmDefinition) {
this.originator = originator; this.originator = originator;
@ -58,38 +53,50 @@ class DeviceProfileAlarmState {
} }
public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException { public void process(TbContext ctx, TbMsg msg, DeviceDataSnapshot data) throws ExecutionException, InterruptedException {
if (!initialFetchDone) { initCurrentAlarm(ctx);
Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get(); lastMsgMetaData = msg.getMetaData();
if (alarm != null && !alarm.getStatus().isCleared()) { lastMsgQueueName = msg.getQueueName();
currentAlarm = alarm; createOrClearAlarms(ctx, data, AlarmRuleState::eval);
} }
initialFetchDone = true;
} public void process(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
initCurrentAlarm(ctx);
createOrClearAlarms(ctx, ts, AlarmRuleState::eval);
}
public <T> void createOrClearAlarms(TbContext ctx, T data, BiFunction<AlarmRuleState, T, Boolean> evalFunction) {
AlarmSeverity resultSeverity = null; AlarmSeverity resultSeverity = null;
for (Map.Entry<AlarmSeverity, AlarmRule> kv : createRulesSortedBySeverityDesc.entrySet()) { for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
AlarmRule alarmRule = kv.getValue(); if (evalFunction.apply(state, data)) {
if (eval(alarmRule.getCondition(), data)) { resultSeverity = state.getSeverity();
resultSeverity = kv.getKey();
break; break;
} }
} }
if (resultSeverity != null) { if (resultSeverity != null) {
pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity), msg); pushMsg(ctx, calculateAlarmResult(ctx, resultSeverity));
} else if (currentAlarm != null) { } else if (currentAlarm != null) {
AlarmRule clearRule = alarmDefinition.getClearRule(); if (evalFunction.apply(clearState, data)) {
if (eval(clearRule.getCondition(), data)) {
ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis()); ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm), msg); pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
currentAlarm = null; currentAlarm = null;
} }
} }
} }
public void pushMsg(TbContext ctx, TbAlarmResult alarmResult, TbMsg originalMsg) { public void initCurrentAlarm(TbContext ctx) throws InterruptedException, ExecutionException {
if (!initialFetchDone) {
Alarm alarm = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), originator, alarmDefinition.getAlarmType()).get();
if (alarm != null && !alarm.getStatus().isCleared()) {
currentAlarm = alarm;
}
initialFetchDone = true;
}
}
public void pushMsg(TbContext ctx, TbAlarmResult alarmResult) {
JsonNode jsonNodes = JacksonUtil.valueToTree(alarmResult.getAlarm()); JsonNode jsonNodes = JacksonUtil.valueToTree(alarmResult.getAlarm());
String data = jsonNodes.toString(); String data = jsonNodes.toString();
TbMsgMetaData metaData = originalMsg.getMetaData().copy(); TbMsgMetaData metaData = lastMsgMetaData.copy();
String relationType; String relationType;
if (alarmResult.isCreated()) { if (alarmResult.isCreated()) {
relationType = "Alarm Created"; relationType = "Alarm Created";
@ -105,14 +112,18 @@ class DeviceProfileAlarmState {
relationType = "Alarm Cleared"; relationType = "Alarm Cleared";
metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString()); metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString());
} }
TbMsg newMsg = ctx.newMsg(originalMsg.getQueueName(), "ALARM", originalMsg.getOriginator(), metaData, data); TbMsg newMsg = ctx.newMsg(lastMsgQueueName, "ALARM", originator, metaData, data);
ctx.tellNext(newMsg, relationType); ctx.tellNext(newMsg, relationType);
} }
public void updateState(DeviceProfileAlarm alarm) { public void updateState(DeviceProfileAlarm alarm) {
this.alarmDefinition = alarm; this.alarmDefinition = alarm;
this.createRulesSortedBySeverityDesc = new TreeMap<>(Comparator.comparingInt(AlarmSeverity::ordinal)); this.createRulesSortedBySeverityDesc = new ArrayList<>();
this.createRulesSortedBySeverityDesc.putAll(alarmDefinition.getCreateRules()); alarmDefinition.getCreateRules().forEach((severity, rule) -> {
createRulesSortedBySeverityDesc.add(new AlarmRuleState(severity, rule));
});
createRulesSortedBySeverityDesc.sort(Comparator.comparingInt(state -> state.getSeverity().ordinal()));
clearState = new AlarmRuleState(null, alarmDefinition.getClearRule());
} }
private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) {
@ -147,175 +158,5 @@ class DeviceProfileAlarmState {
} }
} }
private boolean eval(AlarmCondition condition, DeviceDataSnapshot data) {
boolean eval = true;
for (KeyFilter keyFilter : condition.getCondition()) {
EntityKeyValue value = data.getValue(keyFilter.getKey());
if (value == null) {
return false;
}
eval = eval && eval(value, keyFilter.getPredicate());
}
//TODO: use condition duration;
return eval;
}
private boolean eval(EntityKeyValue value, KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
return evalStrPredicate(value, (StringFilterPredicate) predicate);
case NUMERIC:
return evalNumPredicate(value, (NumericFilterPredicate) predicate);
case COMPLEX:
return evalComplexPredicate(value, (ComplexFilterPredicate) predicate);
case BOOLEAN:
return evalBoolPredicate(value, (BooleanFilterPredicate) predicate);
default:
return false;
}
}
private boolean evalComplexPredicate(EntityKeyValue ekv, ComplexFilterPredicate predicate) {
switch (predicate.getOperation()) {
case OR:
for (KeyFilterPredicate kfp : predicate.getPredicates()) {
if (eval(ekv, kfp)) {
return true;
}
}
return false;
case AND:
for (KeyFilterPredicate kfp : predicate.getPredicates()) {
if (!eval(ekv, kfp)) {
return false;
}
}
return true;
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalBoolPredicate(EntityKeyValue ekv, BooleanFilterPredicate predicate) {
Boolean value;
switch (ekv.getDataType()) {
case LONG:
value = ekv.getLngValue() > 0;
break;
case DOUBLE:
value = ekv.getDblValue() > 0;
break;
case BOOLEAN:
value = ekv.getBoolValue();
break;
case STRING:
try {
value = Boolean.parseBoolean(ekv.getStrValue());
break;
} catch (RuntimeException e) {
return false;
}
case JSON:
try {
value = Boolean.parseBoolean(ekv.getJsonValue());
break;
} catch (RuntimeException e) {
return false;
}
default:
return false;
}
if (value == null) {
return false;
}
switch (predicate.getOperation()) {
case EQUAL:
return value.equals(predicate.getValue().getDefaultValue());
case NOT_EQUAL:
return !value.equals(predicate.getValue().getDefaultValue());
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalNumPredicate(EntityKeyValue ekv, NumericFilterPredicate predicate) {
Double value;
switch (ekv.getDataType()) {
case LONG:
value = ekv.getLngValue().doubleValue();
break;
case DOUBLE:
value = ekv.getDblValue();
break;
case BOOLEAN:
value = ekv.getBoolValue() ? 1.0 : 0.0;
break;
case STRING:
try {
value = Double.parseDouble(ekv.getStrValue());
break;
} catch (RuntimeException e) {
return false;
}
case JSON:
try {
value = Double.parseDouble(ekv.getJsonValue());
break;
} catch (RuntimeException e) {
return false;
}
default:
return false;
}
if (value == null) {
return false;
}
Double predicateValue = predicate.getValue().getDefaultValue();
switch (predicate.getOperation()) {
case NOT_EQUAL:
return !value.equals(predicateValue);
case EQUAL:
return value.equals(predicateValue);
case GREATER:
return value > predicateValue;
case GREATER_OR_EQUAL:
return value >= predicateValue;
case LESS:
return value < predicateValue;
case LESS_OR_EQUAL:
return value <= predicateValue;
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
private boolean evalStrPredicate(EntityKeyValue ekv, StringFilterPredicate predicate) {
String val;
String predicateValue;
if (predicate.isIgnoreCase()) {
val = ekv.getStrValue().toLowerCase();
predicateValue = predicate.getValue().getDefaultValue().toLowerCase();
} else {
val = ekv.getStrValue();
predicateValue = predicate.getValue().getDefaultValue();
}
switch (predicate.getOperation()) {
case CONTAINS:
return val.contains(predicateValue);
case EQUAL:
return val.equals(predicateValue);
case STARTS_WITH:
return val.startsWith(predicateValue);
case ENDS_WITH:
return val.endsWith(predicateValue);
case NOT_EQUAL:
return !val.equals(predicateValue);
case NOT_CONTAINS:
return !val.contains(predicateValue);
default:
throw new RuntimeException("Operation not supported: " + predicate.getOperation());
}
}
} }

82
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.profile; package org.thingsboard.rule.engine.profile;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode; import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -35,6 +36,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.dao.sql.query.EntityKeyMapping; import org.thingsboard.server.dao.sql.query.EntityKeyMapping;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -77,18 +79,73 @@ class DeviceState {
} }
} }
public void harvestAlarms(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
for (DeviceProfileAlarmState state : alarmStates.values()) {
state.process(ctx, ts);
}
}
public void process(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { public void process(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
if (latestValues == null) { if (latestValues == null) {
latestValues = fetchLatestValues(ctx, deviceId); latestValues = fetchLatestValues(ctx, deviceId);
} }
if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) {
processTelemetry(ctx, msg); processTelemetry(ctx, msg);
} else if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) {
processAttributesUpdateRequest(ctx, msg);
} else if (msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) {
processAttributesUpdateNotification(ctx, msg);
} else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) {
processAttributesDeleteNotification(ctx, msg);
} else { } else {
ctx.tellSuccess(msg); ctx.tellSuccess(msg);
} }
} }
private void processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { private void processAttributesUpdateNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData()));
String scope = msg.getMetaData().getValue("scope");
if (StringUtils.isEmpty(scope)) {
scope = DataConstants.CLIENT_SCOPE;
}
processAttributesUpdate(ctx, msg, attributes, scope);
}
private void processAttributesDeleteNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
List<String> keys = new ArrayList<>();
new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray().forEach(e -> keys.add(e.getAsString()));
String scope = msg.getMetaData().getValue("scope");
if (StringUtils.isEmpty(scope)) {
scope = DataConstants.CLIENT_SCOPE;
}
if (!keys.isEmpty()) {
EntityKeyType keyType = getKeyTypeFromScope(scope);
keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key)));
for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new DeviceProfileAlarmState(deviceId, alarm));
alarmState.process(ctx, msg, latestValues);
}
}
ctx.tellSuccess(msg);
}
protected void processAttributesUpdateRequest(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData()));
processAttributesUpdate(ctx, msg, attributes, DataConstants.CLIENT_SCOPE);
}
private void processAttributesUpdate(TbContext ctx, TbMsg msg, Set<AttributeKvEntry> attributes, String scope) throws ExecutionException, InterruptedException {
if (!attributes.isEmpty()) {
latestValues = merge(latestValues, attributes, scope);
for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) {
DeviceProfileAlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new DeviceProfileAlarmState(deviceId, alarm));
alarmState.process(ctx, msg, latestValues);
}
}
ctx.tellSuccess(msg);
}
protected void processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg)); Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg));
for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) { for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) {
Long ts = entry.getKey(); Long ts = entry.getKey();
@ -110,6 +167,28 @@ class DeviceState {
return latestValues; return latestValues;
} }
private DeviceDataSnapshot merge(DeviceDataSnapshot latestValues, Set<AttributeKvEntry> attributes, String scope) {
long ts = latestValues.getTs();
for (AttributeKvEntry entry : attributes) {
ts = Math.max(ts, entry.getLastUpdateTs());
latestValues.putValue(new EntityKey(getKeyTypeFromScope(scope), entry.getKey()), toEntityValue(entry));
}
latestValues.setTs(ts);
return latestValues;
}
private static EntityKeyType getKeyTypeFromScope(String scope) {
switch (scope) {
case DataConstants.CLIENT_SCOPE:
return EntityKeyType.CLIENT_ATTRIBUTE;
case DataConstants.SHARED_SCOPE:
return EntityKeyType.SHARED_ATTRIBUTE;
case DataConstants.SERVER_SCOPE:
return EntityKeyType.SERVER_ATTRIBUTE;
}
return EntityKeyType.ATTRIBUTE;
}
private DeviceDataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException { private DeviceDataSnapshot fetchLatestValues(TbContext ctx, EntityId originator) throws ExecutionException, InterruptedException {
Set<EntityKey> entityKeysToFetch = deviceProfile.getEntityKeys(); Set<EntityKey> entityKeysToFetch = deviceProfile.getEntityKeys();
DeviceDataSnapshot result = new DeviceDataSnapshot(entityKeysToFetch); DeviceDataSnapshot result = new DeviceDataSnapshot(entityKeysToFetch);
@ -224,5 +303,4 @@ class DeviceState {
public DeviceProfileId getProfileId() { public DeviceProfileId getProfileId() {
return deviceProfile.getProfileId(); return deviceProfile.getProfileId();
} }
} }

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

@ -23,16 +23,21 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -47,53 +52,77 @@ import java.util.concurrent.ExecutionException;
configDirective = "tbNodeEmptyConfig" configDirective = "tbNodeEmptyConfig"
) )
public class TbDeviceProfileNode implements TbNode { public class TbDeviceProfileNode implements TbNode {
private static final String PERIODIC_MSG_TYPE = "TbDeviceProfilePeriodicMsg";
private RuleEngineDeviceProfileCache cache; private RuleEngineDeviceProfileCache cache;
private Map<DeviceId, DeviceState> deviceStates = new ConcurrentHashMap<>(); private final Map<DeviceId, DeviceState> deviceStates = new ConcurrentHashMap<>();
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
cache = ctx.getDeviceProfileCache(); cache = ctx.getDeviceProfileCache();
scheduleAlarmHarvesting(ctx);
//TODO: check that I am in root rule chain.
// If Yes - Init for all device profiles that do not have default rule chain id in device profile.
// If No - find device profiles with this rule chain id.
} }
/** /**
* TODO: * 2. Dynamic values evaluation;
* 1. Duration in the alarm conditions;
* 3. Update of the Device attributes (client, server and shared);
* 4. Dynamic values evaluation;
*/ */
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
EntityType originatorType = msg.getOriginator().getEntityType(); EntityType originatorType = msg.getOriginator().getEntityType();
if (EntityType.DEVICE.equals(originatorType)) { if (msg.getType().equals(PERIODIC_MSG_TYPE)) {
DeviceId deviceId = new DeviceId(msg.getOriginator().getId()); scheduleAlarmHarvesting(ctx);
if (msg.getType().equals("ENTITY_UPDATED")) { harvestAlarms(ctx, System.currentTimeMillis());
//TODO: handle if device profile id has changed. } else {
} else { if (EntityType.DEVICE.equals(originatorType)) {
DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId); DeviceId deviceId = new DeviceId(msg.getOriginator().getId());
if (deviceState != null) { if (msg.getType().equals(DataConstants.ENTITY_UPDATED)) {
deviceState.process(ctx, msg); invalidateDeviceProfileCache(deviceId, msg.getData());
} else if (msg.getType().equals(DataConstants.ENTITY_DELETED)) {
deviceStates.remove(deviceId);
} else { } else {
ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!")); DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId);
if (deviceState != null) {
deviceState.process(ctx, msg);
} else {
ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!"));
}
} }
} } else if (EntityType.DEVICE_PROFILE.equals(originatorType)) {
} else if (EntityType.DEVICE_PROFILE.equals(originatorType)) { if (msg.getType().equals("ENTITY_UPDATED")) {
if (msg.getType().equals("ENTITY_UPDATED")) { DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class);
DeviceProfile deviceProfile = JacksonUtil.fromString(msg.getData(), DeviceProfile.class); for (DeviceState state : deviceStates.values()) {
for (DeviceState state : deviceStates.values()) { if (deviceProfile.getId().equals(state.getProfileId())) {
if (deviceProfile.getId().equals(state.getProfileId())) { state.updateProfile(ctx, deviceProfile);
state.updateProfile(ctx, deviceProfile); }
} }
} }
ctx.tellSuccess(msg);
} else {
ctx.tellSuccess(msg);
}
}
}
public void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) {
DeviceState deviceState = deviceStates.get(deviceId);
if (deviceState != null) {
DeviceProfileId currentProfileId = deviceState.getProfileId();
Device device = JacksonUtil.fromString(deviceJson, Device.class);
if (!currentProfileId.equals(device.getDeviceProfileId())) {
deviceStates.remove(deviceId);
} }
ctx.tellSuccess(msg);
} else {
ctx.tellSuccess(msg);
} }
} }
private DeviceState getOrCreateDeviceState(TbContext ctx, DeviceId deviceId) { @Override
public void destroy() {
deviceStates.clear();
}
protected DeviceState getOrCreateDeviceState(TbContext ctx, DeviceId deviceId) {
DeviceState deviceState = deviceStates.get(deviceId); DeviceState deviceState = deviceStates.get(deviceId);
if (deviceState == null) { if (deviceState == null) {
DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId); DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId);
@ -105,9 +134,15 @@ public class TbDeviceProfileNode implements TbNode {
return deviceState; return deviceState;
} }
@Override protected void scheduleAlarmHarvesting(TbContext ctx) {
public void destroy() { TbMsg periodicCheck = TbMsg.newMsg(PERIODIC_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, "{}");
ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1));
}
protected void harvestAlarms(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
for (DeviceState state : deviceStates.values()) {
state.harvestAlarms(ctx, ts);
}
} }
} }

17
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -17,15 +17,13 @@ package org.thingsboard.rule.engine.telemetry;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -53,6 +51,9 @@ public class TbMsgAttributesNode implements TbNode {
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class);
if (config.getNotifyDevice() == null) {
config.setNotifyDevice(true);
}
} }
@Override @Override
@ -63,7 +64,15 @@ public class TbMsgAttributesNode implements TbNode {
} }
String src = msg.getData(); String src = msg.getData();
Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)); Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src));
ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg)); String notifyDeviceStr = msg.getMetaData().getValue("notifyDevice");
ctx.getTelemetryService().saveAndNotify(
ctx.getTenantId(),
msg.getOriginator(),
config.getScope(),
new ArrayList<>(attributes),
config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceStr) || Boolean.parseBoolean(notifyDeviceStr),
new TelemetryNodeCallback(ctx, msg)
);
} }
@Override @Override

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java

@ -24,10 +24,13 @@ public class TbMsgAttributesNodeConfiguration implements NodeConfiguration<TbMsg
private String scope; private String scope;
private Boolean notifyDevice;
@Override @Override
public TbMsgAttributesNodeConfiguration defaultConfiguration() { public TbMsgAttributesNodeConfiguration defaultConfiguration() {
TbMsgAttributesNodeConfiguration configuration = new TbMsgAttributesNodeConfiguration(); TbMsgAttributesNodeConfiguration configuration = new TbMsgAttributesNodeConfiguration();
configuration.setScope(DataConstants.SERVER_SCOPE); configuration.setScope(DataConstants.SERVER_SCOPE);
configuration.setNotifyDevice(false);
return configuration; return configuration;
} }
} }

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

15
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java

@ -124,6 +124,7 @@ public class TbDeviceProfileNodeTest {
DeviceProfile deviceProfile = new DeviceProfile(); DeviceProfile deviceProfile = new DeviceProfile();
DeviceProfileData deviceProfileData = new DeviceProfileData(); DeviceProfileData deviceProfileData = new DeviceProfileData();
KeyFilter highTempFilter = new KeyFilter(); KeyFilter highTempFilter = new KeyFilter();
highTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); highTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC); highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
@ -139,6 +140,20 @@ public class TbDeviceProfileNodeTest {
dpa.setId("highTemperatureAlarmID"); dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm"); dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)); dpa.setCreateRules(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule));
KeyFilter lowTempFilter = new KeyFilter();
lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
lowTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate lowTemperaturePredicate = new NumericFilterPredicate();
lowTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
lowTemperaturePredicate.setValue(new FilterPredicateValue<>(10.0));
lowTempFilter.setPredicate(lowTemperaturePredicate);
AlarmRule clearRule = new AlarmRule();
AlarmCondition clearCondition = new AlarmCondition();
clearCondition.setCondition(Collections.singletonList(lowTempFilter));
clearRule.setCondition(clearCondition);
dpa.setClearRule(clearRule);
deviceProfileData.setAlarms(Collections.singletonList(dpa)); deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData); deviceProfile.setProfileData(deviceProfileData);

1
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -45,7 +45,6 @@ transport:
mqtt: mqtt:
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}" bind_port: "${MQTT_BIND_PORT:1883}"
adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
timeout: "${MQTT_TIMEOUT:10000}" timeout: "${MQTT_TIMEOUT:10000}"
netty: netty:
leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"

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

Loading…
Cancel
Save