Browse Source

Merge remote-tracking branch 'upstream/develop/3.5' into feature-alarm-assigning

pull/8090/head
zbeacon 4 years ago
parent
commit
d9add9bdf8
  1. 2
      application/src/main/data/upgrade/3.4.1/schema_update.sql
  2. 10
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 12
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  4. 11
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  5. 19
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  6. 8
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
  9. 13
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java
  10. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
  14. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  15. 2
      application/src/main/resources/thingsboard.yml
  16. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  17. 31
      application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
  18. 13
      application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
  19. 45
      application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java
  20. 59
      application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java
  21. 4
      common/dao-api/pom.xml
  22. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  23. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java
  24. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  25. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
  26. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
  27. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  28. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java
  29. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  30. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  31. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java
  32. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  33. 43
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityServiceBeanFactory.java
  34. 24
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityServiceRegistry.java
  35. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  36. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  37. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java
  38. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueService.java
  39. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  40. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java
  41. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  42. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileService.java
  43. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  44. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java
  45. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java
  46. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java
  47. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java
  48. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  49. 1
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  50. 31
      common/data/src/main/java/org/thingsboard/server/common/data/util/ReflectionUtils.java
  51. 2
      common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java
  52. 2
      common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java
  53. 4
      dao/pom.xml
  54. 14
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  55. 8
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  56. 7
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  57. 14
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  58. 9
      dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
  59. 8
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  60. 8
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  61. 8
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  62. 7
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  63. 7
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  64. 24
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  65. 53
      dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java
  66. 7
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  67. 9
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  68. 8
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  69. 8
      dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java
  70. 4
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  71. 8
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  72. 8
      dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java
  73. 44
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  74. 33
      dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java
  75. 65
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  76. 8
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
  77. 9
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  78. 8
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  79. 8
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  80. 6
      dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
  81. 8
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  82. 8
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
  83. 20
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceRegistryTest.java
  84. 4
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
  85. 4
      dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java
  86. 4
      dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceRegistrySqlTest.java
  87. 11
      msa/black-box-tests/README.md
  88. 28
      msa/black-box-tests/pom.xml
  89. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
  90. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java
  91. 28
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DisableUIListeners.java
  92. 85
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/SeleniumRemoteWebDriverTest.java
  93. 45
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java
  94. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java
  95. 80
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  96. 3
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java
  97. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
  98. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
  99. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
  100. 143
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractBasePage.java

2
application/src/main/data/upgrade/3.4.1/schema_update.sql

@ -88,7 +88,7 @@ $$
-- in case of running the upgrade script a second time:
IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_edge_event')) THEN
ALTER TABLE edge_event RENAME TO old_edge_event;
CREATE INDEX IF NOT EXISTS idx_old_blob_entity_created_time_tmp ON old_blob_entity(created_time);
CREATE INDEX IF NOT EXISTS idx_old_edge_event_created_time_tmp ON old_edge_event(created_time);
ALTER INDEX IF EXISTS idx_edge_event_tenant_id_and_created_time RENAME TO idx_old_edge_event_tenant_id_and_created_time;
FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts

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

@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -58,6 +59,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -176,6 +178,14 @@ public class ActorSystemContext {
@Getter
private DeviceService deviceService;
@Autowired
@Getter
private DeviceProfileService deviceProfileService;
@Autowired
@Getter
private AssetProfileService assetProfileService;
@Autowired
@Getter
private DeviceCredentialsService deviceCredentialsService;

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

@ -71,12 +71,14 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -559,6 +561,16 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDeviceService();
}
@Override
public DeviceProfileService getDeviceProfileService() {
return mainCtx.getDeviceProfileService();
}
@Override
public AssetProfileService getAssetProfileService() {
return mainCtx.getAssetProfileService();
}
@Override
public DeviceCredentialsService getDeviceCredentialsService() {
return mainCtx.getDeviceCredentialsService();

11
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -1435,7 +1435,8 @@ public class ControllerConstants {
protected static final String ATTRIBUTES_SCOPE_DESCRIPTION = "A string value representing the attributes scope. For example, 'SERVER_SCOPE'.";
protected static final String ATTRIBUTES_KEYS_DESCRIPTION = "A string value representing the comma-separated list of attributes keys. For example, 'active,inactivityAlarmTime'.";
protected static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, CLIENT_SCOPE, SHARED_SCOPE";
protected static final String ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, SHARED_SCOPE";
protected static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES + ", CLIENT_SCOPE";
protected static final String ATTRIBUTES_JSON_REQUEST_DESCRIPTION = "A string value representing the json object. For example, '{\"key\":\"value\"}'. See API call description for more details.";
protected static final String TELEMETRY_KEYS_BASE_DESCRIPTION = "A string value representing the comma-separated list of telemetry keys.";
@ -1461,10 +1462,12 @@ public class ControllerConstants {
protected static final String SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR = "The exception was thrown during processing the request. " +
"Platform creates an audit log event about entity timeseries updates with action type 'TIMESERIES_UPDATED' that includes an error stacktrace.";
protected static final String ENTITY_ATTRIBUTE_SCOPES = " List of possible attribute scopes depends on the entity type: " +
protected static final String ENTITY_ATTRIBUTE_SCOPES_TEMPLATE = " List of possible attribute scopes depends on the entity type: " +
"\n\n * SERVER_SCOPE - supported for all entity types;" +
"\n * CLIENT_SCOPE - supported for devices;" +
"\n * SHARED_SCOPE - supported for devices. "+ "\n\n";
"\n * SHARED_SCOPE - supported for devices";
protected static final String ENTITY_SAVE_ATTRIBUTE_SCOPES = ENTITY_ATTRIBUTE_SCOPES_TEMPLATE + ".\n\n";
protected static final String ENTITY_GET_ATTRIBUTE_SCOPES = ENTITY_ATTRIBUTE_SCOPES_TEMPLATE +
";\n * CLIENT_SCOPE - supported for devices. " + "\n\n";
protected static final String ATTRIBUTE_DATA_EXAMPLE = "[\n" +
" {\"key\": \"stringAttributeKey\", \"value\": \"value\", \"lastUpdateTs\": 1609459200000},\n" +

19
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -58,7 +58,6 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
@ -92,11 +91,9 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
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 java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -104,13 +101,15 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_JSON_REQUEST_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_KEYS_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_ALLOWED_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTE_DATA_EXAMPLE;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ATTRIBUTE_SCOPES;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_GET_ATTRIBUTE_SCOPES;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_SAVE_ATTRIBUTE_SCOPES;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INVALID_STRUCTURE_OF_THE_REQUEST;
@ -241,7 +240,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Get attributes by scope (getAttributesByScope)",
notes = "Returns all attributes of a specified scope that belong to specified entity." +
ENTITY_ATTRIBUTE_SCOPES +
ENTITY_GET_ATTRIBUTE_SCOPES +
"Use optional 'keys' parameter to return specific attributes."
+ "\n Example of the result: \n\n"
+ MARKDOWN_CODE_BLOCK_START
@ -383,7 +382,7 @@ public class TelemetryController extends BaseController {
@ResponseBody
public DeferredResult<ResponseEntity> saveDeviceAttributes(
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
@ -395,7 +394,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)",
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_ATTRIBUTE_SCOPES +
ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@ -411,7 +410,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV1(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
@ -423,7 +422,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)",
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_ATTRIBUTE_SCOPES +
ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE)
@ -439,7 +438,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV2(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SAVE_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
@ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
try {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);

8
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -23,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
@ -67,6 +69,8 @@ import java.util.concurrent.Executors;
@Slf4j
public class DefaultEdgeNotificationService implements EdgeNotificationService {
public static final String EDGE_IS_ROOT_BODY_KEY = "isRoot";
@Autowired
private EdgeService edgeService;
@ -142,7 +146,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception {
edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null).get();
ObjectNode isRootBody = JacksonUtil.OBJECT_MAPPER.createObjectNode();
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, Boolean.TRUE);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, isRootBody).get();
return savedEdge;
}

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -524,7 +524,7 @@ public final class EdgeGrpcSession implements Closeable {
case CUSTOMER:
return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent);
case RULE_CHAIN:
return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edge, edgeEvent);
return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edgeEvent);
case RULE_CHAIN_METADATA:
return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion);
case ALARM:

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java

