Browse Source

Merge branch 'develop/3.4' into refactoring_test_customer_05

pull/6734/head
nickAS21 4 years ago
parent
commit
34d6b7a035
  1. 4
      application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java
  2. 15
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  3. 3
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  4. 120
      application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
  5. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  6. 133
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  7. 1
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  8. 15
      application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java
  9. 43
      application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java
  10. 36
      application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java
  11. 41
      application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java
  12. 9
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
  13. 6
      application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
  14. 6
      application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java
  15. 60
      application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java
  16. 6
      application/src/main/resources/thingsboard.yml
  17. 3
      application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java
  18. 2
      application/src/test/resources/application-test.properties
  19. 43
      common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
  20. 2
      common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java
  21. 6
      common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java
  22. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  23. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java
  24. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  26. 5
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java
  27. 7
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java
  28. 25
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionCreationResult.java
  29. 13
      common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java
  30. 7
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java
  31. 44
      common/queue/src/test/java/org/thingsboard/server/queue/discovery/QueueKeyTest.java
  32. 14
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  33. 2
      dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java
  34. 4
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  35. 20
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  36. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  37. 23
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  38. 2
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  39. 22
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  40. 28
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  41. 8
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  42. 11
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  43. 4
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java
  44. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  45. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java
  46. 14
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java
  47. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  48. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java
  49. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
  50. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
  51. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
  52. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
  53. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java
  54. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java
  55. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
  56. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  57. 13
      dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java
  58. 6
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
  59. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java
  60. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  61. 45
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCacheKey.java
  62. 33
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java
  63. 26
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java
  64. 33
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java
  65. 35
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java
  66. 35
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java
  67. 71
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  68. 10
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  69. 16
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  70. 4
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  71. 36
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
  72. 16
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java
  73. 434
      dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java
  74. 3
      dao/src/test/resources/application-test.properties
  75. 40
      msa/js-executor/api/jsInvokeMessageProcessor.js
  76. 2
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  77. 20
      pom.xml
  78. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java
  79. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java
  80. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
  81. 6
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java
  82. 2
      transport/coap/src/main/resources/tb-coap-transport.yml
  83. 2
      transport/http/src/main/resources/tb-http-transport.yml
  84. 2
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  85. 2
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  86. 2
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  87. 46
      ui-ngx/src/app/core/api/widget-subscription.ts
  88. 55
      ui-ngx/src/app/core/http/entities-version-control.service.ts
  89. 36
      ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
  90. 2
      ui-ngx/src/app/core/services/script/node-script-test.service.ts
  91. 5
      ui-ngx/src/app/core/services/utils.service.ts
  92. 56
      ui-ngx/src/app/core/utils.ts
  93. 37
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts
  94. 15
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  95. 8
      ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts
  96. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  97. 1
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts
  98. 4
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-header.component.ts
  99. 2
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  100. 18
      ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html

4
application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java

@ -32,7 +32,9 @@ import java.util.Arrays;
"org.thingsboard.server.dao",
"org.thingsboard.server.common.stats",
"org.thingsboard.server.common.transport.config.ssl",
"org.thingsboard.server.cache"})
"org.thingsboard.server.cache",
"org.thingsboard.server.springfox"
})
public class ThingsboardInstallApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";

15
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -181,13 +181,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Autowired
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")
.permitAll()
);
// http.authorizeHttpRequests((authorizeHttpRequests) ->
// authorizeHttpRequests
// .antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")
// .permitAll()
// );
http.headers().cacheControl().and().frameOptions().disable()
.and()
.cors()

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

@ -30,7 +30,7 @@ public class ControllerConstants {
protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " +
"The result is wrapped with PageData object that allows you to iterate over result set using pagination. " +
"See the 'Model' tab of the Response Class for more details. ";
protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ENTITY_VIEW_ID_PARAM_DESCRIPTION = "A string value representing the entity view id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
@ -49,6 +49,7 @@ public class ControllerConstants {
protected static final String RULE_NODE_ID_PARAM_DESCRIPTION = "A string value representing the rule node id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String WIDGET_BUNDLE_ID_PARAM_DESCRIPTION = "A string value representing the widget bundle id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String WIDGET_TYPE_ID_PARAM_DESCRIPTION = "A string value representing the widget type id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String VC_REQUEST_ID_PARAM_DESCRIPTION = "A string value representing the version control request id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RESOURCE_ID_PARAM_DESCRIPTION = "A string value representing the resource id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String SYSTEM_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'SYS_ADMIN' authority.";
protected static final String SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'SYS_ADMIN' or 'TENANT_ADMIN' authority.";

120
application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java

@ -43,7 +43,6 @@ import org.thingsboard.server.common.data.sync.vc.EntityDataDiff;
import org.thingsboard.server.common.data.sync.vc.EntityDataInfo;
import org.thingsboard.server.common.data.sync.vc.EntityVersion;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest;
@ -65,6 +64,7 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VERSI
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.VC_REQUEST_ID_PARAM_DESCRIPTION;
@RestController
@TbCoreComponent
@ -117,14 +117,18 @@ public class EntitiesVersionControlController extends BaseController {
" }\n" +
"}\n```")
@PostMapping("/version")
public DeferredResult<VersionCreationResult> saveEntitiesVersion(@RequestBody VersionCreateRequest request) throws ThingsboardException {
public DeferredResult<UUID> saveEntitiesVersion(@RequestBody VersionCreateRequest request) throws Exception {
SecurityUser user = getCurrentUser();
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE);
return wrapFuture(versionControlService.saveEntitiesVersion(user, request));
} catch (Exception e) {
throw handleException(e);
}
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE);
return wrapFuture(versionControlService.saveEntitiesVersion(user, request));
}
@ApiOperation(value = "", notes = "")
@GetMapping(value = "/version/{requestId}/status")
public VersionCreationResult getVersionCreateRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true)
@PathVariable UUID requestId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return versionControlService.getVersionCreateStatus(getCurrentUser(), requestId);
}
@ApiOperation(value = "", notes = "" +
@ -147,15 +151,11 @@ public class EntitiesVersionControlController extends BaseController {
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp")
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listEntityVersions(getTenantId(), branch, externalEntityId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
@RequestParam(required = false) String sortOrder) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listEntityVersions(getTenantId(), branch, externalEntityId, pageLink));
}
@ApiOperation(value = "", notes = "" +
@ -177,14 +177,10 @@ public class EntitiesVersionControlController extends BaseController {
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp")
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listEntityTypeVersions(getTenantId(), branch, entityType, pageLink));
} catch (Exception e) {
throw handleException(e);
}
@RequestParam(required = false) String sortOrder) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listEntityTypeVersions(getTenantId(), branch, entityType, pageLink));
}
@ApiOperation(value = "", notes = "" +
@ -213,65 +209,45 @@ public class EntitiesVersionControlController extends BaseController {
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp")
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listVersions(getTenantId(), branch, pageLink));
} catch (Exception e) {
throw handleException(e);
}
@RequestParam(required = false) String sortOrder) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return wrapFuture(versionControlService.listVersions(getTenantId(), branch, pageLink));
}
@GetMapping("/entity/{branch}/{entityType}/{versionId}")
public DeferredResult<List<VersionedEntityInfo>> listEntitiesAtVersion(@PathVariable String branch,
@PathVariable EntityType entityType,
@PathVariable String versionId) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), branch, versionId, entityType));
} catch (Exception e) {
throw handleException(e);
}
@PathVariable String versionId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), branch, versionId, entityType));
}
@GetMapping("/entity/{branch}/{versionId}")
public DeferredResult<List<VersionedEntityInfo>> listAllEntitiesAtVersion(@PathVariable String branch,
@PathVariable String versionId) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, versionId));
} catch (Exception e) {
throw handleException(e);
}
@PathVariable String versionId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, versionId));
}
@GetMapping("/info/{versionId}/{entityType}/{externalEntityUuid}")
public DeferredResult<EntityDataInfo> getEntityDataInfo(@PathVariable String versionId,
@PathVariable EntityType entityType,
@PathVariable UUID externalEntityUuid) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
return wrapFuture(versionControlService.getEntityDataInfo(getCurrentUser(), entityId, versionId));
} catch (Exception e) {
throw handleException(e);
}
@PathVariable UUID externalEntityUuid) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid);
return wrapFuture(versionControlService.getEntityDataInfo(getCurrentUser(), entityId, versionId));
}
@GetMapping("/diff/{branch}/{entityType}/{internalEntityUuid}")
public DeferredResult<EntityDataDiff> compareEntityDataToVersion(@PathVariable String branch,
@PathVariable EntityType entityType,
@PathVariable UUID internalEntityUuid,
@RequestParam String versionId) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid);
return wrapFuture(versionControlService.compareEntityDataToVersion(getCurrentUser(), branch, entityId, versionId));
} catch (Exception e) {
throw handleException(e);
}
@RequestParam String versionId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid);
return wrapFuture(versionControlService.compareEntityDataToVersion(getCurrentUser(), branch, entityId, versionId));
}
@ApiOperation(value = "", notes = "" +
@ -307,14 +283,18 @@ public class EntitiesVersionControlController extends BaseController {
" }\n" +
"}\n```")
@PostMapping("/entity")
public DeferredResult<VersionLoadResult> loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws ThingsboardException {
public UUID loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws Exception {
SecurityUser user = getCurrentUser();
try {
accessControlService.checkPermission(user, Resource.VERSION_CONTROL, Operation.READ);
return wrapFuture(versionControlService.loadEntitiesVersion(user, request));
} catch (Exception e) {
throw handleException(e);
}
accessControlService.checkPermission(user, Resource.VERSION_CONTROL, Operation.WRITE);
return versionControlService.loadEntitiesVersion(user, request);
}
@ApiOperation(value = "", notes = "")
@GetMapping(value = "/entity/{requestId}/status")
public VersionLoadResult getVersionLoadRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true)
@PathVariable UUID requestId) throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
return versionControlService.getVersionLoadStatus(getCurrentUser(), requestId);
}

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

@ -378,7 +378,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
}
List<ListenableFuture<Void>> futures = new ArrayList<>();
for (EntityView entityView : entityViews) {
ListenableFuture<Boolean> future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(),
ListenableFuture<Boolean> future = relationService.checkRelationAsync(tenantId, edge.getId(), entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
futures.add(Futures.transformAsync(future, result -> {
if (Boolean.TRUE.equals(result)) {
@ -413,11 +413,11 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
}
private ListenableFuture<Void> saveEdgeEvent(TenantId tenantId,
EdgeId edgeId,
EdgeEventType type,
EdgeEventActionType action,
EntityId entityId,
JsonNode body) {
EdgeId edgeId,
EdgeEventType type,
EdgeEventActionType action,
EntityId entityId,
JsonNode body) {
log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body);

133
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -25,9 +25,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.StringUtils;
@ -73,6 +77,7 @@ import org.thingsboard.server.service.sync.ie.EntitiesExportImportService;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
import org.thingsboard.server.service.sync.ie.importing.impl.MissingEntityException;
import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService;
import org.thingsboard.server.service.sync.vc.data.CommitGitRequest;
import org.thingsboard.server.service.sync.vc.data.ComplexEntitiesExportCtx;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@ -110,6 +115,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
private final ExportableEntitiesService exportableEntitiesService;
private final TbNotificationEntityService entityNotificationService;
private final TransactionTemplate transactionTemplate;
private final TbTransactionalCache<UUID, VersionControlTaskCacheEntry> taskCache;
private ListeningExecutorService executor;
@ -130,23 +136,55 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@SuppressWarnings("UnstableApiUsage")
@Override
public ListenableFuture<VersionCreationResult> saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception {
public ListenableFuture<UUID> saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception {
var pendingCommit = gitServiceQueue.prepareCommit(user, request);
return transformAsync(pendingCommit, commit -> {
List<ListenableFuture<Void>> gitFutures = new ArrayList<>();
switch (request.getType()) {
case SINGLE_ENTITY: {
handleSingleEntityRequest(new SimpleEntitiesExportCtx(user, commit, (SingleEntityVersionCreateRequest) request));
break;
}
case COMPLEX: {
handleComplexRequest(new ComplexEntitiesExportCtx(user, commit, (ComplexVersionCreateRequest) request));
break;
DonAsynchron.withCallback(pendingCommit, commit -> {
cachePut(commit.getTxId(), new VersionCreationResult());
try {
List<ListenableFuture<Void>> gitFutures = new ArrayList<>();
switch (request.getType()) {
case SINGLE_ENTITY: {
handleSingleEntityRequest(new SimpleEntitiesExportCtx(user, commit, (SingleEntityVersionCreateRequest) request));
break;
}
case COMPLEX: {
handleComplexRequest(new ComplexEntitiesExportCtx(user, commit, (ComplexVersionCreateRequest) request));
break;
}
}
var resultFuture = Futures.transformAsync(Futures.allAsList(gitFutures), f -> gitServiceQueue.push(commit), executor);
DonAsynchron.withCallback(resultFuture, result -> cachePut(commit.getTxId(), result), e -> processCommitError(user, request, commit, e), executor);
} catch (Exception e) {
processCommitError(user, request, commit, e);
}
return transformAsync(Futures.allAsList(gitFutures), success -> gitServiceQueue.push(commit), executor);
}, executor);
}, t -> log.debug("[{}] Failed to prepare the commit: {}", user.getId(), request, t));
return transform(pendingCommit, CommitGitRequest::getTxId, MoreExecutors.directExecutor());
}
@Override
public VersionCreationResult getVersionCreateStatus(SecurityUser user, UUID requestId) throws ThingsboardException {
return getStatus(user, requestId, VersionControlTaskCacheEntry::getExportResult);
}
@Override
public VersionLoadResult getVersionLoadStatus(SecurityUser user, UUID requestId) throws ThingsboardException {
return getStatus(user, requestId, VersionControlTaskCacheEntry::getImportResult);
}
private <T> T getStatus(SecurityUser user, UUID requestId, Function<VersionControlTaskCacheEntry, T> getter) throws ThingsboardException {
var cacheEntry = taskCache.get(requestId);
if (cacheEntry == null || cacheEntry.get() == null) {
throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND);
} else {
var entry = cacheEntry.get();
var result = getter.apply(entry);
if (result == null) {
throw new ThingsboardException(ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else {
return result;
}
}
}
private void handleSingleEntityRequest(SimpleEntitiesExportCtx ctx) throws Exception {
@ -214,40 +252,44 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@SuppressWarnings({"UnstableApiUsage", "rawtypes"})
@Override
public ListenableFuture<VersionLoadResult> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception {
public UUID loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception {
EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), user, request.getVersionId());
cachePut(ctx.getRequestId(), VersionLoadResult.empty());
switch (request.getType()) {
case SINGLE_ENTITY: {
SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request;
VersionLoadConfig config = versionLoadRequest.getConfig();
ListenableFuture<EntityExportData> future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId());
return Futures.transform(future, entityData -> doInTemplate(user, request, ctx -> loadSingleEntity(ctx, config, entityData)), executor);
DonAsynchron.withCallback(future,
entityData -> doInTemplate(ctx, request, c -> loadSingleEntity(c, config, entityData)),
e -> processLoadError(ctx, e), executor);
break;
}
case ENTITY_TYPE: {
EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request;
return executor.submit(() -> doInTemplate(user, request, ctx -> loadMultipleEntities(ctx, versionLoadRequest)));
executor.submit(() -> doInTemplate(ctx, request, c -> loadMultipleEntities(c, versionLoadRequest)));
break;
}
default:
throw new IllegalArgumentException("Unsupported version load request");
}
return ctx.getRequestId();
}
private <R> VersionLoadResult doInTemplate(SecurityUser user, VersionLoadRequest request, Function<EntitiesImportCtx, VersionLoadResult> function) {
private <R> VersionLoadResult doInTemplate(EntitiesImportCtx ctx, VersionLoadRequest request, Function<EntitiesImportCtx, VersionLoadResult> function) {
try {
EntitiesImportCtx ctx = new EntitiesImportCtx(user, request.getVersionId());
VersionLoadResult result = transactionTemplate.execute(status -> function.apply(ctx));
try {
for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) {
throwingRunnable.run();
}
} catch (ThingsboardException e) {
throw new RuntimeException(e);
for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) {
throwingRunnable.run();
}
return result;
result.setDone(true);
return cachePut(ctx.getRequestId(), result);
} catch (LoadEntityException e) {
return onError(e.getData(), e.getCause());
return cachePut(ctx.getRequestId(), onError(e.getData(), e.getCause()));
} catch (Exception e) {
log.info("[{}] Failed to process request [{}] due to: ", user.getTenantId(), request, e);
throw e;
log.info("[{}] Failed to process request [{}] due to: ", ctx.getTenantId(), request, e);
return cachePut(ctx.getRequestId(), VersionLoadResult.error(EntityLoadError.runtimeError(e.getMessage())));
}
}
@ -286,19 +328,21 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
sw.startNew("Entities " + entityType.name());
ctx.setSettings(getEntityImportSettings(request, entityType));
importEntities(ctx, entityType);
persistToCache(ctx);
}
sw.startNew("Reimport");
reimport(ctx);
persistToCache(ctx);
sw.startNew("Remove Others");
request.getEntityTypes().keySet().stream()
.filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities())
.sorted(exportImportService.getEntityTypeComparatorForImport().reversed())
.forEach(entityType -> removeOtherEntities(ctx, entityType));
persistToCache(ctx);
sw.startNew("References and Relations");
exportImportService.saveReferencesAndRelations(ctx);
sw.stop();
@ -387,7 +431,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
private VersionLoadResult onError(EntityExportData<?> entityData, Throwable e) {
return analyze(e, entityData).orElseThrow(() -> new RuntimeException(e));
return analyze(e, entityData).orElse(VersionLoadResult.error(EntityLoadError.runtimeError(e.getMessage())));
}
private Optional<VersionLoadResult> analyze(Throwable e, EntityExportData<?> entityData) {
@ -477,7 +521,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
@Override
public ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityId entityId) throws Exception {
public ListenableFuture<UUID> autoCommit(SecurityUser user, EntityId entityId) throws Exception {
var repositorySettings = repositorySettingsService.get(user.getTenantId());
if (repositorySettings == null) {
return Futures.immediateFuture(null);
@ -504,7 +548,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
@Override
public ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityType entityType, List<UUID> entityIds) throws Exception {
public ListenableFuture<UUID> autoCommit(SecurityUser user, EntityType entityType, List<UUID> entityIds) throws Exception {
var repositorySettings = repositorySettingsService.get(user.getTenantId());
if (repositorySettings == null) {
return Futures.immediateFuture(null);
@ -550,5 +594,28 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
}
private void processCommitError(SecurityUser user, VersionCreateRequest request, CommitGitRequest commit, Throwable e) {
log.debug("[{}] Failed to prepare the commit: {}", user.getId(), request, e);
cachePut(commit.getTxId(), new VersionCreationResult(e.getMessage()));
}
private void processLoadError(EntitiesImportCtx ctx, Throwable e) {
log.debug("[{}] Failed to load the commit: {}", ctx.getRequestId(), ctx.getVersionId(), e);
cachePut(ctx.getRequestId(), VersionLoadResult.error(EntityLoadError.runtimeError(e.getMessage())));
}
private void cachePut(UUID requestId, VersionCreationResult result) {
taskCache.put(requestId, VersionControlTaskCacheEntry.newForExport(result));
}
private VersionLoadResult cachePut(UUID requestId, VersionLoadResult result) {
log.debug("[{}] Cache put: {}", requestId, result);
taskCache.put(requestId, VersionControlTaskCacheEntry.newForImport(result));
return result;
}
private void persistToCache(EntitiesImportCtx ctx) {
cachePut(ctx.getRequestId(), VersionLoadResult.success(new ArrayList<>(ctx.getResults().values())));
}
}

1
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -378,6 +378,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
commitResult.setAdded(commitResponse.getAdded());
commitResult.setRemoved(commitResponse.getRemoved());
commitResult.setModified(commitResponse.getModified());
commitResult.setDone(true);
((CommitGitRequest) request).getFuture().set(commitResult);
} else if (vcResponseMsg.hasListBranchesResponse()) {
var listBranchesResponse = vcResponseMsg.getListBranchesResponse();

15
application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java

@ -16,7 +16,9 @@
package org.thingsboard.server.service.sync.vc;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -38,7 +40,9 @@ import java.util.UUID;
public interface EntitiesVersionControlService {
ListenableFuture<VersionCreationResult> saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception;
ListenableFuture<UUID> saveEntitiesVersion(SecurityUser user, VersionCreateRequest request) throws Exception;
VersionCreationResult getVersionCreateStatus(SecurityUser user, UUID requestId) throws ThingsboardException;
ListenableFuture<PageData<EntityVersion>> listEntityVersions(TenantId tenantId, String branch, EntityId externalId, PageLink pageLink) throws Exception;
@ -50,7 +54,9 @@ public interface EntitiesVersionControlService {
ListenableFuture<List<VersionedEntityInfo>> listAllEntitiesAtVersion(TenantId tenantId, String branch, String versionId) throws Exception;
ListenableFuture<VersionLoadResult> loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception;
UUID loadEntitiesVersion(SecurityUser user, VersionLoadRequest request) throws Exception;
VersionLoadResult getVersionLoadStatus(SecurityUser user, UUID requestId) throws ThingsboardException;
ListenableFuture<EntityDataDiff> compareEntityDataToVersion(SecurityUser user, String branch, EntityId entityId, String versionId) throws Exception;
@ -64,9 +70,10 @@ public interface EntitiesVersionControlService {
ListenableFuture<Void> checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws Exception;
ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityId entityId) throws Exception;
ListenableFuture<UUID> autoCommit(SecurityUser user, EntityId entityId) throws Exception;
ListenableFuture<VersionCreationResult> autoCommit(SecurityUser user, EntityType entityType, List<UUID> entityIds) throws Exception;
ListenableFuture<UUID> autoCommit(SecurityUser user, EntityType entityType, List<UUID> entityIds) throws Exception;
ListenableFuture<EntityDataInfo> getEntityDataInfo(SecurityUser user, EntityId entityId, String versionId);
}

43
application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java

@ -0,0 +1,43 @@
/**
* 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.service.sync.vc;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.VersionLoadResult;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class VersionControlTaskCacheEntry implements Serializable {
private static final long serialVersionUID = -7875992200801588119L;
private VersionCreationResult exportResult;
private VersionLoadResult importResult;
public static VersionControlTaskCacheEntry newForExport(VersionCreationResult result) {
return new VersionControlTaskCacheEntry(result, null);
}
public static VersionControlTaskCacheEntry newForImport(VersionLoadResult result) {
return new VersionControlTaskCacheEntry(null, result);
}
}

36
application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java

@ -0,0 +1,36 @@
/**
* 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.service.sync.vc;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.UUID;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("VersionControlTaskCache")
public class VersionControlTaskCaffeineCache extends CaffeineTbTransactionalCache<UUID, VersionControlTaskCacheEntry> {
public VersionControlTaskCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.VERSION_CONTROL_TASK_CACHE);
}
}

41
application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java

@ -0,0 +1,41 @@
/**
* 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.service.sync.vc;
import com.google.protobuf.InvalidProtocolBufferException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.UUID;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("VersionControlTaskCache")
public class VersionControlTaskRedisCache extends RedisTbTransactionalCache<UUID, VersionControlTaskCacheEntry> {
public VersionControlTaskRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.VERSION_CONTROL_TASK_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

9
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java

@ -35,11 +35,13 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@Slf4j
@Data
public class EntitiesImportCtx {
private final UUID requestId;
private final SecurityUser user;
private final String versionId;
@ -57,11 +59,12 @@ public class EntitiesImportCtx {
private EntityImportSettings settings;
private EntityImportResult<?> currentImportResult;
public EntitiesImportCtx(SecurityUser user, String versionId) {
this(user, versionId, null);
public EntitiesImportCtx(UUID requestId, SecurityUser user, String versionId) {
this(requestId, user, versionId, null);
}
public EntitiesImportCtx(SecurityUser user, String versionId, EntityImportSettings settings) {
public EntitiesImportCtx(UUID requestId, SecurityUser user, String versionId, EntityImportSettings settings) {
this.requestId = requestId;
this.user = user;
this.versionId = versionId;
this.settings = settings;

6
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java

@ -32,7 +32,7 @@ import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
@ -50,7 +50,7 @@ public class AlarmsCleanUpService {
@Value("${sql.ttl.alarms.removal_batch_size}")
private Integer removalBatchSize;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final AlarmDao alarmDao;
private final AlarmService alarmService;
private final RelationService relationService;
@ -64,7 +64,7 @@ public class AlarmsCleanUpService {
PageLink removalBatchRequest = new PageLink(removalBatchSize, 0 );
PageData<TenantId> tenantsIds;
do {
tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest);
tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest);
for (TenantId tenantId : tenantsIds.getData()) {
if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
continue;

6
application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.rpc.RpcDao;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -43,7 +43,7 @@ public class RpcCleanUpService {
@Value("${sql.ttl.rpc.enabled}")
private boolean ttlTaskExecutionEnabled;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final PartitionService partitionService;
private final TbTenantProfileCache tenantProfileCache;
private final RpcDao rpcDao;
@ -54,7 +54,7 @@ public class RpcCleanUpService {
PageLink tenantsBatchRequest = new PageLink(10_000, 0);
PageData<TenantId> tenantsIds;
do {
tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest);
tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest);
for (TenantId tenantId : tenantsIds.getData()) {
if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
continue;

60
application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java

@ -0,0 +1,60 @@
/**
* 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.springfox;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.thingsboard.server.queue.util.TbCoreComponent;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
@Component
//TODO: remove after fixing issue https://github.com/springfox/springfox/issues/3462 or after migration from springfox to springdoc
public class SpringfoxHandlerProviderBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}

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

@ -394,6 +394,9 @@ cache:
tenantProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_TENANT_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_TENANT_PROFILES_MAX_SIZE:10000}"
tenants:
timeToLiveInMinutes: "${CACHE_SPECS_TENANTS_TTL:1440}"
maxSize: "${CACHE_SPECS_TENANTS_MAX_SIZE:10000}"
deviceProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}"
@ -421,6 +424,9 @@ cache:
twoFaVerificationCodes:
timeToLiveInMinutes: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_TTL:60}"
maxSize: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_MAX_SIZE:100000}"
versionControlTask:
timeToLiveInMinutes: "${CACHE_SPECS_VERSION_CONTROL_TASK_TTL:5}"
maxSize: "${CACHE_SPECS_VERSION_CONTROL_TASK_MAX_SIZE:100000}"
redis:
# standalone or cluster

3
application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java

@ -82,6 +82,7 @@ import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@ -433,7 +434,7 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest
}
protected <E extends ExportableEntity<I>, I extends EntityId> EntityImportResult<E> importEntity(User user, EntityExportData<E> exportData, EntityImportSettings importSettings) throws Exception {
EntitiesImportCtx ctx = new EntitiesImportCtx(getSecurityUser(user), null, importSettings);
EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), getSecurityUser(user), null, importSettings);
ctx.setFinalImportAttempt(true);
exportData = JacksonUtil.treeToValue(JacksonUtil.valueToTree(exportData), EntityExportData.class);
EntityImportResult<E> importResult = exportImportService.importEntity(ctx, exportData);

2
application/src/test/resources/application-test.properties

@ -55,3 +55,5 @@ queue.rule-engine.queues[2].partitions=2
queue.rule-engine.queues[2].processing-strategy.retries=1
queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0
usage.stats.report.enabled=false

43
common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java

@ -21,25 +21,33 @@ import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.JedisClusterCRC16;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Slf4j
public abstract class RedisTbTransactionalCache<K extends Serializable, V extends Serializable> implements TbTransactionalCache<K, V> {
private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);
static final JedisPool MOCK_POOL = new JedisPool(); //non-null pool required for JedisConnection to trigger closing jedis connection
@Getter
private final String cacheName;
private final RedisConnectionFactory connectionFactory;
private final RedisSerializer<String> keySerializer = new StringRedisSerializer();
private final JedisConnectionFactory connectionFactory;
private final RedisSerializer<String> keySerializer = StringRedisSerializer.UTF_8;
private final RedisSerializer<V> valueSerializer;
private final Expiration evictExpiration;
private final Expiration cacheTtl;
@ -50,15 +58,15 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
TBRedisCacheConfiguration configuration,
RedisSerializer<V> valueSerializer) {
this.cacheName = cacheName;
this.connectionFactory = connectionFactory;
this.connectionFactory = (JedisConnectionFactory) connectionFactory;
this.valueSerializer = valueSerializer;
this.evictExpiration = Expiration.from(configuration.getEvictTtlInMs(), TimeUnit.MILLISECONDS);
if (cacheSpecsMap.getSpecs() != null && cacheSpecsMap.getSpecs().get(cacheName) != null) {
CacheSpecs cacheSpecs = cacheSpecsMap.getSpecs().get(cacheName);
this.cacheTtl = Expiration.from(cacheSpecs.getTimeToLiveInMinutes(), TimeUnit.MINUTES);
} else {
this.cacheTtl = Expiration.persistent();
}
this.cacheTtl = Optional.ofNullable(cacheSpecsMap)
.map(CacheSpecsMap::getSpecs)
.map(x -> x.get(cacheName))
.map(CacheSpecs::getTimeToLiveInMinutes)
.map(t -> Expiration.from(t, TimeUnit.MINUTES))
.orElseGet(Expiration::persistent);
}
@Override
@ -130,8 +138,23 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
return new RedisTbCacheTransaction<>(this, connection);
}
private RedisConnection getConnection(byte[] rawKey) {
if (!connectionFactory.isRedisClusterAware()) {
return connectionFactory.getConnection();
}
RedisConnection connection = connectionFactory.getClusterConnection();
int slotNum = JedisClusterCRC16.getSlot(rawKey);
Jedis jedis = ((JedisClusterConnection) connection).getNativeConnection().getConnectionFromSlot(slotNum);
JedisConnection jedisConnection = new JedisConnection(jedis, MOCK_POOL, jedis.getDB());
jedisConnection.setConvertPipelineAndTxResults(connectionFactory.getConvertPipelineAndTxResults());
return jedisConnection;
}
private RedisConnection watch(byte[][] rawKeysList) {
var connection = connectionFactory.getConnection();
RedisConnection connection = getConnection(rawKeysList[0]);
try {
connection.watch(rawKeysList);
connection.multi();

2
common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java

@ -67,7 +67,7 @@ public class TbCaffeineCacheConfiguration {
//SimpleCacheManager is not a bean (will be wrapped), so call initializeCaches manually
manager.initializeCaches();
return new TransactionAwareCacheManagerProxy(manager);
return manager;
}
private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) {

6
common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java

@ -39,6 +39,12 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
TbCacheTransaction<K, V> newTransactionForKey(K key);
/**
* Note that all keys should be in the same cache slot for redis. You may control the cache slot using '{}' bracers.
* See CLUSTER KEYSLOT command for more details.
* @param keys - list of keys to use
* @return transaction object
*/
TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys);
default V getAndPutInTransaction(K key, Supplier<V> dbCall, boolean cacheNullValue) {

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

@ -72,6 +72,8 @@ public interface EntityViewService {
ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId);
List<EntityView> findEntityViewsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId);
void deleteEntityView(TenantId tenantId, EntityViewId entityViewId);
void deleteEntityViewsByTenantId(TenantId tenantId);

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

@ -35,7 +35,9 @@ import java.util.List;
*/
public interface RelationService {
ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);

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

@ -35,6 +35,8 @@ public interface TenantService {
Tenant saveTenant(Tenant tenant);
boolean tenantExists(TenantId tenantId);
void deleteTenant(TenantId tenantId);
PageData<Tenant> findTenants(PageLink pageLink);
@ -44,4 +46,6 @@ public interface TenantService {
List<TenantId> findTenantIdsByTenantProfileId(TenantProfileId tenantProfileId);
void deleteTenants();
PageData<TenantId> findTenantsIds(PageLink pageLink);
}

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

@ -26,6 +26,7 @@ public class CacheConstants {
public static final String CLAIM_DEVICES_CACHE = "claimDevices";
public static final String SECURITY_SETTINGS_CACHE = "securitySettings";
public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
public static final String TENANTS_CACHE = "tenants";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";
public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
@ -34,4 +35,5 @@ public class CacheConstants {
public static final String REPOSITORY_SETTINGS_CACHE = "repositorySettings";
public static final String AUTO_COMMIT_SETTINGS_CACHE = "autoCommitSettings";
public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes";
public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask";
}

5
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java

@ -30,6 +30,7 @@ public class EntityLoadError {
private String type;
private EntityId source;
private EntityId target;
private String message;
public static EntityLoadError credentialsError(EntityId sourceId) {
return EntityLoadError.builder().type("DEVICE_CREDENTIALS_CONFLICT").source(sourceId).build();
@ -39,4 +40,8 @@ public class EntityLoadError {
return EntityLoadError.builder().type("MISSING_REFERENCED_ENTITY").source(sourceId).target(targetId).build();
}
public static EntityLoadError runtimeError(String msg) {
return EntityLoadError.builder().type("RUNTIME").message(msg).build();
}
}

7
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java

@ -19,10 +19,15 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EntityVersion {
public class EntityVersion implements Serializable {
private static final long serialVersionUID = -3705022663019175258L;
private long timestamp;
private String id;
private String name;

25
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionCreationResult.java

@ -16,11 +16,34 @@
package org.thingsboard.server.common.data.sync.vc;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
public class VersionCreationResult {
@NoArgsConstructor
public class VersionCreationResult implements Serializable {
private static final long serialVersionUID = 8032189124530267838L;
private EntityVersion version;
private int added;
private int modified;
private int removed;
private String error;
private boolean done;
public VersionCreationResult(EntityVersion version, int added, int modified, int removed) {
this.version = version;
this.added = added;
this.modified = modified;
this.removed = removed;
this.done = true;
}
public VersionCreationResult(String error) {
this.error = error;
this.done = true;
}
}

13
common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java

@ -19,15 +19,24 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VersionLoadResult {
public class VersionLoadResult implements Serializable {
private static final long serialVersionUID = -1386093599856747449L;
private List<EntityTypeLoadResult> result;
private EntityLoadError error;
private boolean done;
public static VersionLoadResult empty() {
return VersionLoadResult.builder().result(Collections.emptyList()).build();
}
public static VersionLoadResult success(List<EntityTypeLoadResult> result) {
return VersionLoadResult.builder().result(result).build();
@ -38,7 +47,7 @@ public class VersionLoadResult {
}
public static VersionLoadResult error(EntityLoadError error) {
return VersionLoadResult.builder().error(error).build();
return VersionLoadResult.builder().error(error).done(true).build();
}
}

7
common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java

@ -53,4 +53,11 @@ public class QueueKey {
this.queueName = MAIN;
this.tenantId = TenantId.SYS_TENANT_ID;
}
@Override
public String toString() {
return "QK(" + queueName + "," + type + "," +
(TenantId.SYS_TENANT_ID.equals(tenantId) ? "system" : tenantId) +
')';
}
}

44
common/queue/src/test/java/org/thingsboard/server/queue/discovery/QueueKeyTest.java

@ -0,0 +1,44 @@
/**
* 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.queue.discovery;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
class QueueKeyTest {
@Test
void testToStringSystemTenant() {
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "Main", TenantId.SYS_TENANT_ID);
log.info("The queue key is {}",queueKey);
assertThat(queueKey.toString()).isEqualTo("QK(Main,TB_RULE_ENGINE,system)");
}
@Test
void testToStringCustomTenant() {
TenantId tenantId = TenantId.fromUUID(UUID.fromString("3ebd39eb-43d4-4911-a818-cdbf8d508f88"));
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "Main", tenantId);
log.info("The queue key is {}",queueKey);
assertThat(queueKey.toString()).isEqualTo("QK(Main,TB_RULE_ENGINE,3ebd39eb-43d4-4911-a818-cdbf8d508f88)");
}
}

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

@ -23,8 +23,6 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
@ -53,7 +51,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
@ -160,14 +157,9 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
deleteEntityRelations(tenantId, assetId);
Asset asset = assetDao.findById(tenantId, assetId.getId());
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get();
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete asset that has entity views!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for assetId [{}]", assetId, e);
throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(asset.getTenantId(), assetId);
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete asset that has entity views!");
}
publishEvictEvent(new AssetCacheEvictEvent(asset.getTenantId(), asset.getName(), null));

2
dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java

@ -34,6 +34,6 @@ public class AttributeCacheKey implements Serializable {
@Override
public String toString() {
return entityId + "_" + scope + "_" + key;
return "{" + entityId + "}" + scope + "_" + key;
}
}

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

@ -31,7 +31,6 @@ import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@ -64,9 +63,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Autowired
private DeviceService deviceService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private DashboardService dashboardService;

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

@ -58,7 +58,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private CustomerDao customerDao;
@Autowired
private EdgeDao edgeDao;
@ -289,16 +289,16 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
private PaginatedRemover<TenantId, DashboardInfo> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardInfo>() {
@Override
protected PageData<DashboardInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
}
@Override
protected PageData<DashboardInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
deleteDashboard(tenantId, new DashboardId(entity.getUuidId()));
}
};
@Override
protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
deleteDashboard(tenantId, new DashboardId(entity.getUuidId()));
}
};
private class CustomerDashboardsUnassigner extends PaginatedRemover<Customer, DashboardInfo> {

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

@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
@ -47,8 +45,6 @@ import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -76,8 +72,6 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService<Device
@Autowired
private QueueService queueService;
private final Lock findOrCreateLock = new ReentrantLock();
@TransactionalEventListener(classes = DeviceProfileEvictEvent.class)
@Override
public void handleEvictEvent(DeviceProfileEvictEvent event) {

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

@ -78,7 +78,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
@ -313,14 +312,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
Device device = deviceDao.findById(tenantId, deviceId.getId());
DeviceCacheEvictEvent deviceCacheEvictEvent = new DeviceCacheEvictEvent(device.getTenantId(), device.getId(), device.getName(), null);
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get();
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete device that has entity views!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e);
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), deviceId);
if (entityViews != null && !entityViews.isEmpty()) {
throw new DataValidationException("Can't delete device that has entity views!");
}
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId);
@ -523,14 +517,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
@Override
public Device assignDeviceToTenant(TenantId tenantId, Device device) {
log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device);
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
if (!CollectionUtils.isEmpty(entityViews)) {
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), device.getId());
if (!CollectionUtils.isEmpty(entityViews)) {
throw new DataValidationException("Can't assign device that has entity views to another tenant!");
}
eventService.removeEvents(device.getTenantId(), device.getId());

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

@ -26,8 +26,6 @@ import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;

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

@ -84,20 +84,16 @@ public abstract class AbstractEntityService {
}
protected void checkAssignedEntityViewsToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) {
try {
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId).get();
if (entityViews != null && !entityViews.isEmpty()) {
EntityView entityView = entityViews.get(0);
// TODO: @voba - refactor this blocking operation
Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE).get();
if (relationExists) {
throw new DataValidationException("Can't unassign device/asset from edge that is related to entity view and entity view is assigned to edge!");
}
List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(tenantId, entityId);
if (entityViews != null && !entityViews.isEmpty()) {
EntityView entityView = entityViews.get(0);
Boolean relationExists = relationService.checkRelation(
tenantId, edgeId, entityView.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE
);
if (relationExists) {
throw new DataValidationException("Can't unassign device/asset from edge that is related to entity view and entity view is assigned to edge!");
}
} catch (Exception e) {
log.error("[{}] Exception while finding entity views for entityId [{}]", tenantId, entityId, e);
throw new RuntimeException("Exception while finding entity views for entityId [" + entityId + "]", e);
}
}

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

@ -22,8 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
@ -53,7 +51,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -68,7 +65,6 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityViewCacheKey, EntityViewCacheValue, EntityViewEvictEvent> implements EntityViewService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId ";
public static final String INCORRECT_EDGE_ID = "Incorrect edgeId ";
@ -280,6 +276,17 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
EntityViewCacheValue::getEntityViews, v -> new EntityViewCacheValue(null, v), true));
}
@Override
public List<EntityView> findEntityViewsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId) {
log.trace("Executing findEntityViewsByTenantIdAndEntityId, tenantId [{}], entityId [{}]", tenantId, entityId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(entityId.getId(), "Incorrect entityId" + entityId);
return cache.getAndPutInTransaction(EntityViewCacheKey.byEntityId(tenantId, entityId),
() -> entityViewDao.findEntityViewsByTenantIdAndEntityId(tenantId.getId(), entityId.getId()),
EntityViewCacheValue::getEntityViews, v -> new EntityViewCacheValue(null, v), true);
}
@Override
public void deleteEntityView(TenantId tenantId, EntityViewId entityViewId) {
log.trace("Executing deleteEntityView [{}]", entityViewId);
@ -320,15 +327,10 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService<EntityVie
throw new DataValidationException("Can't assign entityView to edge from different tenant!");
}
try {
Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getEntityId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE).get();
if (!relationExists) {
throw new DataValidationException("Can't assign entity view to edge because related device/asset doesn't assigned to edge!");
}
} catch (ExecutionException | InterruptedException e) {
log.error("Exception during relation check", e);
throw new RuntimeException("Exception during relation check", e);
Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getEntityId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
if (!relationExists) {
throw new DataValidationException("Can't assign entity view to edge because related device/asset doesn't assigned to edge!");
}
try {

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

@ -22,8 +22,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.OtaPackage;
@ -42,8 +40,6 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -248,8 +244,4 @@ public class BaseOtaPackageService extends AbstractCachedEntityService<OtaPackag
}
}
private static List<OtaPackageId> toOtaPackageInfoKey(OtaPackageId otaPackageId) {
return Collections.singletonList(otaPackageId);
}
}

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

@ -28,8 +28,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
@ -90,7 +90,14 @@ public class BaseRelationService implements RelationService {
}
@Override
public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
public ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
log.trace("Executing checkRelationAsync [{}][{}][{}][{}]", from, to, relationType, typeGroup);
validate(from, to, relationType, typeGroup);
return relationDao.checkRelationAsync(tenantId, from, to, relationType, typeGroup);
}
@Override
public boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
log.trace("Executing checkRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
validate(from, to, relationType, typeGroup);
return relationDao.checkRelation(tenantId, from, to, relationType, typeGroup);

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

@ -42,7 +42,9 @@ public interface RelationDao {
List<EntityRelation> findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);

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

@ -70,7 +70,9 @@ import java.util.Set;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.TENANT;
import static org.thingsboard.server.dao.service.Validator.*;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
/**
* Created by igor on 3/12/18.

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java

@ -18,18 +18,17 @@ package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
@AllArgsConstructor
public class AlarmDataValidator extends DataValidator<Alarm> {
private final TenantDao tenantDao;
private final TenantService tenantService;
@Override
protected void validateDataImpl(TenantId tenantId, Alarm alarm) {
@ -48,8 +47,7 @@ public class AlarmDataValidator extends DataValidator<Alarm> {
if (alarm.getTenantId() == null) {
throw new DataValidationException("Alarm should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(alarm.getTenantId())) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
}

14
dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java

@ -15,29 +15,29 @@
*/
package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
@AllArgsConstructor
public class ApiUsageDataValidator extends DataValidator<ApiUsageState> {
private final TenantDao tenantDao;
@Lazy
@Autowired
private TenantService tenantService;
@Override
protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) {
if (apiUsageState.getTenantId() == null) {
throw new DataValidationException("ApiUsageState should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId());
if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) {
if (!tenantService.tenantExists(apiUsageState.getTenantId()) && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) {
throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java

@ -21,7 +21,6 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,7 +31,7 @@ import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -43,7 +42,7 @@ public class AssetDataValidator extends DataValidator<Asset> {
private AssetDao assetDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
private CustomerDao customerDao;
@ -82,8 +81,7 @@ public class AssetDataValidator extends DataValidator<Asset> {
if (asset.getTenantId() == null) {
throw new DataValidationException("Asset should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(asset.getTenantId())) {
throw new DataValidationException("Asset is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java

@ -20,18 +20,17 @@ import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Objects;
public abstract class BaseOtaPackageDataValidator<D extends BaseData<?>> extends DataValidator<D> {
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
private DeviceProfileDao deviceProfileDao;
@ -40,8 +39,7 @@ public abstract class BaseOtaPackageDataValidator<D extends BaseData<?>> extends
if (otaPackageInfo.getTenantId() == null) {
throw new DataValidationException("OtaPackage should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(otaPackageInfo.getTenantId())) {
throw new DataValidationException("OtaPackage is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java

@ -21,7 +21,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
@ -29,7 +28,7 @@ import org.thingsboard.server.dao.customer.CustomerServiceImpl;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -40,7 +39,7 @@ public class CustomerDataValidator extends DataValidator<Customer> {
private CustomerDao customerDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -87,8 +86,7 @@ public class CustomerDataValidator extends DataValidator<Customer> {
if (customer.getTenantId() == null) {
throw new DataValidationException("Customer should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, customer.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(customer.getTenantId())) {
throw new DataValidationException("Customer is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java

@ -21,14 +21,13 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
public class DashboardDataValidator extends DataValidator<Dashboard> {
@ -37,7 +36,7 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
private DashboardDao dashboardDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -59,8 +58,7 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
if (dashboard.getTenantId() == null) {
throw new DataValidationException("Dashboard should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, dashboard.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(dashboard.getTenantId())) {
throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java

@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -35,7 +34,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -48,7 +47,7 @@ public class DeviceDataValidator extends DataValidator<Device> {
private DeviceDao deviceDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
private CustomerDao customerDao;
@ -85,8 +84,7 @@ public class DeviceDataValidator extends DataValidator<Device> {
if (device.getTenantId() == null) {
throw new DataValidationException("Device should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(device.getTenantId(), device.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(device.getTenantId())) {
throw new DataValidationException("Device is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java

@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
@ -67,7 +66,7 @@ import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.HashSet;
import java.util.List;
@ -90,7 +89,7 @@ public class DeviceProfileDataValidator extends DataValidator<DeviceProfile> {
@Autowired
private DeviceDao deviceDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
private QueueService queueService;
@ -119,8 +118,7 @@ public class DeviceProfileDataValidator extends DataValidator<DeviceProfile> {
if (deviceProfile.getTenantId() == null) {
throw new DataValidationException("Device profile should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(deviceProfile.getTenantId(), deviceProfile.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(deviceProfile.getTenantId())) {
throw new DataValidationException("Device profile is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java

@ -19,7 +19,6 @@ import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -27,7 +26,7 @@ import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -36,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public class EdgeDataValidator extends DataValidator<Edge> {
private final EdgeDao edgeDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final CustomerDao customerDao;
@Override
@ -65,8 +64,7 @@ public class EdgeDataValidator extends DataValidator<Edge> {
if (edge.getTenantId() == null) {
throw new DataValidationException("Edge should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(edge.getTenantId(), edge.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(edge.getTenantId())) {
throw new DataValidationException("Edge is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java

@ -20,14 +20,13 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entityview.EntityViewDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -36,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
public class EntityViewDataValidator extends DataValidator<EntityView> {
private final EntityViewDao entityViewDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final CustomerDao customerDao;
@Override
@ -69,8 +68,7 @@ public class EntityViewDataValidator extends DataValidator<EntityView> {
if (entityView.getTenantId() == null) {
throw new DataValidationException("Entity view should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, entityView.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(entityView.getTenantId())) {
throw new DataValidationException("Entity view is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -28,7 +27,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.resource.TbResourceDao;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
@ -39,7 +38,7 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
private TbResourceDao resourceDao;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -73,8 +72,7 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
}
if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, resource.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(resource.getTenantId())) {
throw new DataValidationException("Resource is referencing to non-existent tenant!");
}
}

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

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -30,7 +29,7 @@ import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
public class RuleChainDataValidator extends DataValidator<RuleChain> {
@ -43,7 +42,7 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
private RuleChainService ruleChainService;
@Autowired
private TenantDao tenantDao;
private TenantService tenantService;
@Autowired
@Lazy
@ -68,8 +67,7 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
throw new DataValidationException("Rule chain should be assigned to tenant!");
}
Tenant tenant = tenantDao.findById(tenantId, ruleChain.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(ruleChain.getTenantId())) {
throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
}
if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) {

13
dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java

@ -21,7 +21,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,7 +31,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserDao;
import org.thingsboard.server.dao.user.UserService;
@ -46,9 +45,6 @@ public class UserDataValidator extends DataValidator<User> {
@Lazy
private UserService userService;
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@ -56,6 +52,10 @@ public class UserDataValidator extends DataValidator<User> {
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Autowired
@Lazy
private TenantService tenantService;
@Override
protected void validateCreate(TenantId tenantId, User user) {
if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
@ -119,8 +119,7 @@ public class UserDataValidator extends DataValidator<User> {
+ " already present in database!");
}
if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(user.getTenantId())) {
throw new DataValidationException("User is referencing to non-existent tenant!");
}
}

6
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java

@ -27,6 +27,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
@ -35,8 +36,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao;
public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
private final WidgetTypeDao widgetTypeDao;
private final TenantDao tenantDao;
private final WidgetsBundleDao widgetsBundleDao;
private final TenantService tenantService;
@Override
protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
@ -53,8 +54,7 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
widgetTypeDetails.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
}
if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(widgetTypeDetails.getTenantId())) {
throw new DataValidationException("Widget type is referencing to non-existent tenant!");
}
}

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java

@ -18,13 +18,12 @@ package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
@Component
@ -32,7 +31,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao;
public class WidgetsBundleDataValidator extends DataValidator<WidgetsBundle> {
private final WidgetsBundleDao widgetsBundleDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
@Override
protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) {
@ -43,8 +42,7 @@ public class WidgetsBundleDataValidator extends DataValidator<WidgetsBundle> {
widgetsBundle.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
}
if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
Tenant tenant = tenantDao.findById(tenantId, widgetsBundle.getTenantId().getId());
if (tenant == null) {
if (!tenantService.tenantExists(widgetsBundle.getTenantId())) {
throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!");
}
}

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

@ -115,9 +115,14 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
}
@Override
public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
public ListenableFuture<Boolean> checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
return service.submit(() -> checkRelation(tenantId, from, to, relationType, typeGroup));
}
@Override
public boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup);
return service.submit(() -> relationRepository.existsById(key));
return relationRepository.existsById(key);
}
@Override

45
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCacheKey.java

@ -0,0 +1,45 @@
/**
* 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.tenant;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class TenantCacheKey implements Serializable {
private static final long serialVersionUID = -121787454251592384L;
private final TenantId tenantId;
private final TenantCacheKeyPrefix keyPrefix;
public static TenantCacheKey fromId(TenantId tenantId) {
return new TenantCacheKey(tenantId, TenantCacheKeyPrefix.TENANT);
}
public static TenantCacheKey fromIdExists(TenantId tenantId) {
return new TenantCacheKey(tenantId, TenantCacheKeyPrefix.EXISTS);
}
public enum TenantCacheKeyPrefix {
TENANT, EXISTS
}
}

33
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java

@ -0,0 +1,33 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("TenantCache")
public class TenantCaffeineCache extends CaffeineTbTransactionalCache<TenantCacheKey, Tenant> {
public TenantCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.TENANTS_CACHE);
}
}

26
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java

@ -0,0 +1,26 @@
/**
* 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.tenant;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class TenantEvictEvent {
private final TenantId tenantId;
// for exists tenant cache
private final boolean isExistsTenant;
}

33
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java

@ -0,0 +1,33 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("TenantExistsCache")
public class TenantExistsCaffeineCache extends CaffeineTbTransactionalCache<TenantCacheKey, Boolean> {
public TenantExistsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.TENANTS_CACHE);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java

@ -0,0 +1,35 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("TenantExistsCache")
public class TenantExistsRedisCache extends RedisTbTransactionalCache<TenantCacheKey, Boolean> {
public TenantExistsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

35
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantRedisCache.java

@ -0,0 +1,35 @@
/**
* 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.tenant;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.Tenant;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("TenantCache")
public class TenantRedisCache extends RedisTbTransactionalCache<TenantCacheKey, Tenant> {
public TenantRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>());
}
}

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

@ -18,7 +18,11 @@ package org.thingsboard.server.dao.tenant;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
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.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
@ -31,8 +35,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.resource.ResourceService;
@ -52,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
public class TenantServiceImpl extends AbstractEntityService implements TenantService {
public class TenantServiceImpl extends AbstractCachedEntityService<TenantCacheKey, Tenant, TenantEvictEvent> implements TenantService {
private static final String DEFAULT_TENANT_REGION = "Global";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@ -64,6 +67,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
private TenantProfileService tenantProfileService;
@Autowired
@Lazy
private UserService userService;
@Autowired
@ -78,12 +82,10 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private DeviceProfileService deviceProfileService;
@Lazy
@Autowired
private ApiUsageStateService apiUsageStateService;
@Autowired
private EntityViewService entityViewService;
@Autowired
private WidgetsBundleService widgetsBundleService;
@ -111,11 +113,26 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private AdminSettingsService adminSettingsService;
@Autowired
protected TbTransactionalCache<TenantCacheKey, Boolean> existsTenantCache;
@TransactionalEventListener(classes = TenantEvictEvent.class)
@Override
public void handleEvictEvent(TenantEvictEvent event) {
TenantId tenantId = event.getTenantId();
cache.evict(TenantCacheKey.fromId(tenantId));
if (event.isExistsTenant()) {
existsTenantCache.evict(TenantCacheKey.fromIdExists(tenantId));
}
}
@Override
public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return tenantDao.findById(tenantId, tenantId.getId());
return cache.getAndPutInTransaction(TenantCacheKey.fromId(tenantId),
() -> tenantDao.findById(tenantId, tenantId.getId()), true);
}
@Override
@ -127,12 +144,13 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Override
public ListenableFuture<Tenant> findTenantByIdAsync(TenantId callerId, TenantId tenantId) {
log.trace("Executing TenantIdAsync [{}]", tenantId);
log.trace("Executing findTenantByIdAsync [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return tenantDao.findByIdAsync(callerId, tenantId.getId());
}
@Override
@Transactional
public Tenant saveTenant(Tenant tenant) {
log.trace("Executing saveTenant [{}]", tenant);
tenant.setRegion(DEFAULT_TENANT_REGION);
@ -142,6 +160,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
}
tenantValidator.validate(tenant, Tenant::getId);
Tenant savedTenant = tenantDao.save(tenant.getId(), tenant);
publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), false));
if (tenant.getId() == null) {
deviceProfileService.createDefaultDeviceProfile(savedTenant.getId());
apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null);
@ -150,6 +169,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
}
@Override
@Transactional(timeout = 60 * 60)
public void deleteTenant(TenantId tenantId) {
log.trace("Executing deleteTenant [{}]", tenantId);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
@ -170,6 +190,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
queueService.deleteQueuesByTenantId(tenantId);
adminSettingsService.deleteAdminSettingsByTenantId(tenantId);
tenantDao.removeById(tenantId, tenantId.getId());
publishEvictEvent(new TenantEvictEvent(tenantId, true));
deleteEntityRelations(tenantId, tenantId);
}
@ -199,17 +220,29 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID);
}
private PaginatedRemover<TenantId, Tenant> tenantsRemover =
new PaginatedRemover<>() {
@Override
public PageData<TenantId> findTenantsIds(PageLink pageLink) {
log.trace("Executing findTenantsIds");
Validator.validatePageLink(pageLink);
return tenantDao.findTenantsIds(pageLink);
}
@Override
protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return tenantDao.findTenants(tenantId, pageLink);
}
@Override
public boolean tenantExists(TenantId tenantId) {
return existsTenantCache.getAndPutInTransaction(TenantCacheKey.fromIdExists(tenantId),
() -> tenantDao.existsById(tenantId, tenantId.getId()), false);
}
@Override
protected void removeEntity(TenantId tenantId, Tenant entity) {
deleteTenant(TenantId.fromUUID(entity.getUuidId()));
}
};
private PaginatedRemover<TenantId, Tenant> tenantsRemover = new PaginatedRemover<>() {
@Override
protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return tenantDao.findTenants(tenantId, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, Tenant entity) {
deleteTenant(TenantId.fromUUID(entity.getUuidId()));
}
};
}

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

@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantProfileDao;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.ArrayList;
@ -52,16 +52,16 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
private final ApiUsageStateDao apiUsageStateDao;
private final TenantProfileDao tenantProfileDao;
private final TenantDao tenantDao;
private final TenantService tenantService;
private final TimeseriesService tsService;
private final DataValidator<ApiUsageState> apiUsageStateValidator;
public ApiUsageStateServiceImpl(ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao,
TenantDao tenantDao, @Lazy TimeseriesService tsService,
TenantService tenantService, @Lazy TimeseriesService tsService,
DataValidator<ApiUsageState> apiUsageStateValidator) {
this.apiUsageStateDao = apiUsageStateDao;
this.tenantProfileDao = tenantProfileDao;
this.tenantDao = tenantDao;
this.tenantService = tenantService;
this.tsService = tsService;
this.apiUsageStateValidator = apiUsageStateValidator;
}
@ -118,7 +118,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) {
tenantId = (TenantId) entityId;
Tenant tenant = tenantDao.findById(tenantId, tenantId.getId());
Tenant tenant = tenantService.findTenantById(tenantId);
TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId());
TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration();

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

@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
@ -50,6 +51,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
@Service
@Slf4j
@RequiredArgsConstructor
public class UserServiceImpl extends AbstractEntityService implements UserService {
public static final String USER_PASSWORD_HISTORY = "userPasswordHistory";
@ -73,20 +75,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
private final DataValidator<UserCredentials> userCredentialsValidator;
private final ApplicationEventPublisher eventPublisher;
public UserServiceImpl(UserDao userDao,
UserCredentialsDao userCredentialsDao,
UserAuthSettingsDao userAuthSettingsDao,
DataValidator<User> userValidator,
DataValidator<UserCredentials> userCredentialsValidator,
ApplicationEventPublisher eventPublisher) {
this.userDao = userDao;
this.userCredentialsDao = userCredentialsDao;
this.userAuthSettingsDao = userAuthSettingsDao;
this.userValidator = userValidator;
this.userCredentialsValidator = userCredentialsValidator;
this.eventPublisher = eventPublisher;
}
@Override
public User findUserByEmail(TenantId tenantId, String email) {
log.trace("Executing findUserByEmail [{}]", email);

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

@ -65,6 +65,7 @@ import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -170,6 +171,9 @@ public abstract class AbstractServiceTest {
@Autowired
protected OtaPackageService otaPackageService;
@Autowired
protected RpcService rpcService;
@Autowired
protected QueueService queueService;

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

@ -97,26 +97,26 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
createFirmware(tenantId, "1");
createAndSaveFirmware(tenantId, "1");
Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
thrown.expect(DataValidationException.class);
thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE));
createFirmware(tenantId, "2");
createAndSaveFirmware(tenantId, "2");
}
@Test
public void sumDataSizeByTenantId() {
Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
createFirmware(tenantId, "0.1");
createAndSaveFirmware(tenantId, "0.1");
Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8;
List<OtaPackage> packages = new ArrayList<>(maxSumDataSize);
for (int i = 2; i <= maxSumDataSize; i++) {
packages.add(createFirmware(tenantId, "0." + i));
packages.add(createAndSaveFirmware(tenantId, "0." + i));
Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId));
}
@ -419,15 +419,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testSaveFirmwareWithExistingTitleAndVersion() {
createFirmware(tenantId, VERSION);
createAndSaveFirmware(tenantId, VERSION);
thrown.expect(DataValidationException.class);
thrown.expectMessage("OtaPackage with such title and version already exists!");
createFirmware(tenantId, VERSION);
createAndSaveFirmware(tenantId, VERSION);
}
@Test
public void testDeleteFirmwareWithReferenceByDevice() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
Device device = new Device();
device.setTenantId(tenantId);
@ -448,7 +448,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testUpdateDeviceProfileId() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
try {
thrown.expect(DataValidationException.class);
@ -493,7 +493,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testFindFirmwareById() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
@ -519,7 +519,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testDeleteFirmware() {
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
@ -532,7 +532,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
public void testFindTenantFirmwaresByTenantId() {
List<OtaPackageInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i));
OtaPackageInfo info = new OtaPackageInfo(createAndSaveFirmware(tenantId, VERSION + i));
info.setHasData(true);
firmwares.add(info);
}
@ -579,7 +579,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
public void testFindTenantFirmwaresByTenantIdAndHasData() {
List<OtaPackageInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i))));
firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createAndSaveFirmware(tenantId, VERSION + i))));
}
OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
@ -695,7 +695,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
otaPackageService.saveOtaPackageInfo(firmwareInfo, true);
}
private OtaPackage createFirmware(TenantId tenantId, String version) {
private OtaPackage createAndSaveFirmware(TenantId tenantId, String version) {
return otaPackageService.saveOtaPackage(createFirmware(tenantId, version, deviceProfileId));
}
public static OtaPackage createFirmware(
TenantId tenantId,
String version,
DeviceProfileId deviceProfileId
) {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
@ -708,6 +716,6 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize(DATA_SIZE);
return otaPackageService.saveOtaPackage(firmware);
return firmware;
}
}

16
dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java

@ -57,13 +57,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(saveRelation(relation));
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON));
}
@Test
@ -80,9 +80,9 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
}
@ -118,9 +118,9 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
relationService.deleteEntityRelations(SYSTEM_TENANT_ID, childId);
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON));
}
@Test

434
dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java

@ -17,26 +17,77 @@ package org.thingsboard.server.dao.service;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.TbCacheValueWrapper;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
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.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.edge.Edge;
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.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TenantCacheKey;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.assertj.core.api.Assertions.assertThat;
public abstract class BaseTenantServiceTest extends AbstractServiceTest {
private IdComparator<Tenant> idComparator = new IdComparator<>();
@SpyBean
protected TenantDao tenantDao;
@Autowired
protected TbTransactionalCache<TenantCacheKey, Tenant> cache;
@Autowired
protected TbTransactionalCache<TenantCacheKey, Boolean> existsTenantCache;
@Test
public void testSaveTenant() {
Tenant tenant = new Tenant();
@ -103,7 +154,6 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest {
@Test
public void testFindTenants() {
List<Tenant> tenants = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<Tenant> pageData = tenantService.findTenants(pageLink);
@ -275,4 +325,382 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest {
tenant.setTenantProfileId(isolatedTenantProfile.getId());
tenantService.saveTenant(tenant);
}
@Test
public void testGettingTenantAddingItToCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Mockito.reset(tenantDao);
verify(tenantDao, Mockito.times(0)).findById(any(), any());
tenantService.findTenantById(savedTenant.getId());
verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
var cachedTenant = cache.get(TenantCacheKey.fromId(savedTenant.getId()));
Assert.assertNotNull("Getting an existing Tenant doesn't add it to the cache!", cachedTenant);
Assert.assertEquals(savedTenant, cachedTenant.get());
for (int i = 0; i < 100; i++) {
tenantService.findTenantById(savedTenant.getId());
}
verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
tenantService.deleteTenant(savedTenant.getId());
}
@Test
public void testExistsTenantAddingResultToCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Mockito.reset(tenantDao);
//fromIdExists invoked from device profile validator
existsTenantCache.evict(TenantCacheKey.fromIdExists(savedTenant.getTenantId()));
verify(tenantDao, Mockito.times(0)).existsById(any(), any());
tenantService.tenantExists(savedTenant.getId());
verify(tenantDao, Mockito.times(1)).existsById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
var isExists = existsTenantCache.get(TenantCacheKey.fromIdExists(savedTenant.getId()));
Assert.assertNotNull("Getting an existing Tenant doesn't add it to the cache!", isExists);
for (int i = 0; i < 100; i++) {
tenantService.tenantExists(savedTenant.getId());
}
verify(tenantDao, Mockito.times(1)).existsById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
tenantService.deleteTenant(savedTenant.getId());
}
@Test
public void testUpdatingExistingTenantEvictCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
tenantService.findTenantById(savedTenant.getId());
var cachedTenant = cache.get(TenantCacheKey.fromId(savedTenant.getId()));
Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedTenant);
Assert.assertEquals(savedTenant, cachedTenant.get());
savedTenant.setTitle("My new tenant");
savedTenant = tenantService.saveTenant(savedTenant);
Mockito.reset(tenantDao);
cachedTenant = cache.get(TenantCacheKey.fromId(savedTenant.getId()));
Assert.assertNull("Updating a Tenant doesn't evict the cache!", cachedTenant);
verify(tenantDao, Mockito.times(0)).findById(any(), any());
tenantService.findTenantById(savedTenant.getId());
verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId()));
tenantService.deleteTenant(savedTenant.getId());
}
@Test
public void testRemovingTenantEvictCache() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
tenantService.findTenantById(savedTenant.getId());
tenantService.tenantExists(savedTenant.getId());
var cachedTenant =
cache.get(TenantCacheKey.fromId(savedTenant.getId()));
var cachedExists =
existsTenantCache.get(TenantCacheKey.fromIdExists(savedTenant.getId()));
Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedTenant);
Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedExists);
tenantService.deleteTenant(savedTenant.getId());
cachedTenant =
cache.get(TenantCacheKey.fromId(savedTenant.getId()));
cachedExists =
existsTenantCache.get(TenantCacheKey.fromIdExists(savedTenant.getId()));
Assert.assertNull("Removing a Tenant doesn't evict the cache!", cachedTenant);
Assert.assertNull("Removing a Tenant doesn't evict the cache!", cachedExists);
}
@Test
public void testDeleteTenantDeletingAllRelatedEntities() throws Exception {
TenantProfile profile = createAndSaveTenantProfile();
Tenant tenant = createAndSaveTenant(profile);
User user = createAndSaveUserFor(tenant);
Customer customer = createAndSaveCustomerFor(tenant);
WidgetsBundle widgetsBundle = createAndSaveWidgetBundleFor(tenant);
DeviceProfile deviceProfile = createAndSaveDeviceProfileWithProfileDataFor(tenant);
Device device = createAndSaveDeviceFor(tenant, customer, deviceProfile);
EntityView entityView = createAndSaveEntityViewFor(tenant, customer, device);
Asset asset = createAndSaveAssetFor(tenant, customer);
Dashboard dashboard = createAndSaveDashboardFor(tenant, customer);
RuleChain ruleChain = createAndSaveRuleChainFor(tenant);
Edge edge = createAndSaveEdgeFor(tenant);
OtaPackage otaPackage = createAndSaveOtaPackageFor(tenant, deviceProfile);
TbResource resource = createAndSaveResourceFor(tenant);
Rpc rpc = createAndSaveRpcFor(tenant, device);
tenantService.deleteTenant(tenant.getId());
Assert.assertNull(tenantService.findTenantById(tenant.getId()));
assertCustomerIsDeleted(tenant, customer);
assertWidgetsBundleIsDeleted(tenant, widgetsBundle);
assertEntityViewIsDeleted(tenant, device, entityView);
assertAssetIsDeleted(tenant, asset);
assertDeviceIsDeleted(tenant, device);
assertDeviceProfileIsDeleted(tenant, deviceProfile);
assertDashboardIsDeleted(tenant, dashboard);
assertEdgeIsDeleted(tenant, edge);
assertTenantAdminIsDeleted(tenant);
assertUserIsDeleted(tenant, user);
Assert.assertNull(ruleChainService.findRuleChainById(tenant.getId(), ruleChain.getId()));
Assert.assertNull(apiUsageStateService.findTenantApiUsageState(tenant.getId()));
assertResourceIsDeleted(tenant, resource);
assertOtaPackageIsDeleted(tenant, otaPackage);
Assert.assertNull(rpcService.findById(tenant.getId(), rpc.getId()));
tenantProfileService.deleteTenantProfile(TenantId.SYS_TENANT_ID, profile.getId());
}
private void assertOtaPackageIsDeleted(Tenant tenant, OtaPackage otaPackage) {
assertThat(otaPackageService.findOtaPackageById(tenant.getId(), otaPackage.getId()))
.as("otaPackage").isNull();
PageLink pageLinkOta = new PageLink(1);
PageData<OtaPackageInfo> pageDataOta = otaPackageService.findTenantOtaPackagesByTenantId(tenant.getId(), pageLinkOta);
Assert.assertEquals(0, pageDataOta.getTotalElements());
}
private void assertResourceIsDeleted(Tenant tenant, TbResource resource) {
assertThat(resourceService.findResourceById(tenant.getId(), resource.getId()))
.as("resource").isNull();
PageLink pageLinkResources = new PageLink(1);
PageData<TbResourceInfo> tenantResources =
resourceService.findAllTenantResourcesByTenantId(tenant.getId(), pageLinkResources);
Assert.assertEquals(0, tenantResources.getTotalElements());
}
private void assertUserIsDeleted(Tenant tenant, User user) {
assertThat(userService.findUserById(tenant.getId(), user.getId()))
.as("user").isNull();
PageLink pageLinkUsers = new PageLink(1);
PageData<User> users =
userService.findUsersByTenantId(tenant.getId(), pageLinkUsers);
Assert.assertEquals(0, users.getTotalElements());
}
private void assertTenantAdminIsDeleted(Tenant savedTenant) {
PageLink pageLinkTenantAdmins = new PageLink(1);
PageData<User> tenantAdmins =
userService.findTenantAdmins(savedTenant.getId(), pageLinkTenantAdmins);
Assert.assertEquals(0, tenantAdmins.getTotalElements());
}
private void assertEdgeIsDeleted(Tenant tenant, Edge edge) {
assertThat(edgeService.findEdgeById(tenant.getId(), edge.getId()))
.as("edge").isNull();
PageLink pageLinkEdges = new PageLink(1);
PageData<Edge> edges = edgeService.findEdgesByTenantId(tenant.getId(), pageLinkEdges);
Assert.assertEquals(0, edges.getTotalElements());
}
private void assertDashboardIsDeleted(Tenant tenant, Dashboard dashboard) {
assertThat(dashboardService.findDashboardById(tenant.getId(), dashboard.getId()))
.as("dashboard").isNull();
PageLink pageLinkDashboards = new PageLink(1);
PageData<DashboardInfo> dashboards =
dashboardService.findDashboardsByTenantId(tenant.getId(), pageLinkDashboards);
Assert.assertEquals(0, dashboards.getTotalElements());
}
private void assertDeviceProfileIsDeleted(Tenant tenant, DeviceProfile deviceProfile) {
assertThat(deviceProfileService.findDeviceProfileById(tenant.getId(), deviceProfile.getId()))
.as("deviceProfile").isNull();
PageLink pageLinkDeviceProfiles = new PageLink(1);
PageData<DeviceProfile> profiles =
deviceProfileService.findDeviceProfiles(tenant.getId(), pageLinkDeviceProfiles);
Assert.assertEquals(0, profiles.getTotalElements());
}
private void assertDeviceIsDeleted(Tenant tenant, Device device) {
assertThat(deviceService.findDeviceById(tenant.getId(), device.getId()))
.as("device").isNull();
PageLink pageLinkDevices = new PageLink(1);
PageData<Device> devices =
deviceService.findDevicesByTenantId(tenant.getId(), pageLinkDevices);
Assert.assertEquals(0, devices.getTotalElements());
}
private void assertAssetIsDeleted(Tenant tenant, Asset asset) {
assertThat(assetService.findAssetById(tenant.getId(), asset.getId()))
.as("asset").isNull();
PageLink pageLinkAssets = new PageLink(1);
PageData<Asset> assets =
assetService.findAssetsByTenantId(tenant.getId(), pageLinkAssets);
Assert.assertEquals(0, assets.getTotalElements());
}
private void assertEntityViewIsDeleted(Tenant tenant, Device device, EntityView entityView) {
assertThat(entityViewService.findEntityViewById(tenant.getId(), entityView.getId()))
.as("entityView").isNull();
List<EntityView> entityViews =
entityViewService.findEntityViewsByTenantIdAndEntityId(tenant.getId(), device.getId());
Assert.assertTrue(entityViews.isEmpty());
}
private void assertWidgetsBundleIsDeleted(Tenant tenant, WidgetsBundle widgetsBundle) {
assertThat(widgetsBundleService.findWidgetsBundleById(tenant.getId(), widgetsBundle.getId()))
.as("widgetBundle").isNull();
List<WidgetsBundle> widgetsBundlesByTenantId =
widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenant.getId());
Assert.assertTrue(widgetsBundlesByTenantId.isEmpty());
}
private void assertCustomerIsDeleted(Tenant tenant, Customer customer) {
assertThat(customerService.findCustomerById(tenant.getId(), customer.getId()))
.as("customer").isNull();
PageLink pageLinkCustomer = new PageLink(1);
PageData<Customer> pageDataCustomer = customerService
.findCustomersByTenantId(tenant.getId(), pageLinkCustomer);
Assert.assertEquals(0, pageDataCustomer.getTotalElements());
}
private Rpc createAndSaveRpcFor(Tenant tenant, Device device) {
Rpc rpc = new Rpc();
rpc.setTenantId(tenant.getId());
rpc.setDeviceId(device.getId());
rpc.setStatus(RpcStatus.QUEUED);
rpc.setRequest(JacksonUtil.toJsonNode("{}"));
return rpcService.save(rpc);
}
private TbResource createAndSaveResourceFor(Tenant tenant) {
TbResource resource = new TbResource();
resource.setTenantId(tenant.getId());
resource.setTitle("Test resource");
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setFileName("filename.txt");
resource.setResourceKey("Test resource key");
resource.setData("Some super test data");
return resourceService.saveResource(resource);
}
private OtaPackage createAndSaveOtaPackageFor(Tenant tenant, DeviceProfile deviceProfile) {
return otaPackageService.saveOtaPackage(
BaseOtaPackageServiceTest.createFirmware(
tenant.getId(), "2", deviceProfile.getId())
);
}
private Edge createAndSaveEdgeFor(Tenant tenant) {
Edge edge = constructEdge(tenant.getId(), "Test edge", "Simple");
return edgeService.saveEdge(edge);
}
private RuleChain createAndSaveRuleChainFor(Tenant tenant) {
RuleChain ruleChain = new RuleChain();
ruleChain.setTenantId(tenant.getId());
ruleChain.setName("Test rule chain");
ruleChain.setType(RuleChainType.CORE);
return ruleChainService.saveRuleChain(ruleChain);
}
private Dashboard createAndSaveDashboardFor(Tenant tenant, Customer customer) {
Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenant.getId());
dashboard.setTitle("Test dashboard");
dashboard.setAssignedCustomers(Set.of(customer.toShortCustomerInfo()));
return dashboardService.saveDashboard(dashboard);
}
private Asset createAndSaveAssetFor(Tenant tenant, Customer customer) {
Asset asset = new Asset();
asset.setTenantId(tenant.getId());
asset.setCustomerId(customer.getId());
asset.setType("Test asset type");
asset.setName("Test asset type");
asset.setLabel("Test asset type");
return assetService.saveAsset(asset);
}
private EntityView createAndSaveEntityViewFor(Tenant tenant, Customer customer, Device device) {
EntityView entityView = new EntityView();
entityView.setEntityId(device.getId());
entityView.setTenantId(tenant.getId());
entityView.setCustomerId(customer.getId());
entityView.setType("Test type");
entityView.setName("Test entity view");
entityView.setStartTimeMs(0);
entityView.setEndTimeMs(840000);
return entityViewService.saveEntityView(entityView);
}
private Device createAndSaveDeviceFor(Tenant tenant, Customer customer, DeviceProfile deviceProfile) {
Device device = new Device();
device.setCustomerId(customer.getId());
device.setTenantId(tenant.getId());
device.setType("Test type");
device.setName("TestType");
device.setLabel("Test type");
device.setDeviceProfileId(deviceProfile.getId());
return deviceService.saveDevice(device);
}
private DeviceProfile createAndSaveDeviceProfileWithProfileDataFor(Tenant tenant) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setTenantId(tenant.getId());
deviceProfile.setTransportType(DeviceTransportType.MQTT);
deviceProfile.setName("Test device profile");
deviceProfile.setType(DeviceProfileType.DEFAULT);
DeviceProfileData profileData = new DeviceProfileData();
profileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration());
deviceProfile.setProfileData(profileData);
return deviceProfileService.saveDeviceProfile(deviceProfile);
}
private WidgetsBundle createAndSaveWidgetBundleFor(Tenant tenant) {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTenantId(tenant.getId());
widgetsBundle.setTitle("Test widgets bundle");
widgetsBundle.setAlias("TestWidgetsBundle");
widgetsBundle.setDescription("Just a simple widgets bundle");
return widgetsBundleService.saveWidgetsBundle(widgetsBundle);
}
private Customer createAndSaveCustomerFor(Tenant tenant) {
Customer customer = new Customer();
customer.setTitle("Test customer");
customer.setTenantId(tenant.getId());
customer.setEmail("testCustomer@test.com");
return customerService.saveCustomer(customer);
}
private User createAndSaveUserFor(Tenant tenant) {
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("tenantAdmin@test.com");
user.setFirstName("tenantAdmin");
user.setLastName("tenantAdmin");
user.setTenantId(tenant.getId());
return userService.saveUser(user);
}
private Tenant createAndSaveTenant(TenantProfile tenantProfile) {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
tenant.setTenantProfileId(tenantProfile.getId());
return tenantService.saveTenant(tenant);
}
private TenantProfile createAndSaveTenantProfile() {
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Test tenant profile");
return tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile);
}
}

3
dao/src/test/resources/application-test.properties

@ -35,6 +35,9 @@ cache.specs.entityViews.maxSize=100000
cache.specs.claimDevices.timeToLiveInMinutes=1440
cache.specs.claimDevices.maxSize=100000
cache.specs.tenants.timeToLiveInMinutes=1440
cache.specs.tenants.maxSize=100000
cache.specs.securitySettings.timeToLiveInMinutes=1440
cache.specs.securitySettings.maxSize=100000

40
msa/js-executor/api/jsInvokeMessageProcessor.js

@ -32,15 +32,17 @@ const maxActiveScripts = Number(config.get('script.max_active_scripts'));
const slowQueryLogMs = Number(config.get('script.slow_query_log_ms'));
const slowQueryLogBody = config.get('script.slow_query_log_body') === 'true';
const {performance} = require('perf_hooks');
const { performance } = require('node:perf_hooks');
function JsInvokeMessageProcessor(producer) {
this.producer = producer;
this.executor = new JsExecutor(useSandbox);
this.scriptMap = new Map();
this.scriptIds = [];
this.executedScriptIdsCounter = [];
this.executedScriptsCounter = 0;
this.lastStatTime = performance.now();
this.compilationTime = 0;
}
JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function (message) {
@ -125,7 +127,8 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function (requestId, r
const msSinceLastStat = nowMs - this.lastStatTime;
const requestsPerSec = msSinceLastStat == 0 ? statFrequency : statFrequency / msSinceLastStat * 1000;
this.lastStatTime = nowMs;
logger.info('STAT[%s]: requests [%s], took [%s]ms, request/s [%s]', this.executedScriptsCounter, statFrequency, msSinceLastStat, requestsPerSec);
logger.info('STAT[%s]: requests [%s], took [%s]ms, request/s [%s], compilation [%s]ms', this.executedScriptsCounter, statFrequency, msSinceLastStat, requestsPerSec, this.compilationTime);
this.compilationTime = 0;
}
if (this.executedScriptsCounter % scriptBodyTraceFrequency == 0) {
@ -167,6 +170,7 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function (requestId,
var index = this.scriptIds.indexOf(scriptId);
if (index > -1) {
this.scriptIds.splice(index, 1);
this.executedScriptIdsCounter.splice(index, 1);
}
this.scriptMap.delete(scriptId);
}
@ -198,14 +202,18 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scri
return new Promise(function (resolve, reject) {
const script = self.scriptMap.get(scriptId);
if (script) {
incrementUseScriptId.call(self, scriptId);
resolve(script);
} else {
const startTime = performance.now();
self.executor.compileScript(scriptBody).then(
(compiledScript) => {
self.compilationTime += (performance.now() - startTime);
self.cacheScript(scriptId, compiledScript);
resolve(compiledScript);
},
(err) => {
self.compilationTime += (performance.now() - startTime);
reject(err);
}
);
@ -216,11 +224,10 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scri
JsInvokeMessageProcessor.prototype.cacheScript = function (scriptId, script) {
if (!this.scriptMap.has(scriptId)) {
this.scriptIds.push(scriptId);
this.executedScriptIdsCounter.push(0);
while (this.scriptIds.length > maxActiveScripts) {
logger.info('Active scripts count [%s] exceeds maximum limit [%s]', this.scriptIds.length, maxActiveScripts);
const prevScriptId = this.scriptIds.shift();
logger.info('Removing active script with id [%s]', prevScriptId);
this.scriptMap.delete(prevScriptId);
deleteMinUsedScript.apply(this);
}
}
this.scriptMap.set(scriptId, script);
@ -291,4 +298,27 @@ function getScriptId(request) {
return Utils.toUUIDString(request.scriptIdMSB, request.scriptIdLSB);
}
function incrementUseScriptId(scriptId) {
const index = this.scriptIds.indexOf(scriptId);
if (this.executedScriptIdsCounter[index] < Number.MAX_SAFE_INTEGER) {
this.executedScriptIdsCounter[index]++;
}
}
function deleteMinUsedScript() {
let min = Infinity;
let minIndex = 0;
const scriptIdsLength = this.executedScriptIdsCounter.length - 1; // ignored last added script
for (let i = 0; i < scriptIdsLength; i++) {
if (this.executedScriptIdsCounter[i] < min) {
min = this.executedScriptIdsCounter[i];
minIndex = i;
}
}
const prevScriptId = this.scriptIds.splice(minIndex, 1)[0];
this.executedScriptIdsCounter.splice(minIndex, 1)
logger.info('Removing active script with id [%s]', prevScriptId);
this.scriptMap.delete(prevScriptId);
}
module.exports = JsInvokeMessageProcessor;

2
msa/vc-executor/src/main/resources/tb-vc-executor.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

20
pom.xml

@ -39,12 +39,12 @@
<javax-annotation.version>1.3.2</javax-annotation.version>
<jakarta.xml.bind-api.version>2.3.2</jakarta.xml.bind-api.version>
<jaxb-runtime.version>2.3.2</jaxb-runtime.version>
<spring-boot.version>2.5.12</spring-boot.version>
<spring-data.version>2.5.10</spring-data.version>
<spring.version>5.3.18</spring.version>
<spring-redis.version>5.5.10</spring-redis.version>
<spring-security.version>5.6.2</spring-security.version>
<spring-data-redis.version>2.5.10</spring-data-redis.version>
<spring-boot.version>2.7.0</spring-boot.version>
<spring-data.version>2.7.0</spring-data.version>
<spring.version>5.3.20</spring.version>
<spring-redis.version>5.5.12</spring-redis.version>
<spring-security.version>5.7.1</spring-security.version>
<spring-data-redis.version>2.7.0</spring-data-redis.version>
<jedis.version>3.7.1</jedis.version>
<jjwt.version>0.7.0</jjwt.version>
<slf4j.version>1.7.32</slf4j.version>
@ -70,7 +70,7 @@
<json-schema-validator.version>2.2.6</json-schema-validator.version>
<californium.version>3.0.0</californium.version>
<leshan.version>2.0.0-M5</leshan.version>
<gson.version>2.6.2</gson.version>
<gson.version>2.9.0</gson.version>
<freemarker.version>2.3.30</freemarker.version>
<mail.version>1.6.2</mail.version>
<curator.version>4.2.0</curator.version>
@ -112,21 +112,21 @@
<ua-parser.version>1.4.3</ua-parser.version>
<commons-beanutils.version>1.9.4</commons-beanutils.version>
<commons-collections.version>3.2.2</commons-collections.version>
<micrometer.version>1.8.3</micrometer.version>
<micrometer.version>1.9.0</micrometer.version>
<protobuf-dynamic.version>1.0.3TB</protobuf-dynamic.version>
<wire-schema.version>3.4.0</wire-schema.version>
<twilio.version>8.17.0</twilio.version>
<hibernate-validator.version>6.0.20.Final</hibernate-validator.version>
<javax.el.version>3.0.0</javax.el.version>
<javax.validation-api.version>2.0.1.Final</javax.validation-api.version>
<antisamy.version>1.6.4</antisamy.version>
<antisamy.version>1.6.8</antisamy.version>
<snmp4j.version>2.8.5</snmp4j.version>
<!-- TEST SCOPE -->
<awaitility.version>4.1.0</awaitility.version>
<cassandra-unit.version>4.3.1.0</cassandra-unit.version>
<dbunit.version>2.7.2</dbunit.version>
<java-websocket.version>1.5.2</java-websocket.version>
<jupiter.version>5.7.2</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<jupiter.version>5.8.2</jupiter.version> <!-- keep the same version as spring-boot-starter-test depend on jupiter-->
<json-path.version>2.6.0</json-path.version>
<spring-test-dbunit.version>1.3.0</spring-test-dbunit.version> <!-- 2016 -->
<takari-cpsuite.version>1.2.7</takari-cpsuite.version> <!-- 2015 -->

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java

@ -124,7 +124,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
}
private ListenableFuture<Boolean> checkRelation(TbContext ctx, SearchDirectionIds sdId, String relationType) {
return ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON);
return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON);
}
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java

@ -98,7 +98,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer, String relationType) {
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON),
return Futures.transformAsync(ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON),
result -> {
if (result) {
return processSingleDeleteRelation(ctx, sdId, relationType);

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java

@ -82,7 +82,7 @@ public class TbCheckRelationNode implements TbNode {
to = EntityIdFactory.getByTypeAndId(config.getEntityType(), config.getEntityId());
from = msg.getOriginator();
}
return ctx.getRelationService().checkRelation(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
return ctx.getRelationService().checkRelationAsync(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
}
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {

6
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCreateRelationNodeTest.java

@ -113,7 +113,7 @@ public class TbCreateRelationNodeTest {
metaData.putValue("type", "AssetType");
msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
when(ctx.getRelationService().checkRelation(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(false));
when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON))))
.thenReturn(Futures.immediateFuture(true));
@ -144,7 +144,7 @@ public class TbCreateRelationNodeTest {
when(ctx.getRelationService().findByToAndTypeAsync(any(), eq(msg.getOriginator()), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(Collections.singletonList(relation)));
when(ctx.getRelationService().deleteRelationAsync(any(), eq(relation))).thenReturn(Futures.immediateFuture(true));
when(ctx.getRelationService().checkRelation(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(false));
when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON))))
.thenReturn(Futures.immediateFuture(true));
@ -171,7 +171,7 @@ public class TbCreateRelationNodeTest {
metaData.putValue("type", "AssetType");
msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
when(ctx.getRelationService().checkRelation(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
when(ctx.getRelationService().checkRelationAsync(any(), eq(assetId), eq(deviceId), eq(RELATION_TYPE_CONTAINS), eq(RelationTypeGroup.COMMON)))
.thenReturn(Futures.immediateFuture(false));
when(ctx.getRelationService().saveRelationAsync(any(), eq(new EntityRelation(assetId, deviceId, RELATION_TYPE_CONTAINS, RelationTypeGroup.COMMON))))
.thenReturn(Futures.immediateFuture(true));

2
transport/coap/src/main/resources/tb-coap-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/http/src/main/resources/tb-http-transport.yml

@ -14,6 +14,8 @@
# limitations under the License.
#
spring.main.allow-circular-references: "true"
server:
# Server bind address
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

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

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

2
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -19,6 +19,8 @@ spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}"
# If you enabled process metrics you should set 'web-application-type' to 'servlet' value.
spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}"
spring.main.allow-circular-references: "true"
server:
# Server bind address (has no effect if web-environment is disabled).
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"

46
ui-ngx/src/app/core/api/widget-subscription.ts

@ -53,12 +53,14 @@ import { forkJoin, Observable, of, ReplaySubject, Subject, throwError, timer } f
import { CancelAnimationFrame } from '@core/services/raf.service';
import { EntityType } from '@shared/models/entity-type.models';
import {
createLabelFromDatasource, createLabelFromPattern,
deepClone, flatFormattedData,
createLabelFromPattern,
deepClone,
flatFormattedData,
formattedDataFormDatasourceData,
isDefined,
isDefinedAndNotNull,
isEqual
isEqual,
parseHttpErrorMessage
} from '@core/utils';
import { EntityId } from '@app/shared/models/id/entity-id';
import * as moment_ from 'moment';
@ -801,10 +803,10 @@ export class WidgetSubscription implements IWidgetSubscription {
this.rpcErrorText = 'Request Timeout.';
} else {
this.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
const error = this.extractRejectionErrorText(rejection);
const error = parseHttpErrorMessage(rejection, this.ctx.translate);
if (error) {
this.rpcErrorText += '</br>';
this.rpcErrorText += error;
this.rpcErrorText += error.message;
}
}
this.callbacks.onRpcFailed(this);
@ -816,40 +818,6 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
private extractRejectionErrorText(rejection: HttpErrorResponse) {
let error = null;
if (rejection.error) {
error = rejection.error;
try {
error = rejection.error ? JSON.parse(rejection.error) : null;
} catch (e) {}
}
if (error && !error.message) {
error = this.prepareMessageFromData(error);
} else if (error && error.message) {
error = error.message;
}
return error;
}
private prepareMessageFromData(data) {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}
update(isTimewindowTypeChanged = false) {
if (this.type !== widgetType.rpc) {
this.widgetTimewindowChangedSubject.next(this.timeWindowConfig);

55
ui-ngx/src/app/core/http/entities-version-control.service.ts

@ -17,13 +17,19 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable, of } from 'rxjs';
import { Observable, of, timer } from 'rxjs';
import {
BranchInfo, EntityDataDiff, EntityDataInfo, EntityLoadError, entityLoadErrorTranslationMap, EntityLoadErrorType,
BranchInfo,
EntityDataDiff,
EntityDataInfo,
EntityLoadError,
entityLoadErrorTranslationMap,
EntityLoadErrorType,
EntityVersion,
VersionCreateRequest,
VersionCreationResult,
VersionLoadRequest, VersionLoadResult
VersionLoadRequest,
VersionLoadResult
} from '@shared/models/vc.models';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
@ -32,9 +38,11 @@ import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.m
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { selectIsUserLoaded } from '@core/auth/auth.selectors';
import { catchError, tap } from 'rxjs/operators';
import { catchError, finalize, map, switchMap, takeWhile, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActionLoadFinish, ActionLoadStart } from '@core/interceptors/load.actions';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@Injectable({
providedIn: 'root'
@ -83,16 +91,30 @@ export class EntitiesVersionControlService {
}
public saveEntitiesVersion(request: VersionCreateRequest, config?: RequestConfig): Observable<VersionCreationResult> {
return this.http.post<VersionCreationResult>('/api/entities/vc/version', request, defaultHttpOptionsFromConfig(config)).pipe(
tap(() => {
this.store.dispatch(new ActionLoadStart());
return this.http.post<string>('/api/entities/vc/version', request,
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}})).pipe(
switchMap((requestId) => {
return timer(0, 2000).pipe(
switchMap(() => this.getVersionCreateRequestStatus(requestId, config)),
takeWhile((res) => !res.done, true)
);
}),
finalize(() => {
const branch = request.branch;
if (this.branchList && !this.branchList.find(b => b.name === branch)) {
this.branchList = null;
}
})
this.store.dispatch(new ActionLoadFinish());
}),
);
}
private getVersionCreateRequestStatus(requestId: string, config?: RequestConfig): Observable<VersionCreationResult> {
return this.http.get<VersionCreationResult>(`/api/entities/vc/version/${requestId}/status`,
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}}));
}
public listEntityVersions(pageLink: PageLink, branch: string,
externalEntityId: EntityId,
config?: RequestConfig): Observable<PageData<EntityVersion>> {
@ -114,7 +136,24 @@ export class EntitiesVersionControlService {
}
public loadEntitiesVersion(request: VersionLoadRequest, config?: RequestConfig): Observable<VersionLoadResult> {
return this.http.post<VersionLoadResult>('/api/entities/vc/entity', request, defaultHttpOptionsFromConfig(config));
this.store.dispatch(new ActionLoadStart());
return this.http.post<string>('/api/entities/vc/entity', request,
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}})).pipe(
switchMap((requestId) => {
return timer(0, 2000).pipe(
switchMap(() => this.getVersionLoadRequestStatus(requestId, config)),
takeWhile((res) => !res.done, true),
);
}),
finalize(() => {
this.store.dispatch(new ActionLoadFinish());
}),
);
}
private getVersionLoadRequestStatus(requestId: string, config?: RequestConfig): Observable<VersionLoadResult> {
return this.http.get<VersionLoadResult>(`/api/entities/vc/entity/${requestId}/status`,
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}}));
}
public compareEntityDataToVersion(branch: string,

36
ui-ngx/src/app/core/interceptors/global-http-interceptor.ts

@ -29,6 +29,7 @@ import { ActionLoadFinish, ActionLoadStart } from './load.actions';
import { ActionNotificationShow } from '@app/core/notification/notification.actions';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { parseHttpErrorMessage } from '@core/utils';
let tmpHeaders = {};
@ -131,43 +132,12 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
}
if (unhandled && !ignoreErrors) {
let error = null;
if (req.responseType === 'text') {
try {
error = errorResponse.error ? JSON.parse(errorResponse.error) : null;
} catch (e) {}
} else {
error = errorResponse.error;
}
if (error && !error.message) {
this.showError(this.prepareMessageFromData(error));
} else if (error && error.message) {
this.showError(error.message, error.timeout ? error.timeout : 0);
} else {
this.showError('Unhandled error code ' + (error ? error.status : '\'Unknown\''));
}
const errorMessageWithTimeout = parseHttpErrorMessage(errorResponse, this.translate, req.responseType);
this.showError(errorMessageWithTimeout.message, errorMessageWithTimeout.timeout);
}
return throwError(errorResponse);
}
private prepareMessageFromData(data) {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}
private retryRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const thisTimeout = 1000 + Math.random() * 3000;
return of(null).pipe(

2
ui-ngx/src/app/core/services/script/node-script-test.service.ts

@ -85,7 +85,7 @@ export class NodeScriptTestService {
return this.dialog.open<NodeScriptTestDialogComponent, NodeScriptTestDialogData, string>(NodeScriptTestDialogComponent,
{
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog', 'tb-fullscreen-dialog-gt-sm'],
panelClass: ['tb-dialog', 'tb-fullscreen-dialog', 'tb-fullscreen-dialog-gt-xs'],
data: {
msg,
metadata,

5
ui-ngx/src/app/core/services/utils.service.ts

@ -284,6 +284,11 @@ export class UtilsService {
if (!datasource.dataKeys) {
datasource.dataKeys = [];
}
datasource.dataKeys.forEach(dataKey => {
if (isUndefined(dataKey.label)) {
dataKey.label = dataKey.name;
}
});
});
return datasources;
}

56
ui-ngx/src/app/core/utils.ts

@ -21,6 +21,10 @@ import { Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/sha
import { EntityId } from '@shared/models/id/entity-id';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityType, baseDetailsPageByEntityType } from '@shared/models/entity-type.models';
import { HttpErrorResponse } from '@angular/common/http';
import { letterSpacing } from 'html2canvas/dist/types/css/property-descriptors/letter-spacing';
import { TranslateService } from '@ngx-translate/core';
import { serverErrorCodesTranslations } from '@shared/models/constants';
const varsRegex = /\${([^}]*)}/g;
@ -96,7 +100,7 @@ export function isEmptyStr(value: any): boolean {
}
export function isNotEmptyStr(value: any): boolean {
return value !== null && typeof value === 'string' && value.trim().length > 0;
return typeof value === 'string' && value.trim().length > 0;
}
export function isFunction(value: any): boolean {
@ -664,3 +668,53 @@ export function randomAlphanumeric(length: number): string {
export function getEntityDetailsPageURL(id: string, entityType: EntityType): string {
return `${baseDetailsPageByEntityType.get(entityType)}/${id}`;
}
export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
translate: TranslateService, responseType?: string): {message: string, timeout: number} {
let error = null;
let errorMessage: string;
let timeout = 0;
if (responseType === 'text') {
try {
error = errorResponse.error ? JSON.parse(errorResponse.error) : null;
} catch (e) {}
} else {
error = errorResponse.error;
}
if (error && !error.message) {
errorMessage = prepareMessageFromData(error);
} else if (error && error.message) {
errorMessage = error.message;
timeout = error.timeout ? error.timeout : 0;
} else {
errorMessage = `Unhandled error code ${error ? error.status : '\'Unknown\''}`;
}
if (isObject(errorMessage)) {
let errorText = `${errorResponse.status}: `;
let errorKey = null;
if ((errorMessage as any).errorCode) {
errorKey = serverErrorCodesTranslations.get((errorMessage as any).errorCode);
}
errorText += errorKey ? translate.instant(errorKey) : errorResponse.statusText;
errorMessage = errorText;
}
return {message: errorMessage, timeout};
}
function prepareMessageFromData(data): string {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}

37
ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts

@ -35,7 +35,8 @@ import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
export interface AlarmDetailsDialogData {
alarmId: string;
alarmId?: string;
alarm?: AlarmInfo;
allowAcknowledgment: boolean;
allowClear: boolean;
displayDetails: boolean;
@ -48,6 +49,7 @@ export interface AlarmDetailsDialogData {
})
export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDialogComponent, boolean> implements OnInit {
alarmId: string;
alarmFormGroup: FormGroup;
allowAcknowledgment: boolean;
@ -93,12 +95,17 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
}
);
this.loadAlarm();
if (!this.data.alarm) {
this.alarmId = this.data.alarmId;
this.loadAlarm();
} else {
this.alarmId = this.data.alarm?.id?.id;
this.loadAlarmSubject.next(this.data.alarm);
}
}
loadAlarm() {
this.alarmService.getAlarmInfo(this.data.alarmId).subscribe(
this.alarmService.getAlarmInfo(this.alarmId).subscribe(
alarm => this.loadAlarmSubject.next(alarm)
);
}
@ -140,15 +147,25 @@ export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDia
}
acknowledge(): void {
this.alarmService.ackAlarm(this.data.alarmId).subscribe(
() => { this.alarmUpdated = true; this.loadAlarm(); }
);
if (this.alarmId) {
this.alarmService.ackAlarm(this.alarmId).subscribe(
() => {
this.alarmUpdated = true;
this.loadAlarm();
}
);
}
}
clear(): void {
this.alarmService.clearAlarm(this.data.alarmId).subscribe(
() => { this.alarmUpdated = true; this.loadAlarm(); }
);
if (this.alarmId) {
this.alarmService.clearAlarm(this.alarmId).subscribe(
() => {
this.alarmUpdated = true;
this.loadAlarm();
}
);
}
}
}

15
ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts

@ -44,9 +44,15 @@ import {
AlarmDetailsDialogData
} from '@home/components/alarm/alarm-details-dialog.component';
import { DAY, historyInterval } from '@shared/models/time/time.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> {
private authUser = getCurrentAuthUser(this.store);
searchStatus: AlarmSearchStatus;
constructor(private alarmService: AlarmService,
@ -55,7 +61,8 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
private datePipe: DatePipe,
private dialog: MatDialog,
public entityId: EntityId = null,
private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY) {
private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY,
private store: Store<AppState>) {
super();
this.loadDataOnInit = false;
this.tableTitle = '';
@ -114,6 +121,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
}
showAlarmDetails(entity: AlarmInfo) {
const isPermissionWrite = this.authUser.authority !== Authority.CUSTOMER_USER || entity.customerId.id === this.authUser.customerId;
this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean>
(AlarmDetailsDialogComponent,
{
@ -121,8 +129,9 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
alarmId: entity.id.id,
allowAcknowledgment: true,
allowClear: true,
alarm: entity,
allowAcknowledgment: isPermissionWrite,
allowClear: isPermissionWrite,
displayDetails: true
}
}).afterClosed().subscribe(

8
ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts

@ -24,6 +24,8 @@ import { DialogService } from '@core/services/dialog.service';
import { AlarmTableConfig } from './alarm-table-config';
import { AlarmSearchStatus } from '@shared/models/alarm.models';
import { AlarmService } from '@app/core/http/alarm.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Component({
selector: 'tb-alarm-table',
@ -68,7 +70,8 @@ export class AlarmTableComponent implements OnInit {
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialog: MatDialog) {
private dialog: MatDialog,
private store: Store<AppState>) {
}
ngOnInit() {
@ -80,7 +83,8 @@ export class AlarmTableComponent implements OnInit {
this.datePipe,
this.dialog,
this.entityIdValue,
AlarmSearchStatus.ANY
AlarmSearchStatus.ANY,
this.store
);
}

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -235,6 +235,7 @@
[opened]="isEditingWidget || isAddingWidget"
(openedStart)="detailsDrawerOpenedStart()"
(closed)="detailsDrawerClosed()"
disableClose
mode="over"
position="end">
<tb-details-panel *ngIf="!isEditingWidgetClosed" fxFlex

1
ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts

@ -76,6 +76,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
this.searchEnabled = false;
this.addEnabled = false;
this.entitiesDeleteEnabled = false;
this.pageMode = false;
this.headerComponent = EdgeDownlinkTableHeaderComponent;
this.entityTranslations = { noEntities: 'edge.no-downlinks-prompt' };

4
ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-header.component.ts

@ -18,8 +18,8 @@ import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component';
import { EdgeEvent } from "@shared/models/edge.models";
import { EdgeDownlinkTableConfig } from "@home/components/edge/edge-downlink-table-config";
import { EdgeEvent } from '@shared/models/edge.models';
import { EdgeDownlinkTableConfig } from '@home/components/edge/edge-downlink-table-config';
@Component({
selector: 'tb-edge-downlink-table-header',

2
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -329,7 +329,7 @@
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-public-user</mat-label>
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-regular-user</mat-label>
<input matInput type="number" formControlName="maxWsSessionsPerRegularUser">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxWsSessionsPerRegularUser').hasError('min')">
{{ 'tenant-profile.too-small-value-zero' | translate}}

18
ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.html

@ -16,7 +16,7 @@
-->
<section style="min-width: 800px;">
<section *ngIf="!resultMessage">
<section *ngIf="!versionCreateResult$">
<mat-toolbar>
<h2>{{ 'version-control.create-entities-version' | translate }}</h2>
<span fxFlex></span>
@ -69,9 +69,11 @@
</button>
</div>
</section>
<section *ngIf="resultMessage">
<div class="mat-title vc-result-message" [innerHtml]="resultMessage"></div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<section *ngIf="versionCreateResult$">
<section *ngIf="resultMessage">
<div class="mat-title vc-result-message" [innerHtml]="resultMessage"></div>
</section>
<div *ngIf="(versionCreateResult$ | async)?.done || hasError; else progress" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
@ -79,5 +81,13 @@
{{ 'action.close' | translate }}
</button>
</div>
<ng-template #progress>
<section fxLayout="column" fxLayoutAlign="center center">
<div class="mat-title vc-result-message progress">
<span translate>version-control.creating-version</span>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</section>
</ng-template>
</section>
</section>

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

Loading…
Cancel
Save