@ -35,13 +35,13 @@ import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMetadat
@TbCoreComponent
public class RuleChainMsgConstructor {
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) {
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain, boolean isRoot) {
RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(ruleChain.getId().getId().getMostSignificantBits())
.setIdLSB(ruleChain.getId().getId().getLeastSignificantBits())
.setName(ruleChain.getName())
.setRoot(ruleChain.getId().equals(edgeRootRuleChainId))
.setRoot(isRoot)
.setDebugMode(ruleChain.isDebugMode())
.setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration()));
if (ruleChain.getFirstRuleNodeId() != null) {

13
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java

@ -19,7 +19,6 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
@ -33,12 +32,14 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
@Component
@Slf4j
@TbCoreComponent
public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertRuleChainEventToDownlink(Edge edge, EdgeEvent edgeEvent) {
public DownlinkMsg convertRuleChainEventToDownlink(EdgeEvent edgeEvent) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
@ -47,9 +48,15 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
case ASSIGNED_TO_EDGE:
RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
if (ruleChain != null) {
boolean isRoot = false;
if (edgeEvent.getBody() != null && edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY) != null) {
try {
isRoot = Boolean.parseBoolean(edgeEvent.getBody().get(EDGE_IS_ROOT_BODY_KEY).asText());
} catch (Exception ignored) {}
}
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainMsgConstructor.constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain);
ruleChainMsgConstructor.constructRuleChainUpdatedMsg(msgType, ruleChain, isRoot);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addRuleChainUpdateMsg(ruleChainUpdateMsg)

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java

@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -71,9 +71,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
@Slf4j
@ -325,6 +323,9 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
case CUSTOMER:
entityId = new CustomerId(edgeEvent.getEntityId());
break;
case USER:
entityId = new UserId(edgeEvent.getEntityId());
break;
case EDGE:
entityId = new EdgeId(edgeEvent.getEntityId());
break;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -165,7 +165,8 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
Map<String, Object> entityData = new HashMap<>();
ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode();
for (AttributeKvEntry attr : ssAttributes) {
if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())) {
if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())
&& !DefaultDeviceStateService.INACTIVITY_TIMEOUT.equals(attr.getKey())) {
continue;
}
if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {

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

@ -561,7 +561,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
while (hasNext) {
for (Alarm alarm : alarms.getData()) {
if (alarm.getCustomerId() == null && alarm.getOriginator() != null) {
alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()));
alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get());
alarmDao.save(tenantId, alarm);
}
if (processed.incrementAndGet() % 1000 == 0) {

2
application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java

@ -52,7 +52,7 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi
@Override
public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) {
log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions);
cache.putIfAbsent(deviceId, sessions);
cache.put(deviceId, sessions);
return sessions;
}
}

2
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -87,7 +87,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
@Autowired
private EntityActionService entityActionService;
private static ThreadPoolExecutor executor;
private ThreadPoolExecutor executor;
@PostConstruct
private void initExecutor() {

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

@ -148,7 +148,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.2}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.3}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records

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

@ -597,7 +597,7 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
}
protected String msgErrorFieldLength(String fieldName) {
return "length of " + fieldName + " must be equal or less than 255";
return fieldName + " length must be equal or less than 255";
}
protected String msgErrorNoFound(String entityClassName, String assetIdStr) {

31
application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java

@ -26,6 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.rule.engine.action.TbCreateAlarmNode;
import org.thingsboard.rule.engine.action.TbCreateAlarmNodeConfiguration;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -35,7 +37,9 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao;
@ -44,6 +48,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -254,9 +259,35 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
testEntityDaoWithRelationsTransactionalException(ruleChainDao, savedTenant.getId(), ruleChainId, "/api/ruleChain/" + ruleChainId);
}
@Test
public void givenRuleNodeWithInvalidConfiguration_thenReturnError() throws Exception {
RuleChain ruleChain = createRuleChain("Rule chain with invalid nodes");
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
ruleChainMetaData.setRuleChainId(ruleChain.getId());
RuleNode createAlarmNode = new RuleNode();
createAlarmNode.setName("Create alarm");
createAlarmNode.setType(TbCreateAlarmNode.class.getName());
TbCreateAlarmNodeConfiguration invalidCreateAlarmNodeConfiguration = new TbCreateAlarmNodeConfiguration();
invalidCreateAlarmNodeConfiguration.setSeverity("<script/>");
invalidCreateAlarmNodeConfiguration.setAlarmType("<script/>");
createAlarmNode.setConfiguration(mapper.valueToTree(invalidCreateAlarmNodeConfiguration));
List<RuleNode> ruleNodes = new ArrayList<>();
ruleNodes.add(createAlarmNode);
ruleChainMetaData.setFirstNodeIndex(0);
ruleChainMetaData.setNodes(ruleNodes);
String error = getErrorMessage(doPost("/api/ruleChain/metadata", ruleChainMetaData)
.andExpect(status().isBadRequest()));
assertThat(error).contains("severity is malformed");
assertThat(error).contains("alarmType is malformed");
}
private RuleChain createRuleChain(String name) {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(name);
return doPost("/api/ruleChain", ruleChain, RuleChain.class);
}
}

13
application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java

@ -319,6 +319,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Device device = findDeviceByName("Edge Device 1");
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
"key1", "value1");
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"inactivityTimeout\":3600000}",
"inactivityTimeout", "3600000");
sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2");
}
@ -576,7 +578,16 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) {
if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedKey, keyValueProto.getKey());
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
switch (keyValueProto.getType()) {
case STRING_V:
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
break;
case LONG_V:
Assert.assertEquals(Long.parseLong(expectedValue), keyValueProto.getLongV());
break;
default:
Assert.fail("Unexpected data type: " + keyValueProto.getType());
}
found = true;
}
}

45
application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java

@ -18,6 +18,7 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -165,4 +166,48 @@ abstract public class BaseRuleChainEdgeTest extends AbstractEdgeTest {
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
}
@Test
public void testSetRootRuleChain() throws Exception {
// create rule chain
edgeImitator.expectMessageAmount(1);
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge New Root Rule Chain");
ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// set new rule chain as root
RuleChainId currentRootRuleChainId = edge.getRootRuleChainId();
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId()
+ "/" + savedRuleChain.getUuidId() + "/root", Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
Assert.assertTrue(ruleChainUpdateMsg.getRoot());
// revert root rule chain
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId()
+ "/" + currentRootRuleChainId.getId() + "/root", Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// unassign rule chain from edge
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
// delete rule chain
edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
}
}

59
application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
@ -67,36 +68,11 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
public void testAttributes() throws Exception {
Device device = findDeviceByName("Edge Device 1");
testAttributesUpdatedMsg(device);
testAttributesUpdatedMsg(device.getId());
testPostAttributesMsg(device);
testAttributesDeleteMsg(device);
}
private void testAttributesUpdatedMsg(Device device) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType());
Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope());
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0);
Assert.assertEquals("key1", keyValueProto.getKey());
Assert.assertEquals("value1", keyValueProto.getStringV());
}
private void testPostAttributesMsg(Device device) throws Exception {
String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}";
JsonNode postAttributesEntityData = mapper.readTree(postAttributesData);
@ -226,4 +202,35 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
edgeImitator.setRandomFailuresOnTimeseriesDownlink(false);
}
@Test
public void testAttributesUpdatedMsg_userEntity() throws Exception {
testAttributesUpdatedMsg(tenantAdmin.getId());
}
private void testAttributesUpdatedMsg(EntityId entityId) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, entityId.getId(), EdgeEventType.valueOf(entityId.getEntityType().name()), attributesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage;
Assert.assertEquals(entityId.getId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB());
Assert.assertEquals(entityId.getId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB());
Assert.assertEquals(entityId.getEntityType().name(), latestEntityDataMsg.getEntityType());
Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope());
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(1, attributesUpdatedMsg.getKvCount());
TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0);
Assert.assertEquals("key1", keyValueProto.getKey());
Assert.assertEquals("value1", keyValueProto.getStringV());
}
}

4
common/dao-api/pom.xml

@ -115,10 +115,6 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
</dependencies>
<build>

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

@ -31,14 +31,14 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.Collection;
/**
* Created by ashvayka on 11.05.17.
*/
public interface AlarmService extends TbEntityService {
public interface AlarmService extends EntityDaoService {
AlarmOperationResult createOrUpdateAlarm(Alarm alarm);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java

@ -21,9 +21,9 @@ import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface AssetProfileService extends TbEntityService {
public interface AssetProfileService extends EntityDaoService {
AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -27,11 +27,11 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface AssetService extends TbEntityService {
public interface AssetService extends EntityDaoService {
AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java

@ -25,11 +25,10 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import java.util.List;
public interface AuditLogService extends TbEntityService {
public interface AuditLogService {
PageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List<ActionType> actionTypes, TimePageLink pageLink);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java

@ -21,11 +21,11 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.Optional;
public interface CustomerService extends TbEntityService {
public interface CustomerService extends EntityDaoService {
Customer findCustomerById(TenantId tenantId, CustomerId customerId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -24,11 +24,11 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface DashboardService extends TbEntityService {
public interface DashboardService extends EntityDaoService {
Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceProfileService.java

@ -21,9 +21,9 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface DeviceProfileService extends TbEntityService {
public interface DeviceProfileService extends EntityDaoService {
DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -33,12 +33,12 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
import java.util.UUID;
public interface DeviceService extends TbEntityService {
public interface DeviceService extends EntityDaoService {
DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -26,12 +26,12 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
import java.util.Optional;
public interface EdgeService extends TbEntityService {
public interface EdgeService extends EntityDaoService {
Edge findEdgeById(TenantId tenantId, EdgeId edgeId);

7
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/TbEntityService.java → common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityDaoService.java

@ -15,14 +15,17 @@
*/
package org.thingsboard.server.dao.entity;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Optional;
public interface TbEntityService {
public interface EntityDaoService {
Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId);
Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId);
EntityType getEntityType();
}

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

@ -31,7 +31,7 @@ public interface EntityService {
Optional<String> fetchEntityLabel(TenantId tenantId, EntityId entityId);
CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);

43
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityServiceBeanFactory.java

@ -1,43 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entity;
import org.apache.commons.text.CaseUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
@Service
public class EntityServiceBeanFactory {
private static final String SERVICE_SUFFIX = "DaoService";
private final BeanFactory beanFactory;
public EntityServiceBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public TbEntityService getServiceByEntityType(EntityType entityType) {
String beanName = EntityType.RULE_NODE.equals(entityType) ? getBeanName(EntityType.RULE_CHAIN) : getBeanName(entityType);
return beanFactory.getBean(beanName, TbEntityService.class);
}
private String getBeanName(EntityType entityType) {
return CaseUtils.toCamelCase(entityType.name(), true, '_') + SERVICE_SUFFIX;
}
}

24
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityServiceRegistry.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entity;
import org.thingsboard.server.common.data.EntityType;
public interface EntityServiceRegistry {
EntityDaoService getServiceByEntityType(EntityType entityType);
}

4
common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -27,14 +27,14 @@ import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
/**
* Created by Victor Basanets on 8/27/2017.
*/
public interface EntityViewService extends TbEntityService {
public interface EntityViewService extends EntityDaoService {
EntityView saveEntityView(EntityView entityView);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -24,11 +24,10 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import java.util.List;
public interface EventService extends TbEntityService {
public interface EventService {
ListenableFuture<Void> saveAsync(Event event);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java

@ -25,11 +25,11 @@ import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.nio.ByteBuffer;
public interface OtaPackageService extends TbEntityService {
public interface OtaPackageService extends EntityDaoService {
OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueService.java

@ -20,11 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface QueueService extends TbEntityService {
public interface QueueService extends EntityDaoService {
Queue saveQueue(Queue queue);

5
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -23,11 +23,12 @@ import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface ResourceService extends TbEntityService {
public interface ResourceService extends EntityDaoService {
TbResource saveResource(TbResource resource);
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);

5
common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java

@ -23,9 +23,10 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface RpcService extends EntityDaoService {
public interface RpcService extends TbEntityService {
Rpc save(Rpc rpc);
void deleteRpc(TenantId tenantId, RpcId id);

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

@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.Collection;
import java.util.List;
@ -39,7 +39,7 @@ import java.util.List;
/**
* Created by igor on 3/12/18.
*/
public interface RuleChainService extends TbEntityService {
public interface RuleChainService extends EntityDaoService {
RuleChain saveRuleChain(RuleChain ruleChain);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileService.java

@ -21,9 +21,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface TenantProfileService extends TbEntityService {
public interface TenantProfileService extends EntityDaoService {
TenantProfile findTenantProfileById(TenantId tenantId, TenantProfileId tenantProfileId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java

@ -22,11 +22,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface TenantService extends TbEntityService {
public interface TenantService extends EntityDaoService {
Tenant findTenantById(TenantId tenantId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java

@ -19,9 +19,9 @@ import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.id.ApiUsageStateId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface ApiUsageStateService extends TbEntityService {
public interface ApiUsageStateService extends EntityDaoService {
ApiUsageState createDefaultApiUsageState(TenantId id, EntityId entityId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java

@ -24,9 +24,9 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface UserService extends TbEntityService {
public interface UserService extends EntityDaoService {
User findUserById(TenantId tenantId, UserId userId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java

@ -20,11 +20,11 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface WidgetTypeService extends TbEntityService {
public interface WidgetTypeService extends EntityDaoService {
WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java

@ -20,11 +20,11 @@ import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.entity.TbEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface WidgetsBundleService extends TbEntityService {
public interface WidgetsBundleService extends EntityDaoService {
WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId);

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.List;
@ -50,6 +51,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
@ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId;
@NoXss
@ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@Length(fieldName = "type")
private String type;

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

@ -39,7 +39,6 @@ public class RuleChainMetaData {
@ApiModelProperty(position = 2, required = true, value = "Index of the first rule node in the 'nodes' list")
private Integer firstNodeIndex;
@Valid
@ApiModelProperty(position = 3, required = true, value = "List of rule node JSON objects")
private List<RuleNode> nodes;

31
common/data/src/main/java/org/thingsboard/server/common/data/util/ReflectionUtils.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2022 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.util;
import java.lang.annotation.Annotation;
@SuppressWarnings("unchecked")
public class ReflectionUtils {
private ReflectionUtils() {}
public static <T> T getAnnotationProperty(String targetType, String annotationType, String property) throws Exception {
Class<Annotation> annotationClass = (Class<Annotation>) Class.forName(annotationType);
Annotation annotation = Class.forName(targetType).getAnnotation(annotationClass);
return (T) annotationClass.getDeclaredMethod(property).invoke(annotation);
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java

@ -26,7 +26,7 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Constraint(validatedBy = {})
public @interface Length {
String message() default "length of {fieldName} must be equal or less than {max}";
String message() default "length must be equal or less than {max}";
String fieldName();

2
common/data/src/main/java/org/thingsboard/server/common/data/validation/NoXss.java

@ -26,7 +26,7 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Constraint(validatedBy = {})
public @interface NoXss {
String message() default "field value is malformed";
String message() default "is malformed";
Class<?>[] groups() default {};

4
dao/pom.xml

@ -108,10 +108,6 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>

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

@ -28,6 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -121,7 +122,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getEndTs() == 0L) {
alarm.setEndTs(alarm.getStartTs());
}
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()));
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()).get());
if (alarm.getId() == null) {
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType());
if (existing == null || existing.getStatus().isCleared()) {
@ -357,8 +358,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private ListenableFuture<PageData<AlarmInfo>> fetchAlarmsOriginators(TenantId tenantId, PageData<AlarmInfo> alarms) {
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
for (AlarmInfo alarmInfo : alarms.getData()) {
Optional<String> originatorNameOpt = entityService.fetchEntityName(tenantId, alarmInfo.getOriginator());
alarmInfo.setOriginatorName(originatorNameOpt.isEmpty() ? "Deleted" : originatorNameOpt.get());
alarmInfo.setOriginatorName(
entityService.fetchEntityName(tenantId, alarmInfo.getOriginator()).orElse("Deleted"));
alarmFutures.add(Futures.immediateFuture(alarmInfo));
}
return Futures.transform(Futures.successfulAsList(alarmFutures),
@ -475,8 +476,13 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findAlarmById(tenantId, new AlarmId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.ALARM;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
@ -268,10 +269,15 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findAssetProfileById(tenantId, new AssetProfileId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.ASSET_PROFILE;
}
private PaginatedRemover<TenantId, AssetProfile> tenantAssetProfilesRemover =
new PaginatedRemover<>() {

7
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -433,8 +433,13 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findAssetById(tenantId, new AssetId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.ASSET;
}
}

14
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.AuditLogId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -54,7 +53,6 @@ import org.thingsboard.server.dao.service.DataValidator;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@ -62,7 +60,7 @@ import static org.thingsboard.server.dao.service.Validator.validateEntityId;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j
@Service("AuditLogDaoService")
@Service
@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "true")
public class AuditLogServiceImpl implements AuditLogService {
@ -128,8 +126,7 @@ public class AuditLogServiceImpl implements AuditLogService {
entityName = entity.getName();
} else {
try {
Optional<String> entityNameOpt = entityService.fetchEntityName(tenantId, entityId);
entityName = entityNameOpt.orElse(entityName);
entityName = entityService.fetchEntityName(tenantId, entityId).orElse(entityName);
} catch (Exception ignored) {
}
}
@ -394,7 +391,7 @@ public class AuditLogServiceImpl implements AuditLogService {
try {
auditLogValidator.validate(auditLogEntry, AuditLog::getTenantId);
} catch (Exception e) {
if (StringUtils.contains(e.getMessage(), "value is malformed")) {
if (StringUtils.contains(e.getMessage(), "is malformed")) {
auditLogEntry.setEntityName("MALFORMED");
} else {
return Futures.immediateFailedFuture(e);
@ -408,9 +405,4 @@ public class AuditLogServiceImpl implements AuditLogService {
return Futures.allAsList(futures);
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
return Optional.empty();
}
}

9
dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java

@ -23,16 +23,14 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List;
import java.util.Optional;
@Service("AuditLogDaoService")
@Service
@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "false")
public class DummyAuditLogServiceImpl implements AuditLogService {
@ -61,9 +59,4 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
return null;
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
return Optional.empty();
}
}

8
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -24,6 +24,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -182,8 +183,13 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findCustomerById(tenantId, new CustomerId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.CUSTOMER;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@ -311,10 +312,15 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findDashboardById(tenantId, new DashboardId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.DASHBOARD;
}
private class CustomerDashboardsUnassigner extends PaginatedRemover<Customer, DashboardInfo> {
private Customer customer;

8
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
@ -290,10 +291,15 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findDeviceProfileById(tenantId, new DeviceProfileId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.DEVICE_PROFILE;
}
private PaginatedRemover<TenantId, DeviceProfile> tenantDeviceProfilesRemover =
new PaginatedRemover<>() {

7
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -709,8 +709,13 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findDeviceById(tenantId, new DeviceId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.DEVICE;
}
}

7
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -522,8 +522,13 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findEdgeById(tenantId, new EdgeId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.EDGE;
}
}

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

@ -57,7 +57,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
private EntityQueryDao entityQueryDao;
@Autowired
EntityServiceBeanFactory entityServiceBeanFactory;
EntityServiceRegistry entityServiceRegistry;
@Override
public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
@ -80,8 +80,8 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
@Override
public Optional<String> fetchEntityName(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityName [{}]", entityId);
TbEntityService tbEntityService = entityServiceBeanFactory.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> hasIdOpt = tbEntityService.fetchEntity(tenantId, entityId);
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> hasIdOpt = entityDaoService.findEntity(tenantId, entityId);
if (hasIdOpt.isPresent()) {
HasId<?> hasId = hasIdOpt.get();
if (hasId instanceof HasName) {
@ -95,8 +95,8 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
@Override
public Optional<String> fetchEntityLabel(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityLabel [{}]", entityId);
TbEntityService tbEntityService = entityServiceBeanFactory.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> entityOpt = tbEntityService.fetchEntity(tenantId, entityId);
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> entityOpt = entityDaoService.fetchEntity(tenantId, entityId);
String entityLabel = null;
if (entityOpt.isPresent()) {
HasId<?> entity = entityOpt.get();
@ -117,18 +117,22 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
}
@Override
public CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) {
public Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityCustomerId [{}]", entityId);
TbEntityService tbEntityService = entityServiceBeanFactory.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> hasIdOpt = tbEntityService.fetchEntity(tenantId, entityId);
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> hasIdOpt = entityDaoService.findEntity(tenantId, entityId);
if (hasIdOpt.isPresent()) {
HasId<?> hasId = hasIdOpt.get();
if (hasId instanceof HasCustomerId) {
HasCustomerId hasCustomerId = (HasCustomerId) hasId;
return hasCustomerId.getCustomerId();
CustomerId customerId = hasCustomerId.getCustomerId();
if (customerId == null) {
customerId = NULL_CUSTOMER_ID;
}
return Optional.of(customerId);
}
}
return NULL_CUSTOMER_ID;
return Optional.of(NULL_CUSTOMER_ID);
}
private static void validateEntityCountQuery(EntityCountQuery query) {

53
dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.entity;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Service
public class DefaultEntityServiceRegistry implements EntityServiceRegistry {
private final ApplicationContext applicationContext;
private final Map<EntityType, EntityDaoService> entityDaoServicesMap;
public DefaultEntityServiceRegistry(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.entityDaoServicesMap = new HashMap<>();
}
@PostConstruct
public void init() {
applicationContext.getBeansOfType(EntityDaoService.class).values().forEach(entityDaoService -> {
EntityType entityType = entityDaoService.getEntityType();
entityDaoServicesMap.put(entityType, entityDaoService);
if (EntityType.RULE_CHAIN.equals(entityType)) {
entityDaoServicesMap.put(EntityType.RULE_NODE, entityDaoService);
}
});
}
@Override
public EntityDaoService getServiceByEntityType(EntityType entityType) {
return entityDaoServicesMap.get(entityType);
}
}

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

@ -412,8 +412,13 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findEntityViewById(tenantId, new EntityViewId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.ENTITY_VIEW;
}
}

9
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -31,19 +31,17 @@ import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.event.RuleChainDebugEvent;
import org.thingsboard.server.common.data.event.RuleNodeDebugEvent;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service("EventDaoService")
@Service
@Slf4j
public class BaseEventService implements EventService {
@ -151,9 +149,4 @@ public class BaseEventService implements EventService {
return list == null ? null : list.stream().map(e -> e.toInfo(entityType)).collect(Collectors.toList());
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
return Optional.empty();
}
}

8
dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java

@ -24,6 +24,7 @@ import org.hibernate.exception.ConstraintViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
@ -237,8 +238,13 @@ public class BaseOtaPackageService extends AbstractCachedEntityService<OtaPackag
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findOtaPackageInfoById(tenantId, new OtaPackageId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.OTA_PACKAGE;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java

@ -21,6 +21,7 @@ import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -123,10 +124,15 @@ public class BaseQueueService extends AbstractEntityService implements QueueServ
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findQueueById(tenantId, new QueueId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.QUEUE;
}
private PaginatedRemover<TenantId, Queue> tenantQueuesRemover =
new PaginatedRemover<>() {

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

@ -375,9 +375,9 @@ public class BaseRelationService implements RelationService {
private ListenableFuture<EntityRelationInfo> fetchRelationInfoAsync(TenantId tenantId, EntityRelation relation,
Function<EntityRelation, EntityId> entityIdGetter,
BiConsumer<EntityRelationInfo, String> entityNameSetter) {
Optional<String> entityNameOpt = entityService.fetchEntityName(tenantId, entityIdGetter.apply(relation));
EntityRelationInfo relationInfo = new EntityRelationInfo(relation);
entityNameSetter.accept(relationInfo, entityNameOpt.orElse("N/A"));
entityNameSetter.accept(relationInfo,
entityService.fetchEntityName(tenantId, entityIdGetter.apply(relation)).orElse("N/A"));
return Futures.immediateFuture(relationInfo);
}

8
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -20,6 +20,7 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
@ -137,10 +138,15 @@ public class BaseResourceService implements ResourceService {
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findResourceInfoById(tenantId, new TbResourceId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.TB_RESOURCE;
}
@Override
public long sumDataSizeByTenantId(TenantId tenantId) {
return resourceDao.sumDataSizeByTenantId(tenantId);

8
dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -98,10 +99,15 @@ public class BaseRpcService implements RpcService {
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findById(tenantId, new RpcId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.RPC;
}
private PaginatedRemover<TenantId, Rpc> tenantRpcRemover =
new PaginatedRemover<>() {
@Override

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

@ -52,22 +52,20 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.RuleChainDataValidator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.TENANT;
@ -138,12 +136,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
if (ruleChain == null) {
return RuleChainUpdateResult.failed();
}
ConstraintValidator.validateFields(ruleChainMetaData);
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
}
RuleChainDataValidator.validateMetaData(ruleChainMetaData);
List<RuleNode> nodes = ruleChainMetaData.getNodes();
List<RuleNode> toAddOrUpdate = new ArrayList<>();
@ -161,6 +154,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
}
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
List<RuleNode> existingRuleNodes = getRuleChainNodes(tenantId, ruleChainMetaData.getRuleChainId());
for (RuleNode existingNode : existingRuleNodes) {
deleteEntityRelations(tenantId, existingNode.getId());
@ -250,31 +244,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return RuleChainUpdateResult.successful(updatedRuleNodes);
}
private void validateCircles(List<NodeConnectionInfo> connectionInfos) {
Map<Integer, Set<Integer>> connectionsMap = new HashMap<>();
for (NodeConnectionInfo nodeConnection : connectionInfos) {
if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) {
throw new DataValidationException("Can't create the relation to yourself.");
}
connectionsMap
.computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>())
.add(nodeConnection.getToIndex());
}
connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap));
}
private void validateCircles(int from, Set<Integer> toList, Map<Integer, Set<Integer>> connectionsMap) {
if (toList == null) {
return;
}
for (Integer to : toList) {
if (from == to) {
throw new DataValidationException("Can't create circling relations in rule chain.");
}
validateCircles(from, connectionsMap.get(to), connectionsMap);
}
}
@Override
public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id.");
@ -761,13 +730,18 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
HasId<?> hasId = EntityType.RULE_NODE.equals(entityId.getEntityType()) ?
findRuleNodeById(tenantId, new RuleNodeId(entityId.getId())) :
findRuleChainById(tenantId, new RuleChainId(entityId.getId()));
return Optional.ofNullable(hasId);
}
@Override
public EntityType getEntityType() {
return EntityType.RULE_CHAIN;
}
private List<EntityRelation> getRuleChainToNodeRelations(TenantId tenantId, RuleChainId ruleChainId) {
return relationService.findByFrom(tenantId, ruleChainId, RelationTypeGroup.RULE_CHAIN);
}

33
dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.service;
import com.google.common.collect.Iterators;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
@ -23,11 +24,10 @@ import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import org.thingsboard.server.dao.exception.DataValidationException;
import javax.validation.ConstraintViolation;
import javax.validation.Path;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@ -40,14 +40,30 @@ public class ConstraintValidator {
}
public static void validateFields(Object data) {
Set<ConstraintViolation<Object>> constraintsViolations = fieldsValidator.validate(data);
List<String> validationErrors = constraintsViolations.stream()
.map(ConstraintViolation::getMessage)
validateFields(data, "Validation error: ");
}
public static void validateFields(Object data, String errorPrefix) {
List<String> constraintsViolations = getConstraintsViolations(data);
if (!constraintsViolations.isEmpty()) {
throw new DataValidationException(errorPrefix + String.join(", ", constraintsViolations));
}
}
public static List<String> getConstraintsViolations(Object data) {
return fieldsValidator.validate(data).stream()
.map(constraintViolation -> {
String property;
if (constraintViolation.getConstraintDescriptor().getAttributes().containsKey("fieldName")) {
property = constraintViolation.getConstraintDescriptor().getAttributes().get("fieldName").toString();
} else {
Path propertyPath = constraintViolation.getPropertyPath();
property = Iterators.getLast(propertyPath.iterator()).toString();
}
return property + " " + constraintViolation.getMessage();
})
.distinct()
.collect(Collectors.toList());
if (!validationErrors.isEmpty()) {
throw new DataValidationException("Validation error: " + String.join(", ", validationErrors));
}
}
private static void initializeValidators() {
@ -60,4 +76,5 @@ public class ConstraintValidator {
fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
}
}

65
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -15,23 +15,39 @@
*/
package org.thingsboard.server.dao.service.validator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.util.ReflectionUtils;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component
@Slf4j
public class RuleChainDataValidator extends DataValidator<RuleChain> {
@Autowired
@ -83,4 +99,53 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
}
}
}
public static void validateMetaData(RuleChainMetaData ruleChainMetaData) {
ConstraintValidator.validateFields(ruleChainMetaData);
ruleChainMetaData.getNodes().forEach(RuleChainDataValidator::validateRuleNode);
if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
validateCircles(ruleChainMetaData.getConnections());
}
}
public static void validateRuleNode(RuleNode ruleNode) {
String errorPrefix = "'" + ruleNode.getName() + "' node configuration is invalid: ";
ConstraintValidator.validateFields(ruleNode, errorPrefix);
Object nodeConfig;
try {
Class<Object> nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(),
"org.thingsboard.rule.engine.api.RuleNode", "configClazz");
nodeConfig = JacksonUtil.treeToValue(ruleNode.getConfiguration(), nodeConfigType);
} catch (Exception e) {
log.warn("Failed to validate node configuration: {}", ExceptionUtils.getRootCauseMessage(e));
return;
}
ConstraintValidator.validateFields(nodeConfig, errorPrefix);
}
private static void validateCircles(List<NodeConnectionInfo> connectionInfos) {
Map<Integer, Set<Integer>> connectionsMap = new HashMap<>();
for (NodeConnectionInfo nodeConnection : connectionInfos) {
if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) {
throw new DataValidationException("Can't create the relation to yourself.");
}
connectionsMap
.computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>())
.add(nodeConnection.getToIndex());
}
connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap));
}
private static void validateCircles(int from, Set<Integer> toList, Map<Integer, Set<Integer>> connectionsMap) {
if (toList == null) {
return;
}
for (Integer to : toList) {
if (from == to) {
throw new DataValidationException("Can't create circling relations in rule chain.");
}
validateCircles(from, connectionsMap.get(to), connectionsMap);
}
}
}

8
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
@ -208,10 +209,15 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService<Tenant
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findTenantProfileById(tenantId, new TenantProfileId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.TENANT_PROFILE;
}
private final PaginatedRemover<String, TenantProfile> tenantProfilesRemover =
new PaginatedRemover<>() {

9
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -23,6 +23,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
@ -261,7 +262,13 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findTenantById(new TenantId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.TENANT;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java

@ -164,7 +164,13 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findApiUsageStateById(tenantId, new ApiUsageStateId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.API_USAGE_STATE;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -28,6 +28,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
@ -401,8 +402,13 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
};
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findUserById(tenantId, new UserId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.USER;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java

@ -327,12 +327,12 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
counter.clear();
if (printTenantNames) {
String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> {
String defaultName = "N/A";
try {
Optional<String> entityNameOpt = entityService.fetchEntityName(TenantId.SYS_TENANT_ID, tenantId);
return entityNameOpt.orElse("N/A");
return entityService.fetchEntityName(TenantId.SYS_TENANT_ID, tenantId).orElse(defaultName);
} catch (Exception e) {
log.error("[{}] Failed to get tenant name", tenantId, e);
return "N/A";
return defaultName;
}
});
log.info("[{}][{}] Rate limited requests: {}", tenantId, name, rateLimitedRequests);

8
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.widget;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
@ -116,8 +117,13 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findWidgetTypeById(tenantId, new WidgetTypeId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.WIDGET_TYPE;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.widget;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
@ -155,10 +156,15 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findWidgetsBundleById(tenantId, new WidgetsBundleId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.WIDGETS_BUNDLE;
}
private PaginatedRemover<TenantId, WidgetsBundle> tenantWidgetsBundleRemover =
new PaginatedRemover<TenantId, WidgetsBundle>() {

20
dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceBeanFactoryTest.java → dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceRegistryTest.java

@ -16,31 +16,31 @@
package org.thingsboard.server.dao.service;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.dao.entity.EntityServiceBeanFactory;
import org.thingsboard.server.dao.rule.BaseRuleChainService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.entity.EntityServiceRegistry;
import org.thingsboard.server.dao.rule.RuleChainService;
@Slf4j
public abstract class BaseEntityServiceBeanFactoryTest extends AbstractServiceTest {
public abstract class BaseEntityServiceRegistryTest extends AbstractServiceTest {
@Autowired
private EntityServiceBeanFactory entityServiceBeanFactory;
private EntityServiceRegistry entityServiceRegistry;
@Test
public void givenAllEntityTypes_whenGetServiceByEntityTypeCalled_thenNoExceptionsThrows() {
public void givenAllEntityTypes_whenGetServiceByEntityTypeCalled_thenAllBeansExists() {
for (EntityType entityType : EntityType.values()) {
Assertions.assertThatCode(() -> entityServiceBeanFactory.getServiceByEntityType(entityType))
.doesNotThrowAnyException();
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityType);
Assert.assertNotNull("entityDaoService bean is missed for type: " + entityType.name(), entityDaoService);
}
}
@Test
public void givenRuleNodeEntityType_whenGetServiceByEntityTypeCalled_thenReturnedRuleChainDaoService() {
Assert.assertTrue(entityServiceBeanFactory.getServiceByEntityType(EntityType.RULE_NODE) instanceof BaseRuleChainService);
public void givenRuleNodeEntityType_whenGetServiceByEntityTypeCalled_thenReturnedRuleChainService() {
Assert.assertTrue(entityServiceRegistry.getServiceByEntityType(EntityType.RULE_NODE) instanceof RuleChainService);
}
}

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

@ -674,7 +674,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmwareInfo.setTenantId(tenantId);
thrown.expect(DataValidationException.class);
thrown.expectMessage("length of title must be equal or less than 255");
thrown.expectMessage("title length must be equal or less than 255");
otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
}
@ -689,7 +689,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(StringUtils.random(257));
thrown.expectMessage("length of version must be equal or less than 255");
thrown.expectMessage("version length must be equal or less than 255");
otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
}

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

@ -43,7 +43,7 @@ public class NoXssValidatorTest {
assertThatThrownBy(() -> {
ConstraintValidator.validateFields(invalidAsset);
}).hasMessageContaining("field value is malformed");
}).hasMessageContaining("is malformed");
}
@Test
@ -55,7 +55,7 @@ public class NoXssValidatorTest {
assertThatThrownBy(() -> {
ConstraintValidator.validateFields(invalidAsset);
}).hasMessageContaining("field value is malformed");
}).hasMessageContaining("is malformed");
}
}

4
dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceBeanFactorySqlTest.java → dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceRegistrySqlTest.java

@ -15,10 +15,10 @@
*/
package org.thingsboard.server.dao.service.sql;
import org.thingsboard.server.dao.service.BaseEntityServiceBeanFactoryTest;
import org.thingsboard.server.dao.service.BaseEntityServiceRegistryTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class EntityServiceBeanFactorySqlTest extends BaseEntityServiceBeanFactoryTest {
public class EntityServiceRegistrySqlTest extends BaseEntityServiceRegistryTest {
}

11
msa/black-box-tests/README.md

@ -30,9 +30,18 @@ As result, in REPOSITORY column, next images should be present:
mvn clean install -DblackBoxTests.skip=false -DblackBoxTests.hybridMode=true
To run the black box tests with using local env run tests in the [msa/black-box-tests](../black-box-tests) directory with runLocal property:
- To run the black box tests with using local env run tests in the [msa/black-box-tests](../black-box-tests) directory with runLocal property:
mvn clean install -DblackBoxTests.skip=false -DrunLocal=true
- To run ui smoke tests in the [msa/black-box-tests](../black-box-tests) directory specifying suite name:
mvn clean install -DblackBoxTests.skip=false -Dsuite=uiTests
- To run all tests in the [msa/black-box-tests](../black-box-tests) directory specifying suite name:
mvn clean install -DblackBoxTests.skip=false -Dsuite=all

28
msa/black-box-tests/pom.xml

@ -34,6 +34,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
<suite>connectivity</suite>
</properties>
<dependencies>
@ -170,6 +171,21 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
@ -178,7 +194,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/testNG.xml</suiteXmlFile>
<suiteXmlFile>src/test/resources/${suite}.xml</suiteXmlFile>
</suiteXmlFiles>
<skipTests>${blackBoxTests.skip}</skipTests>
</configuration>
@ -190,6 +206,16 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>${allure-maven.version}</version>
<configuration>
<reportVersion>${allure-testng.version}</reportVersion>
<allureDownloadUrl>https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/
${allure-testng.version}/allure-commandline-${allure-testng.version}.zip</allureDownloadUrl>
</configuration>
</plugin>
</plugins>
</build>

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java

@ -63,7 +63,7 @@ public abstract class AbstractContainerTest {
}
}
@AfterSuite
@AfterSuite()
public void afterSuite() {
if (containerTestSuite.isActive()) {
containerTestSuite.stop();

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java

@ -109,7 +109,8 @@ public class ContainerTestSuite {
new File(targetDir + "docker-compose.postgres.volumes.yml"),
new File(targetDir + "docker-compose." + QUEUE_TYPE + ".yml"),
new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.yml" : "docker-compose.redis.yml")),
new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.volumes.yml" : "docker-compose.redis.volumes.yml"))
new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.volumes.yml" : "docker-compose.redis.volumes.yml")),
new File(targetDir + ("docker-selenium.yml"))
));
Map<String, String> queueEnv = new HashMap<>();
@ -230,4 +231,8 @@ public class ContainerTestSuite {
fail("failed to update file");
}
}
public DockerComposeContainer<?> getTestContainer() {
return testContainer;
}
}

28
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DisableUIListeners.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 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.msa;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DisableUIListeners {
}

85
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/SeleniumRemoteWebDriverTest.java

@ -0,0 +1,85 @@
/**
* Copyright © 2016-2022 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.msa;
import com.google.common.io.Files;
import io.qameta.allure.Attachment;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.LocalFileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
@Slf4j
public class SeleniumRemoteWebDriverTest {
static final int WIDTH = 1680;
static final int HEIGHT = 1050;
final Dimension dimension = new Dimension(WIDTH, HEIGHT);
WebDriver driver;
@SneakyThrows
@Attachment(value = "Page screenshot", type = "image/png")
public static byte[] captureScreen(WebDriver driver, String dirPath) {
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(screenshot, new File("./target/allure-results/screenshots/" + dirPath + "//" + screenshot.getName()));
return Files.toByteArray(screenshot);
}
/**
* Requirement:
* docker run --name=chrome --rm --network=host -p 4444:4444 -p 7900:7900 --shm-size="2g" -e SE_NODE_MAX_SESSIONS=8 -e SE_NODE_OVERRIDE_MAX_SESSIONS=true -e SE_NODE_SESSION_TIMEOUT=90 -e SE_SCREEN_WIDTH=1920 -e SE_SCREEN_HEIGHT=1080 -e SE_SCREEN_DEPTH=24 -e SE_SCREEN_DPI=74 selenium/standalone-chrome
* */
@BeforeEach
void setUp() throws MalformedURLException {
log.info("Requirement:");
log.info("docker run --name=chrome --rm --network=host -p 4444:4444 -p 7900:7900 --shm-size=\"2g\" -e SE_NODE_MAX_SESSIONS=8 -e SE_NODE_OVERRIDE_MAX_SESSIONS=true -e SE_NODE_SESSION_TIMEOUT=90 -e SE_SCREEN_WIDTH=1920 -e SE_SCREEN_HEIGHT=1080 -e SE_SCREEN_DEPTH=24 -e SE_SCREEN_DPI=74 selenium/standalone-chrome");
log.info("*----------------------* Setup driver *----------------------*");
ChromeOptions options = new ChromeOptions();
RemoteWebDriver remoteWebDriver = new RemoteWebDriver(new URL("http://127.0.0.1:4444"), options);
remoteWebDriver.setFileDetector(new LocalFileDetector());
driver = remoteWebDriver;
driver.manage().window().setSize(dimension);
}
@AfterEach
void tearDown() {
log.info("*----------------------* Teardown *----------------------*");
driver.quit();
}
@Test
void testSeleniumConnection() {
driver.get("https://thingsboard.io/");
captureScreen(driver, "success");
log.info("Check the screenshot on target/allure-results/screenshots/success/screenshot???????????????.png");
//Thread.sleep(TimeUnit.SECONDS.toMillis(30));
}
}

45
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java

@ -16,18 +16,22 @@
package org.thingsboard.server.msa;
import lombok.extern.slf4j.Slf4j;
import org.testng.ITestContext;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import org.testng.internal.ConstructorOrMethod;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import static org.testng.internal.Utils.log;
import static org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest.captureScreen;
@Slf4j
public class TestListener extends TestListenerAdapter {
public class TestListener implements ITestListener {
WebDriver driver;
@Override
public void onTestStart(ITestResult result) {
super.onTestStart(result);
log.info("===>>> Test started: " + result.getName());
}
@ -36,10 +40,14 @@ public class TestListener extends TestListenerAdapter {
*/
@Override
public void onTestSuccess(ITestResult result) {
super.onTestSuccess(result);
if (result != null) {
log.info("<<<=== Test completed successfully: " + result.getName());
log.info("<<<=== Test completed successfully: " + result.getName());
ConstructorOrMethod consOrMethod = result.getMethod().getConstructorOrMethod();
DisableUIListeners disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableUIListeners.class);
if (disable != null) {
return;
}
driver = ((AbstractDriverBaseTest) result.getInstance()).getDriver();
captureScreen(driver, "success");
}
/**
@ -47,7 +55,28 @@ public class TestListener extends TestListenerAdapter {
*/
@Override
public void onTestFailure(ITestResult result) {
super.onTestFailure(result);
log.info("<<<=== Test failed: " + result.getName());
ConstructorOrMethod consOrMethod = result.getMethod().getConstructorOrMethod();
DisableUIListeners disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableUIListeners.class);
if (disable != null) {
return;
}
driver = ((AbstractDriverBaseTest) result.getInstance()).getDriver();
captureScreen(driver, "failure");
}
/**
* Invoked when a test skipped
*/
@Override
public void onTestSkipped(ITestResult result) {
log.info("<<<=== Test skipped: " + result.getName());
ConstructorOrMethod consOrMethod = result.getMethod().getConstructorOrMethod();
DisableUIListeners disable = consOrMethod.getMethod().getDeclaringClass().getAnnotation(DisableUIListeners.class);
if (disable != null) {
return;
}
driver = ((AbstractDriverBaseTest) result.getInstance()).getDriver();
captureScreen(driver, "skipped");
}
}

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java

@ -39,6 +39,13 @@ public class TestProperties {
return getProperties().getProperty("tb.baseUrl");
}
public static String getBaseUiUrl() {
if (instance.isActive()) {
return "https://host.docker.internal";
}
return getProperties().getProperty("tb.baseUrl");
}
public static String getWebSocketUrl() {
if (instance.isActive()) {
return WSS_URL;

80
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -26,8 +26,10 @@ import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
@ -78,7 +80,7 @@ public class TestRestClient {
loginRequest.put("password", password);
JsonPath jsonPath = given().spec(requestSpec).body(loginRequest)
.post( "/api/auth/login")
.post("/api/auth/login")
.getBody().jsonPath();
token = jsonPath.get("token");
refreshToken = jsonPath.get("refreshToken");
@ -86,7 +88,7 @@ public class TestRestClient {
}
public Device postDevice(String accessToken, Device device) {
return given().spec(requestSpec).body(device)
return given().spec(requestSpec).body(device)
.pathParams("accessToken", accessToken)
.post("/api/device?accessToken={accessToken}")
.then()
@ -105,62 +107,65 @@ public class TestRestClient {
}
public ValidatableResponse getDeviceById(DeviceId deviceId, int statusCode) {
return given().spec(requestSpec)
return given().spec(requestSpec)
.pathParams("deviceId", deviceId.getId())
.get("/api/device/{deviceId}")
.then()
.statusCode(statusCode);
}
public Device getDeviceById(DeviceId deviceId) {
return getDeviceById(deviceId, HTTP_OK)
return getDeviceById(deviceId, HTTP_OK)
.extract()
.as(Device.class);
}
public DeviceCredentials getDeviceCredentialsByDeviceId(DeviceId deviceId) {
return given().spec(requestSpec).get("/api/device/{deviceId}/credentials", deviceId.getId())
.then()
.assertThat()
.statusCode(HTTP_OK)
.extract()
.as(DeviceCredentials.class);
.then()
.assertThat()
.statusCode(HTTP_OK)
.extract()
.as(DeviceCredentials.class);
}
public ValidatableResponse postTelemetry(String credentialsId, JsonNode telemetry) {
return given().spec(requestSpec).body(telemetry)
.post("/api/v1/{credentialsId}/telemetry", credentialsId)
.then()
.statusCode(HTTP_OK);
return given().spec(requestSpec).body(telemetry)
.post("/api/v1/{credentialsId}/telemetry", credentialsId)
.then()
.statusCode(HTTP_OK);
}
public ValidatableResponse deleteDevice(DeviceId deviceId) {
return given().spec(requestSpec)
return given().spec(requestSpec)
.delete("/api/device/{deviceId}", deviceId.getId())
.then()
.statusCode(HTTP_OK);
}
public ValidatableResponse deleteDeviceIfExists(DeviceId deviceId) {
return given().spec(requestSpec)
return given().spec(requestSpec)
.delete("/api/device/{deviceId}", deviceId.getId())
.then()
.statusCode(anyOf(is(HTTP_OK),is(HTTP_NOT_FOUND)));
.statusCode(anyOf(is(HTTP_OK), is(HTTP_NOT_FOUND)));
}
public ValidatableResponse postTelemetryAttribute(String entityType, DeviceId deviceId, String scope, JsonNode attribute) {
return given().spec(requestSpec).body(attribute)
return given().spec(requestSpec).body(attribute)
.post("/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", entityType, deviceId.getId(), scope)
.then()
.statusCode(HTTP_OK);
}
public ValidatableResponse postAttribute(String accessToken, JsonNode attribute) {
return given().spec(requestSpec).body(attribute)
return given().spec(requestSpec).body(attribute)
.post("/api/v1/{accessToken}/attributes/", accessToken)
.then()
.statusCode(HTTP_OK);
}
public JsonNode getAttributes(String accessToken, String clientKeys, String sharedKeys) {
return given().spec(requestSpec)
return given().spec(requestSpec)
.queryParam("clientKeys", clientKeys)
.queryParam("sharedKeys", sharedKeys)
.get("/api/v1/{accessToken}/attributes", accessToken)
@ -186,10 +191,11 @@ public class TestRestClient {
.then()
.statusCode(HTTP_OK)
.extract()
.as(new TypeRef<PageData<RuleChain>>() {});
.as(new TypeRef<PageData<RuleChain>>() {
});
}
public RuleChain postRootRuleChain(RuleChain ruleChain) {
public RuleChain postRuleChain(RuleChain ruleChain) {
return given().spec(requestSpec)
.body(ruleChain)
.post("/api/ruleChain")
@ -258,7 +264,8 @@ public class TestRestClient {
.then()
.statusCode(HTTP_OK)
.extract()
.as(new TypeRef<List<EntityRelation>>() {});
.as(new TypeRef<List<EntityRelation>>() {
});
}
public JsonNode postServerSideRpc(DeviceId deviceId, JsonNode serverRpcPayload) {
@ -289,6 +296,35 @@ public class TestRestClient {
.as(DeviceProfile.class);
}
public Customer postCustomer(Customer customer) {
return given().spec(requestSpec)
.body(customer)
.post("/api/customer")
.then()
.statusCode(HTTP_OK)
.extract()
.as(Customer.class);
}
public void deleteCustomer(CustomerId customerId) {
given().spec(requestSpec)
.delete("/api/customer/{customerId}", customerId.getId())
.then()
.statusCode(HTTP_OK);
}
public PageData<Customer> getCustomers(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
return given().spec(requestSpec).queryParams(params)
.get("/api/customers")
.then()
.statusCode(HTTP_OK)
.extract()
.as(new TypeRef<PageData<Customer>>() {
});
}
public String getToken() {
return token;
}

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

@ -18,7 +18,6 @@ package org.thingsboard.server.msa.connectivity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonObject;
import io.restassured.path.json.JsonPath;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@ -29,11 +28,13 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.DisableUIListeners;
import org.thingsboard.server.msa.TestCoapClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype;
@DisableUIListeners
public class CoapClientTest extends AbstractContainerTest {
private TestCoapClient client;

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.DisableUIListeners;
import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
@ -37,6 +38,7 @@ import static org.thingsboard.server.common.data.DataConstants.DEVICE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype;
@DisableUIListeners
public class HttpClientTest extends AbstractContainerTest {
private Device device;
@BeforeMethod

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java

@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.DisableUIListeners;
import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.mapper.AttributesResponse;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
@ -68,6 +69,7 @@ import static org.thingsboard.server.common.data.DataConstants.DEVICE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype;
@DisableUIListeners
@Slf4j
public class MqttClientTest extends AbstractContainerTest {
@ -426,7 +428,7 @@ public class MqttClientTest extends AbstractContainerTest {
RuleChain newRuleChain = new RuleChain();
newRuleChain.setName("testRuleChain");
RuleChain ruleChain = testRestClient.postRootRuleChain(newRuleChain);
RuleChain ruleChain = testRestClient.postRuleChain(newRuleChain);
JsonNode configuration = mapper.readTree(this.getClass().getClassLoader().getResourceAsStream("RpcResponseRuleChainMetadata.json"));
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();

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

@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.DisableUIListeners;
import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
@ -66,6 +67,7 @@ import static org.thingsboard.server.common.data.DataConstants.DEVICE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultGatewayPrototype;
@DisableUIListeners
@Slf4j
public class MqttGatewayClientTest extends AbstractContainerTest {
private Device gatewayDevice;

143
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractBasePage.java

@ -0,0 +1,143 @@
/**
* Copyright © 2016-2022 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.msa.ui.base;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Slf4j
abstract public class AbstractBasePage {
protected WebDriver driver;
protected WebDriverWait wait;
protected Actions actions;
public AbstractBasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofMillis(5000));
this.actions = new Actions(driver);
}
@SneakyThrows
protected static void sleep(double second) {
Thread.sleep((long) (second * 1000L));
}
protected WebElement waitUntilVisibilityOfElementLocated(String locator) {
try {
return wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(locator)));
} catch (WebDriverException e) {
log.error("No visibility element: " + locator);
return null;
}
}
protected WebElement waitUntilElementToBeClickable(String locator) {
try {
return wait.until(ExpectedConditions.elementToBeClickable(By.xpath(locator)));
} catch (WebDriverException e) {
log.error("No clickable element: " + locator);
return null;
}
}
protected List<WebElement> waitUntilVisibilityOfElementsLocated(String locator) {
try {
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(locator)));
return driver.findElements(By.xpath(locator));
} catch (WebDriverException e) {
log.error("No visibility elements: " + locator);
return null;
}
}
protected List<WebElement> waitUntilElementsToBeClickable(String locator) {
try {
wait.until(ExpectedConditions.elementToBeClickable(By.xpath(locator)));
return driver.findElements(By.xpath(locator));
} catch (WebDriverException e) {
log.error("No clickable elements: " + locator);
return null;
}
}
public void waitUntilUrlContainsText(String urlPath) {
try {
wait.until(ExpectedConditions.urlContains(urlPath));
} catch (WebDriverException e) {
log.error("This URL path is missing");
}
}
protected void moveCursor(WebElement element) {
actions.moveToElement(element).perform();
}
protected void doubleClick(WebElement element) {
actions.doubleClick(element).build().perform();
}
public boolean elementIsNotPresent(String locator) {
try {
return wait.until(ExpectedConditions.not(ExpectedConditions.visibilityOfElementLocated(By.xpath(locator))));
} catch (WebDriverException e) {
throw new AssertionError("Element is present");
}
}
public boolean elementsIsNotPresent(String locator) {
try {
return wait.until(ExpectedConditions.not(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.xpath(locator))));
} catch (WebDriverException e) {
throw new AssertionError("Elements is present");
}
}
public void waitUntilNumberOfTabToBe(int tabNumber) {
try {
wait.until(ExpectedConditions.numberOfWindowsToBe(tabNumber));
} catch (WebDriverException e) {
log.error("No tabs with this number");
}
}
public void goToNextTab(int tabNumber) {
waitUntilNumberOfTabToBe(tabNumber);
ArrayList<String> tabs = new ArrayList<>(driver.getWindowHandles());
driver.switchTo().window(tabs.get(tabNumber - 1));
}
public static long getRandomNumber() {
return System.currentTimeMillis();
}
public static char getRandomSymbol() {
Random rand = new Random();
String s = "~`!@#$^&*()_+=-";
return s.charAt(rand.nextInt(s.length()));
}
}

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

Loading…
Cancel
Save