Browse Source

Merge with origin/master

pull/7371/head
ViacheslavKlimov 4 years ago
parent
commit
a9f776587c
  1. 1
      application/pom.xml
  2. 21
      application/src/main/data/upgrade/3.4.1/schema_update_after.sql
  3. 45
      application/src/main/data/upgrade/3.4.1/schema_update_before.sql
  4. 10
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 44
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  6. 16
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  7. 227
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  8. 25
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  9. 10
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  10. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  11. 11
      application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
  12. 1
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  13. 8
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  14. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  15. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  16. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
  17. 67
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java
  18. 47
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java
  19. 63
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java
  20. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  21. 21
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
  22. 50
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  23. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java
  24. 110
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java
  25. 26
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java
  26. 64
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  27. 162
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java
  28. 33
      application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java
  29. 44
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  30. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  31. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  32. 21
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  33. 27
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  34. 5
      application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
  35. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  36. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  37. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  38. 7
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  39. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java
  40. 43
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java
  41. 1
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
  42. 80
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java
  43. 54
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  44. 11
      application/src/main/resources/thingsboard.yml
  45. 50
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  46. 20
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  47. 45
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  48. 463
      application/src/test/java/org/thingsboard/server/controller/BaseAssetProfileControllerTest.java
  49. 20
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  50. 20
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  51. 28
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  52. 20
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  53. 28
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  54. 20
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  55. 19
      application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
  56. 19
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  57. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AssetProfileControllerSqlTest.java
  58. 2
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  59. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  60. 24
      application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java
  61. 97
      application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
  62. 17
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
  63. 53
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java
  64. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  65. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
  66. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
  67. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  68. 26
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java
  69. 2
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  70. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
  71. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  72. 19
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  73. 11
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
  74. 118
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java
  75. 62
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java
  76. 1
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  77. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java
  78. 4
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  79. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  80. 30
      common/edge-api/src/main/proto/edge.proto
  81. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  82. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  83. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  84. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  85. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  86. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  87. 59
      dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStats.java
  88. 38
      dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStatsSnapshot.java
  89. 33
      dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStats.java
  90. 25
      dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStatsSnapshot.java
  91. 214
      dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java
  92. 33
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  93. 63
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java
  94. 33
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java
  95. 46
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java
  96. 31
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java
  97. 35
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java
  98. 286
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  99. 44
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  100. 7
      dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java

1
application/pom.xml

@ -286,7 +286,6 @@
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>

21
application/src/main/data/upgrade/3.4.1/schema_update_after.sql

@ -0,0 +1,21 @@
--
-- 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.
--
DROP PROCEDURE IF EXISTS update_asset_profiles;
ALTER TABLE asset ALTER COLUMN asset_profile_id SET NOT NULL;
ALTER TABLE asset DROP CONSTRAINT IF EXISTS fk_asset_profile;
ALTER TABLE asset ADD CONSTRAINT fk_asset_profile FOREIGN KEY (asset_profile_id) REFERENCES asset_profile(id);

45
application/src/main/data/upgrade/3.4.1/schema_update_before.sql

@ -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.
--
CREATE TABLE IF NOT EXISTS asset_profile (
id uuid NOT NULL CONSTRAINT asset_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
image varchar(1000000),
description varchar,
search_text varchar(255),
is_default boolean,
tenant_id uuid,
default_rule_chain_id uuid,
default_dashboard_id uuid,
default_queue_name varchar(255),
external_id uuid,
CONSTRAINT asset_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT asset_profile_external_id_unq_key UNIQUE (tenant_id, external_id),
CONSTRAINT fk_default_rule_chain_asset_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
CONSTRAINT fk_default_dashboard_asset_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id)
);
CREATE OR REPLACE PROCEDURE update_asset_profiles()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE asset as a SET asset_profile_id = p.id
FROM
(SELECT id, tenant_id, name from asset_profile) as p
WHERE a.asset_profile_id IS NULL AND p.tenant_id = a.tenant_id AND a.type = p.name;
END;
$$;

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

@ -56,6 +56,7 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -86,6 +87,7 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRpcService;
@ -174,6 +176,10 @@ public class ActorSystemContext {
@Getter
private DeviceService deviceService;
@Autowired
@Getter
private DeviceCredentialsService deviceCredentialsService;
@Autowired
@Getter
private TbTenantProfileCache tenantProfileCache;
@ -182,6 +188,10 @@ public class ActorSystemContext {
@Getter
private TbDeviceProfileCache deviceProfileCache;
@Autowired
@Getter
private TbAssetProfileCache assetProfileCache;
@Autowired
@Getter
private AssetService assetService;

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

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
@ -42,6 +43,8 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
@ -64,6 +67,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -327,7 +331,7 @@ class DefaultTbContext implements TbContext {
public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) {
RuleChainId ruleChainId = null;
String queueName = null;
String queueName = null;
if (device.getDeviceProfileId() != null) {
DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId());
if (deviceProfile == null) {
@ -341,7 +345,18 @@ class DefaultTbContext implements TbContext {
}
public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) {
return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED);
RuleChainId ruleChainId = null;
String queueName = null;
if (asset.getAssetProfileId() != null) {
AssetProfile assetProfile = mainCtx.getAssetProfileCache().find(asset.getAssetProfileId());
if (assetProfile == null) {
log.warn("[{}] Asset profile is null!", asset.getAssetProfileId());
} else {
ruleChainId = assetProfile.getDefaultRuleChainId();
queueName = assetProfile.getDefaultQueueName();
}
}
return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, queueName, ruleChainId);
}
public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) {
@ -356,6 +371,15 @@ class DefaultTbContext implements TbContext {
ruleChainId = deviceProfile.getDefaultRuleChainId();
queueName = deviceProfile.getDefaultQueueName();
}
} else if (EntityType.ASSET.equals(alarm.getOriginator().getEntityType())) {
AssetId assetId = new AssetId(alarm.getOriginator().getId());
AssetProfile assetProfile = mainCtx.getAssetProfileCache().get(getTenantId(), assetId);
if (assetProfile == null) {
log.warn("[{}] Asset profile is null!", assetId);
} else {
ruleChainId = assetProfile.getDefaultRuleChainId();
queueName = assetProfile.getDefaultQueueName();
}
}
return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action, queueName, ruleChainId);
}
@ -478,6 +502,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDeviceService();
}
@Override
public DeviceCredentialsService getDeviceCredentialsService() {
return mainCtx.getDeviceCredentialsService();
}
@Override
public TbClusterService getClusterService() {
return mainCtx.getClusterService();
@ -533,6 +562,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getDeviceProfileCache();
}
@Override
public RuleEngineAssetProfileCache getAssetProfileCache() {
return mainCtx.getAssetProfileCache();
}
@Override
public EdgeService getEdgeService() {
return mainCtx.getEdgeService();
@ -647,9 +681,15 @@ class DefaultTbContext implements TbContext {
mainCtx.getDeviceProfileCache().addListener(getTenantId(), getSelfId(), profileListener, deviceListener);
}
@Override
public void addAssetProfileListeners(Consumer<AssetProfile> profileListener, BiConsumer<AssetId, AssetProfile> assetListener) {
mainCtx.getAssetProfileCache().addListener(getTenantId(), getSelfId(), profileListener, assetListener);
}
@Override
public void removeListeners() {
mainCtx.getDeviceProfileCache().removeListener(getTenantId(), getSelfId());
mainCtx.getAssetProfileCache().removeListener(getTenantId(), getSelfId());
mainCtx.getTenantProfileCache().removeListener(getTenantId(), getSelfId());
}

16
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -41,18 +41,19 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.asset.AssetBulkImportService;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.service.entitiy.asset.TbAssetService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
@ -65,6 +66,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_INFO_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_NAME_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_TYPE_DESCRIPTION;
@ -257,6 +259,8 @@ public class AssetController extends BaseController {
@RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@RequestParam(required = false) String assetProfileId,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@ -268,6 +272,9 @@ public class AssetController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetInfosByTenantIdAndType(tenantId, type, pageLink));
} else if (assetProfileId != null && assetProfileId.length() > 0) {
AssetProfileId profileId = new AssetProfileId(toUUID(assetProfileId));
return checkNotNull(assetService.findAssetInfosByTenantIdAndAssetProfileId(tenantId, profileId, pageLink));
} else {
return checkNotNull(assetService.findAssetInfosByTenantId(tenantId, pageLink));
}
@ -345,6 +352,8 @@ public class AssetController extends BaseController {
@RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@RequestParam(required = false) String assetProfileId,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@ -359,6 +368,9 @@ public class AssetController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
} else if (assetProfileId != null && assetProfileId.length() > 0) {
AssetProfileId profileId = new AssetProfileId(toUUID(assetProfileId));
return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(tenantId, customerId, profileId, pageLink));
} else {
return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
}

227
application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java

@ -0,0 +1,227 @@
/**
* 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.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.asset.profile.TbAssetProfileService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_ID;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_INFO_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@RequiredArgsConstructor
@Slf4j
public class AssetProfileController extends BaseController {
private final TbAssetProfileService tbAssetProfileService;
@ApiOperation(value = "Get Asset Profile (getAssetProfileById)",
notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " +
"The server checks that the asset profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.GET)
@ResponseBody
public AssetProfile getAssetProfileById(
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException {
checkParameter(ASSET_PROFILE_ID, strAssetProfileId);
try {
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
return checkAssetProfileId(assetProfileId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)",
notes = "Fetch the Asset Profile Info object based on the provided Asset Profile Id. "
+ ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfo/{assetProfileId}", method = RequestMethod.GET)
@ResponseBody
public AssetProfileInfo getAssetProfileInfoById(
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException {
checkParameter(ASSET_PROFILE_ID, strAssetProfileId);
try {
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
return new AssetProfileInfo(checkAssetProfileId(assetProfileId, Operation.READ));
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get Default Asset Profile (getDefaultAssetProfileInfo)",
notes = "Fetch the Default Asset Profile Info object. " +
ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfo/default", method = RequestMethod.GET)
@ResponseBody
public AssetProfileInfo getDefaultAssetProfileInfo() throws ThingsboardException {
try {
return checkNotNull(assetProfileService.findDefaultAssetProfileInfo(getTenantId()));
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Create Or Update Asset Profile (saveAssetProfile)",
notes = "Create or update the Asset Profile. When creating asset profile, platform generates asset profile id as " + UUID_WIKI_LINK +
"The newly created asset profile id will be present in the response. " +
"Specify existing asset profile id to update the asset profile. " +
"Referencing non-existing asset profile Id will cause 'Not Found' error. " + NEW_LINE +
"Asset profile name is unique in the scope of tenant. Only one 'default' asset profile may exist in scope of tenant. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Asset Profile entity. " +
TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json",
consumes = "application/json")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile", method = RequestMethod.POST)
@ResponseBody
public AssetProfile saveAssetProfile(
@ApiParam(value = "A JSON value representing the asset profile.")
@RequestBody AssetProfile assetProfile) throws Exception {
assetProfile.setTenantId(getTenantId());
checkEntity(assetProfile.getId(), assetProfile, Resource.ASSET_PROFILE);
return tbAssetProfileService.save(assetProfile, getCurrentUser());
}
@ApiOperation(value = "Delete asset profile (deleteAssetProfile)",
notes = "Deletes the asset profile. Referencing non-existing asset profile Id will cause an error. " +
"Can't delete the asset profile if it is referenced by existing assets." + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteAssetProfile(
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException {
checkParameter(ASSET_PROFILE_ID, strAssetProfileId);
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
AssetProfile assetProfile = checkAssetProfileId(assetProfileId, Operation.DELETE);
tbAssetProfileService.delete(assetProfile, getCurrentUser());
}
@ApiOperation(value = "Make Asset Profile Default (setDefaultAssetProfile)",
notes = "Marks asset profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}/default", method = RequestMethod.POST)
@ResponseBody
public AssetProfile setDefaultAssetProfile(
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException {
checkParameter(ASSET_PROFILE_ID, strAssetProfileId);
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
AssetProfile assetProfile = checkAssetProfileId(assetProfileId, Operation.WRITE);
AssetProfile previousDefaultAssetProfile = assetProfileService.findDefaultAssetProfile(getTenantId());
return tbAssetProfileService.setDefaultAssetProfile(assetProfile, previousDefaultAssetProfile, getCurrentUser());
}
@ApiOperation(value = "Get Asset Profiles (getAssetProfiles)",
notes = "Returns a page of asset profile objects owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<AssetProfile> getAssetProfiles(
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(assetProfileService.findAssetProfiles(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get Asset Profile infos (getAssetProfileInfos)",
notes = "Returns a page of asset profile info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData<AssetProfileInfo> getAssetProfileInfos(
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(assetProfileService.findAssetProfileInfos(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

25
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -57,6 +57,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -65,6 +66,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
@ -96,6 +98,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -132,6 +135,7 @@ import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -190,6 +194,9 @@ public abstract class BaseController {
@Autowired
protected AssetService assetService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected AlarmSubscriptionService alarmService;
@ -265,6 +272,9 @@ public abstract class BaseController {
@Autowired
protected TbDeviceProfileCache deviceProfileCache;
@Autowired
protected TbAssetProfileCache assetProfileCache;
@Autowired(required = false)
protected EdgeService edgeService;
@ -548,6 +558,9 @@ public abstract class BaseController {
case ASSET:
checkAssetId(new AssetId(entityId.getId()), operation);
return;
case ASSET_PROFILE:
checkAssetProfileId(new AssetProfileId(entityId.getId()), operation);
return;
case DASHBOARD:
checkDashboardId(new DashboardId(entityId.getId()), operation);
return;
@ -667,6 +680,18 @@ public abstract class BaseController {
}
}
AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation) throws ThingsboardException {
try {
validateId(assetProfileId, "Incorrect assetProfileId " + assetProfileId);
AssetProfile assetProfile = assetProfileService.findAssetProfileById(getCurrentUser().getTenantId(), assetProfileId);
checkNotNull(assetProfile, "Asset profile with id [" + assetProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile);
return assetProfile;
} catch (Exception e) {
throw handleException(e, false);
}
}
Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException {
try {
validateId(alarmId, "Incorrect alarmId " + alarmId);

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

@ -35,6 +35,8 @@ public class ControllerConstants {
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'";
protected static final String DEVICE_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the device profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String ASSET_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the asset profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String TENANT_PROFILE_ID_PARAM_DESCRIPTION = "A string value representing the tenant profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String TENANT_ID_PARAM_DESCRIPTION = "A string value representing the tenant id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String EDGE_ID_PARAM_DESCRIPTION = "A string value representing the edge id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
@ -75,6 +77,8 @@ public class ControllerConstants {
protected static final String TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the tenant profile name.";
protected static final String RULE_CHAIN_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the rule chain name.";
protected static final String DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the device profile name.";
protected static final String ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the asset profile name.";
protected static final String CUSTOMER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the customer title.";
protected static final String EDGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the edge name.";
protected static final String EVENT_TEXT_SEARCH_DESCRIPTION = "The value is not used in searching.";
@ -92,6 +96,8 @@ public class ControllerConstants {
protected static final String TENANT_PROFILE_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name";
protected static final String TENANT_INFO_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, tenantProfileName, title, email, country, state, city, address, address2, zip, phone, email";
protected static final String DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, transportType, description, isDefault";
protected static final String ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, description, isDefault";
protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status";
protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id";
@ -110,6 +116,8 @@ public class ControllerConstants {
protected static final String RELATION_INFO_DESCRIPTION = "Relation Info is an extension of the default Relation object that contains information about the 'from' and 'to' entity names. ";
protected static final String EDGE_INFO_DESCRIPTION = "Edge Info is an extension of the default Edge object that contains information about the assigned customer name. ";
protected static final String DEVICE_PROFILE_INFO_DESCRIPTION = "Device Profile Info is a lightweight object that includes main information about Device Profile excluding the heavyweight configuration object. ";
protected static final String ASSET_PROFILE_INFO_DESCRIPTION = "Asset Profile Info is a lightweight object that includes main information about Asset Profile. ";
protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)";
protected static final String QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES = "TB-RULE-ENGINE, TB-CORE, TB-TRANSPORT, JS-EXECUTOR";
protected static final String QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name.";
@ -1415,6 +1423,8 @@ public class ControllerConstants {
protected static final String DEVICE_PROFILE_ID = "deviceProfileId";
protected static final String ASSET_PROFILE_ID = "assetProfileId";
protected static final String MODEL_DESCRIPTION = "See the 'Model' tab for more details.";
protected static final String ENTITY_VIEW_DESCRIPTION = "Entity Views limit the degree of exposure of the Device or Asset telemetry and attributes to the Customers. " +
"Every Entity View references exactly one entity (device or asset) and defines telemetry and attribute keys that will be visible to the assigned Customer. " +

2
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -191,7 +191,7 @@ public class DeviceProfileController extends BaseController {
"Specify existing device profile id to update the device profile. " +
"Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE +
"Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device Profile entity. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Device Profile entity. " +
TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json",
consumes = "application/json")

11
application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java

@ -22,8 +22,11 @@ import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService;
@ -40,6 +43,7 @@ import java.util.Optional;
public class AssetBulkImportService extends AbstractBulkImportService<Asset> {
private final AssetService assetService;
private final TbAssetService tbAssetService;
private final AssetProfileService assetProfileService;
@Override
protected void setEntityFields(Asset entity, Map<BulkImportColumnType, String> fields) {
@ -66,6 +70,13 @@ public class AssetBulkImportService extends AbstractBulkImportService<Asset> {
@Override
@SneakyThrows
protected Asset saveEntity(SecurityUser user, Asset entity, Map<BulkImportColumnType, String> fields) {
AssetProfile assetProfile;
if (StringUtils.isNotEmpty(entity.getType())) {
assetProfile = assetProfileService.findOrCreateAssetProfile(entity.getTenantId(), entity.getType());
} else {
assetProfile = assetProfileService.findDefaultAssetProfile(entity.getTenantId());
}
entity.setAssetProfileId(assetProfile.getId());
return tbAssetService.save(entity, user);
}

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

@ -134,6 +134,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case ASSET:
case DEVICE:
case DEVICE_PROFILE:
case ASSET_PROFILE:
case ENTITY_VIEW:
case DASHBOARD:
case RULE_CHAIN:

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

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -38,6 +39,7 @@ import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
import org.thingsboard.server.service.edge.rpc.processor.AdminSettingsEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.AssetProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor;
@ -83,6 +85,9 @@ public class EdgeContextComponent {
@Autowired
private DeviceProfileService deviceProfileService;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private AttributesService attributesService;
@ -113,6 +118,9 @@ public class EdgeContextComponent {
@Autowired
private DeviceProfileEdgeProcessor deviceProfileProcessor;
@Autowired
private AssetProfileEdgeProcessor assetProfileProcessor;
@Autowired
private DeviceEdgeProcessor deviceProcessor;

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

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileAssetsRequestMsg;
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
import org.thingsboard.server.gen.edge.v1.ConnectRequestMsg;
import org.thingsboard.server.gen.edge.v1.ConnectResponseCode;
@ -513,6 +514,8 @@ public final class EdgeGrpcSession implements Closeable {
return ctx.getDeviceProcessor().processDeviceToEdge(edge, edgeEvent, msgType, action);
case DEVICE_PROFILE:
return ctx.getDeviceProfileProcessor().processDeviceProfileToEdge(edgeEvent, msgType, action);
case ASSET_PROFILE:
return ctx.getAssetProfileProcessor().processAssetProfileToEdge(edgeEvent, msgType, action);
case ASSET:
return ctx.getAssetProcessor().processAssetToEdge(edge, edgeEvent, msgType, action);
case ENTITY_VIEW:
@ -634,6 +637,11 @@ public final class EdgeGrpcSession implements Closeable {
result.add(ctx.getEdgeRequestsService().processDeviceProfileDevicesRequestMsg(edge.getTenantId(), edge, deviceProfileDevicesRequestMsg));
}
}
if (uplinkMsg.getAssetProfileAssetsRequestMsgCount() > 0) {
for (AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg : uplinkMsg.getAssetProfileAssetsRequestMsgList()) {
result.add(ctx.getEdgeRequestsService().processAssetProfileAssetsRequestMsg(edge.getTenantId(), edge, assetProfileAssetsRequestMsg));
}
}
if (uplinkMsg.getWidgetBundleTypesRequestMsgCount() > 0) {
for (WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg : uplinkMsg.getWidgetBundleTypesRequestMsgList()) {
result.add(ctx.getEdgeRequestsService().processWidgetBundleTypesRequestMsg(edge.getTenantId(), edge, widgetBundleTypesRequestMsg));

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

@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.service.edge.rpc.fetch.AdminSettingsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.AssetProfilesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.AssetsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.CustomerEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.CustomerUsersEdgeEventFetcher;
@ -47,6 +48,7 @@ public class EdgeSyncCursor {
fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new AssetProfilesEdgeEventFetcher(ctx.getAssetProfileService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
fetchers.add(new CustomerEdgeEventFetcher());

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

@ -42,6 +42,10 @@ public class AssetMsgConstructor {
builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits());
builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits());
}
if (asset.getAssetProfileId() != null) {
builder.setAssetProfileIdMSB(asset.getAssetProfileId().getId().getMostSignificantBits());
builder.setAssetProfileIdLSB(asset.getAssetProfileId().getId().getLeastSignificantBits());
}
if (asset.getAdditionalInfo() != null) {
builder.setAdditionalInfo(JacksonUtil.toString(asset.getAdditionalInfo()));
}

67
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java

@ -0,0 +1,67 @@
/**
* 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.edge.rpc.constructor;
import com.google.protobuf.ByteString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.nio.charset.StandardCharsets;
@Component
@TbCoreComponent
public class AssetProfileMsgConstructor {
@Autowired
private DataDecodingEncodingService dataDecodingEncodingService;
public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) {
AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(assetProfile.getId().getId().getMostSignificantBits())
.setIdLSB(assetProfile.getId().getId().getLeastSignificantBits())
.setName(assetProfile.getName())
.setDefault(assetProfile.isDefault());
if (assetProfile.getDefaultRuleChainId() != null) {
builder.setDefaultRuleChainIdMSB(assetProfile.getDefaultRuleChainId().getId().getMostSignificantBits())
.setDefaultRuleChainIdLSB(assetProfile.getDefaultRuleChainId().getId().getLeastSignificantBits());
}
if (assetProfile.getDefaultQueueName() != null) {
builder.setDefaultQueueName(assetProfile.getDefaultQueueName());
}
if (assetProfile.getDescription() != null) {
builder.setDescription(assetProfile.getDescription());
}
if (assetProfile.getImage() != null) {
builder.setImage(ByteString.copyFrom(assetProfile.getImage().getBytes(StandardCharsets.UTF_8)));
}
return builder.build();
}
public AssetProfileUpdateMsg constructAssetProfileDeleteMsg(AssetProfileId assetProfileId) {
return AssetProfileUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(assetProfileId.getId().getMostSignificantBits())
.setIdLSB(assetProfileId.getId().getLeastSignificantBits()).build();
}
}

47
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java

@ -0,0 +1,47 @@
/**
* 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.edge.rpc.fetch;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.asset.AssetProfileService;
@AllArgsConstructor
@Slf4j
public class AssetProfilesEdgeEventFetcher extends BasePageableEdgeEventFetcher<AssetProfile> {
private final AssetProfileService assetProfileService;
@Override
PageData<AssetProfile> fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
return assetProfileService.findAssetProfiles(tenantId, pageLink);
}
@Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, AssetProfile assetProfile) {
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET_PROFILE,
EdgeEventActionType.ADDED, assetProfile.getId(), null);
}
}

63
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java

@ -0,0 +1,63 @@
/**
* 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.edge.rpc.processor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
@Slf4j
@TbCoreComponent
public class AssetProfileEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg processAssetProfileToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (action) {
case ADDED:
case UPDATED:
AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId);
if (assetProfile != null) {
AssetProfileUpdateMsg assetProfileUpdateMsg =
assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAssetProfileUpdateMsg(assetProfileUpdateMsg)
.build();
}
break;
case DELETED:
AssetProfileUpdateMsg assetProfileUpdateMsg =
assetProfileMsgConstructor.constructAssetProfileDeleteMsg(assetProfileId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addAssetProfileUpdateMsg(assetProfileUpdateMsg)
.build();
break;
}
return downlinkMsg;
}
}

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
@ -61,6 +62,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AlarmMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AssetMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AssetProfileMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.CustomerMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DashboardMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor;
@ -75,6 +77,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.state.DeviceStateService;
@ -100,6 +103,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected TbDeviceProfileCache deviceProfileCache;
@Autowired
protected TbAssetProfileCache assetProfileCache;
@Autowired
protected DashboardService dashboardService;
@ -124,6 +130,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected RelationService relationService;
@ -197,6 +206,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected DeviceProfileMsgConstructor deviceProfileMsgConstructor;
@Autowired
protected AssetProfileMsgConstructor assetProfileMsgConstructor;
@Autowired
protected WidgetsBundleMsgConstructor widgetsBundleMsgConstructor;

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

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AssetId;
@ -44,7 +45,6 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
@ -159,23 +159,26 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
}
private Pair<String, RuleChainId> getDefaultQueueNameAndRuleChainId(TenantId tenantId, EntityId entityId) {
RuleChainId ruleChainId = null;
String queueName = null;
if (EntityType.DEVICE.equals(entityId.getEntityType())) {
DeviceProfile deviceProfile = deviceProfileCache.get(tenantId, new DeviceId(entityId.getId()));
RuleChainId ruleChainId;
String queueName;
if (deviceProfile == null) {
log.warn("[{}] Device profile is null!", entityId);
ruleChainId = null;
queueName = null;
} else {
ruleChainId = deviceProfile.getDefaultRuleChainId();
queueName = deviceProfile.getDefaultQueueName();
}
return new ImmutablePair<>(queueName, ruleChainId);
} else {
return new ImmutablePair<>(null, null);
} else if (EntityType.ASSET.equals(entityId.getEntityType())) {
AssetProfile assetProfile = assetProfileCache.get(tenantId, new AssetId(entityId.getId()));
if (assetProfile == null) {
log.warn("[{}] Asset profile is null!", entityId);
} else {
ruleChainId = assetProfile.getDefaultRuleChainId();
queueName = assetProfile.getDefaultQueueName();
}
}
return new ImmutablePair<>(queueName, ruleChainId);
}
private ListenableFuture<Void> processPostTelemetry(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData metaData) {

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

@ -33,10 +33,13 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
@ -57,6 +60,8 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
@ -64,6 +69,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.gen.edge.v1.AssetProfileAssetsRequestMsg;
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileDevicesRequestMsg;
@ -104,6 +110,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Autowired
private DeviceService deviceService;
@Autowired
private AssetService assetService;
@Lazy
@Autowired
private TbEntityViewService entityViewService;
@ -111,6 +120,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Autowired
private DeviceProfileService deviceProfileService;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private WidgetsBundleService widgetsBundleService;
@ -351,6 +363,44 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
@Override
public ListenableFuture<Void> processAssetProfileAssetsRequestMsg(TenantId tenantId, Edge edge, AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg) {
log.trace("[{}] processAssetProfileAssetsRequestMsg [{}][{}]", tenantId, edge.getName(), assetProfileAssetsRequestMsg);
if (assetProfileAssetsRequestMsg.getAssetProfileIdMSB() == 0 || assetProfileAssetsRequestMsg.getAssetProfileIdLSB() == 0) {
return Futures.immediateFuture(null);
}
AssetProfileId assetProfileId = new AssetProfileId(new UUID(assetProfileAssetsRequestMsg.getAssetProfileIdMSB(), assetProfileAssetsRequestMsg.getAssetProfileIdLSB()));
AssetProfile assetProfileById = assetProfileService.findAssetProfileById(tenantId, assetProfileId);
if (assetProfileById == null) {
return Futures.immediateFuture(null);
}
return syncAssets(tenantId, edge, assetProfileById.getName());
}
private ListenableFuture<Void> syncAssets(TenantId tenantId, Edge edge, String assetType) {
log.trace("[{}] syncAssets [{}][{}]", tenantId, edge.getName(), assetType);
List<ListenableFuture<Void>> futures = new ArrayList<>();
try {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Asset> pageData;
do {
pageData = assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edge.getId(), assetType, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Asset asset : pageData.getData()) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET, EdgeEventActionType.ADDED, asset.getId(), null));
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
} catch (Exception e) {
log.error("Exception during loading edge asset(s) on sync!", e);
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
@Override
public ListenableFuture<Void> processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge,
WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) {

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

@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.sync;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AssetProfileAssetsRequestMsg;
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileDevicesRequestMsg;
@ -41,6 +42,8 @@ public interface EdgeRequestsService {
ListenableFuture<Void> processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg);
ListenableFuture<Void> processAssetProfileAssetsRequestMsg(TenantId tenantId, Edge edge, AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg);
ListenableFuture<Void> processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg);
ListenableFuture<Void> processEntityViewsRequestMsg(TenantId tenantId, Edge edge, EntityViewsRequestMsg entityViewsRequestMsg);

110
application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java

@ -0,0 +1,110 @@
/**
* 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.entitiy.asset.profile;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@Service
@TbCoreComponent
@AllArgsConstructor
@Slf4j
public class DefaultTbAssetProfileService extends AbstractTbEntityService implements TbAssetProfileService {
private final AssetProfileService assetProfileService;
@Override
public AssetProfile save(AssetProfile assetProfile, User user) throws Exception {
ActionType actionType = assetProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = assetProfile.getTenantId();
try {
if (TB_SERVICE_QUEUE.equals(assetProfile.getName())) {
throw new ThingsboardException("Unable to save asset profile with name " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else if (assetProfile.getId() != null) {
AssetProfile foundAssetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfile.getId());
if (foundAssetProfile != null && TB_SERVICE_QUEUE.equals(foundAssetProfile.getName())) {
throw new ThingsboardException("Updating asset profile with name " + TB_SERVICE_QUEUE + " is prohibited!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
AssetProfile savedAssetProfile = checkNotNull(assetProfileService.saveAssetProfile(assetProfile));
autoCommit(user, savedAssetProfile.getId());
tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAssetProfile.getId(),
actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedAssetProfile.getId(),
savedAssetProfile, user, actionType, true, null);
return savedAssetProfile;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), assetProfile, actionType, user, e);
throw e;
}
}
@Override
public void delete(AssetProfile assetProfile, User user) {
AssetProfileId assetProfileId = assetProfile.getId();
TenantId tenantId = assetProfile.getTenantId();
try {
assetProfileService.deleteAssetProfile(tenantId, assetProfileId);
tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetProfileId, ComponentLifecycleEvent.DELETED);
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, assetProfileId, assetProfile,
user, ActionType.DELETED, true, null, assetProfileId.toString());
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), ActionType.DELETED,
user, e, assetProfileId.toString());
throw e;
}
}
@Override
public AssetProfile setDefaultAssetProfile(AssetProfile assetProfile, AssetProfile previousDefaultAssetProfile, User user) throws ThingsboardException {
TenantId tenantId = assetProfile.getTenantId();
AssetProfileId assetProfileId = assetProfile.getId();
try {
if (assetProfileService.setDefaultAssetProfile(tenantId, assetProfileId)) {
if (previousDefaultAssetProfile != null) {
previousDefaultAssetProfile = assetProfileService.findAssetProfileById(tenantId, previousDefaultAssetProfile.getId());
notificationEntityService.logEntityAction(tenantId, previousDefaultAssetProfile.getId(), previousDefaultAssetProfile,
ActionType.UPDATED, user);
}
assetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfileId);
notificationEntityService.logEntityAction(tenantId, assetProfileId, assetProfile, ActionType.UPDATED, user);
}
return assetProfile;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET_PROFILE), ActionType.UPDATED,
user, e, assetProfileId.toString());
throw e;
}
}
}

26
application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.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.service.entitiy.asset.profile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
public interface TbAssetProfileService extends SimpleTbEntityService<AssetProfile> {
AssetProfile setDefaultAssetProfile(AssetProfile assetProfile, AssetProfile previousDefaultAssetProfile, User user) throws ThingsboardException;
}

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

@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThrowingConsumer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
@ -33,6 +32,8 @@ import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
@ -108,9 +109,15 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
@Autowired
private DeviceService deviceService;
@Autowired
private AssetService assetService;
@Autowired
private DeviceProfileService deviceProfileService;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private ApiUsageStateService apiUsageStateService;
@ -601,26 +608,57 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
}
break;
case "3.4.1":
execute(connection -> {
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
runSchemaUpdateScript(connection, "3.4.1");
connection.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004002;");
runSchemaUpdateScript(conn, "3.4.1");
if (isOldSchema(conn, 3004001)) {
try {
conn.createStatement().execute("ALTER TABLE asset ADD COLUMN asset_profile_id uuid");
} catch (Exception e) {
}
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_before.sql");
loadSql(schemaUpdateFile, conn);
log.info("Creating default asset profiles...");
PageLink pageLink = new PageLink(100);
PageData<Tenant> pageData;
do {
pageData = tenantService.findTenants(pageLink);
for (Tenant tenant : pageData.getData()) {
List<EntitySubtype> assetTypes = assetService.findAssetTypesByTenantId(tenant.getId()).get();
try {
assetProfileService.createDefaultAssetProfile(tenant.getId());
} catch (Exception e) {
}
for (EntitySubtype assetType : assetTypes) {
try {
assetProfileService.findOrCreateAssetProfile(tenant.getId(), assetType.getType());
} catch (Exception e) {
}
}
}
pageLink = pageLink.nextPageLink();
} while (pageData.hasNext());
log.info("Updating asset profiles...");
conn.createStatement().execute("call update_asset_profiles()");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_after.sql");
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004002;");
}
log.info("Schema updated.");
});
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
}
private void execute(ThrowingConsumer<Connection> function) {
try (Connection connection = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
function.accept(connection);
} catch (Exception e) {
log.error("Failed to update schema!", e);
}
}
private void runSchemaUpdateScript(Connection connection, String version) throws Exception {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, connection);

162
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java

@ -0,0 +1,162 @@
/**
* 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.profile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Service
@Slf4j
public class DefaultTbAssetProfileCache implements TbAssetProfileCache {
private final Lock assetProfileFetchLock = new ReentrantLock();
private final AssetProfileService assetProfileService;
private final AssetService assetService;
private final ConcurrentMap<AssetProfileId, AssetProfile> assetProfilesMap = new ConcurrentHashMap<>();
private final ConcurrentMap<AssetId, AssetProfileId> assetsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, ConcurrentMap<EntityId, Consumer<AssetProfile>>> profileListeners = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, ConcurrentMap<EntityId, BiConsumer<AssetId, AssetProfile>>> assetProfileListeners = new ConcurrentHashMap<>();
public DefaultTbAssetProfileCache(AssetProfileService assetProfileService, AssetService assetService) {
this.assetProfileService = assetProfileService;
this.assetService = assetService;
}
@Override
public AssetProfile get(TenantId tenantId, AssetProfileId assetProfileId) {
AssetProfile profile = assetProfilesMap.get(assetProfileId);
if (profile == null) {
assetProfileFetchLock.lock();
try {
profile = assetProfilesMap.get(assetProfileId);
if (profile == null) {
profile = assetProfileService.findAssetProfileById(tenantId, assetProfileId);
if (profile != null) {
assetProfilesMap.put(assetProfileId, profile);
log.debug("[{}] Fetch asset profile into cache: {}", profile.getId(), profile);
}
}
} finally {
assetProfileFetchLock.unlock();
}
}
log.trace("[{}] Found asset profile in cache: {}", assetProfileId, profile);
return profile;
}
@Override
public AssetProfile get(TenantId tenantId, AssetId assetId) {
AssetProfileId profileId = assetsMap.get(assetId);
if (profileId == null) {
Asset asset = assetService.findAssetById(tenantId, assetId);
if (asset != null) {
profileId = asset.getAssetProfileId();
assetsMap.put(assetId, profileId);
} else {
return null;
}
}
return get(tenantId, profileId);
}
@Override
public void evict(TenantId tenantId, AssetProfileId profileId) {
AssetProfile oldProfile = assetProfilesMap.remove(profileId);
log.debug("[{}] evict asset profile from cache: {}", profileId, oldProfile);
AssetProfile newProfile = get(tenantId, profileId);
if (newProfile != null) {
notifyProfileListeners(newProfile);
}
}
@Override
public void evict(TenantId tenantId, AssetId assetId) {
AssetProfileId old = assetsMap.remove(assetId);
if (old != null) {
AssetProfile newProfile = get(tenantId, assetId);
if (newProfile == null || !old.equals(newProfile.getId())) {
notifyAssetListeners(tenantId, assetId, newProfile);
}
}
}
@Override
public void addListener(TenantId tenantId, EntityId listenerId,
Consumer<AssetProfile> profileListener,
BiConsumer<AssetId, AssetProfile> assetListener) {
if (profileListener != null) {
profileListeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, profileListener);
}
if (assetListener != null) {
assetProfileListeners.computeIfAbsent(tenantId, id -> new ConcurrentHashMap<>()).put(listenerId, assetListener);
}
}
@Override
public AssetProfile find(AssetProfileId assetProfileId) {
return assetProfileService.findAssetProfileById(TenantId.SYS_TENANT_ID, assetProfileId);
}
@Override
public AssetProfile findOrCreateAssetProfile(TenantId tenantId, String profileName) {
return assetProfileService.findOrCreateAssetProfile(tenantId, profileName);
}
@Override
public void removeListener(TenantId tenantId, EntityId listenerId) {
ConcurrentMap<EntityId, Consumer<AssetProfile>> tenantListeners = profileListeners.get(tenantId);
if (tenantListeners != null) {
tenantListeners.remove(listenerId);
}
ConcurrentMap<EntityId, BiConsumer<AssetId, AssetProfile>> assetListeners = assetProfileListeners.get(tenantId);
if (assetListeners != null) {
assetListeners.remove(listenerId);
}
}
private void notifyProfileListeners(AssetProfile profile) {
ConcurrentMap<EntityId, Consumer<AssetProfile>> tenantListeners = profileListeners.get(profile.getTenantId());
if (tenantListeners != null) {
tenantListeners.forEach((id, listener) -> listener.accept(profile));
}
}
private void notifyAssetListeners(TenantId tenantId, AssetId assetId, AssetProfile profile) {
if (profile != null) {
ConcurrentMap<EntityId, BiConsumer<AssetId, AssetProfile>> tenantListeners = assetProfileListeners.get(tenantId);
if (tenantListeners != null) {
tenantListeners.forEach((id, listener) -> listener.accept(assetId, profile));
}
}
}
}

33
application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.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.service.profile;
import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
public interface TbAssetProfileCache extends RuleEngineAssetProfileCache {
void evict(TenantId tenantId, AssetProfileId id);
void evict(TenantId tenantId, AssetId id);
AssetProfile find(AssetProfileId assetProfileId);
AssetProfile findOrCreateAssetProfile(TenantId tenantId, String assetType);
}

44
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -35,8 +35,11 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
@ -69,6 +72,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import java.util.Set;
@ -108,6 +112,7 @@ public class DefaultTbClusterService implements TbClusterService {
private final NotificationsTopicService notificationsTopicService;
private final DataDecodingEncodingService encodingService;
private final TbDeviceProfileCache deviceProfileCache;
private final TbAssetProfileCache assetProfileCache;
private final GatewayNotificationsService gatewayNotificationsService;
@Override
@ -177,6 +182,10 @@ public class DefaultTbClusterService implements TbClusterService {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())));
} else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
} else if (entityId.getEntityType().equals(EntityType.ASSET)) {
tbMsg = transformMsg(tbMsg, assetProfileCache.get(tenantId, new AssetId(entityId.getId())));
} else if (entityId.getEntityType().equals(EntityType.ASSET_PROFILE)) {
tbMsg = transformMsg(tbMsg, assetProfileCache.get(tenantId, new AssetProfileId(entityId.getId())));
}
}
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId);
@ -193,16 +202,30 @@ public class DefaultTbClusterService implements TbClusterService {
if (deviceProfile != null) {
RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId();
String targetQueueName = deviceProfile.getDefaultQueueName();
boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId());
boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName());
if (isRuleChainTransform && isQueueTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName);
} else if (isRuleChainTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId);
} else if (isQueueTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName);
}
tbMsg = transformMsg(tbMsg, targetRuleChainId, targetQueueName);
}
return tbMsg;
}
private TbMsg transformMsg(TbMsg tbMsg, AssetProfile assetProfile) {
if (assetProfile != null) {
RuleChainId targetRuleChainId = assetProfile.getDefaultRuleChainId();
String targetQueueName = assetProfile.getDefaultQueueName();
tbMsg = transformMsg(tbMsg, targetRuleChainId, targetQueueName);
}
return tbMsg;
}
private TbMsg transformMsg(TbMsg tbMsg, RuleChainId targetRuleChainId, String targetQueueName) {
boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId());
boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName());
if (isRuleChainTransform && isQueueTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName);
} else if (isRuleChainTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId);
} else if (isQueueTransform) {
tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName);
}
return tbMsg;
}
@ -367,6 +390,7 @@ public class DefaultTbClusterService implements TbClusterService {
if (entityType.equals(EntityType.TENANT)
|| entityType.equals(EntityType.TENANT_PROFILE)
|| entityType.equals(EntityType.DEVICE_PROFILE)
|| entityType.equals(EntityType.ASSET_PROFILE)
|| entityType.equals(EntityType.API_USAGE_STATE)
|| (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)
|| entityType.equals(EntityType.ENTITY_VIEW)

4
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -67,6 +67,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
@ -138,6 +139,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TbCoreDeviceRpcService tbCoreDeviceRpcService,
StatsFactory statsFactory,
TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache,
TbApiUsageStateService statsService,
TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService,
@ -145,7 +147,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
OtaPackageStateService firmwareStateService,
GitVersionControlQueueService vcQueueService,
PartitionService partitionService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();

4
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -51,6 +51,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision;
@ -121,10 +122,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbRuleEngineDeviceRpcService tbDeviceRpcService,
StatsFactory statsFactory,
TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache,
TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory;

21
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -18,11 +18,11 @@ package org.thingsboard.server.service.queue.processing;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -33,16 +33,17 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.queue.TbPackCallback;
import org.thingsboard.server.service.queue.TbPackProcessingContext;
@ -70,6 +71,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final DataDecodingEncodingService encodingService;
protected final TbTenantProfileCache tenantProfileCache;
protected final TbDeviceProfileCache deviceProfileCache;
protected final TbAssetProfileCache assetProfileCache;
protected final TbApiUsageStateService apiUsageStateService;
protected final PartitionService partitionService;
@ -77,12 +79,13 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbApiUsageStateService apiUsageStateService, PartitionService partitionService,
TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
this.actorContext = actorContext;
this.encodingService = encodingService;
this.tenantProfileCache = tenantProfileCache;
this.deviceProfileCache = deviceProfileCache;
this.assetProfileCache = assetProfileCache;
this.apiUsageStateService = apiUsageStateService;
this.partitionService = partitionService;
this.nfConsumer = nfConsumer;
@ -180,6 +183,10 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
deviceProfileCache.evict(componentLifecycleMsg.getTenantId(), new DeviceId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ASSET_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
assetProfileCache.evict(componentLifecycleMsg.getTenantId(), new AssetProfileId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ASSET.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
assetProfileCache.evict(componentLifecycleMsg.getTenantId(), new AssetId(componentLifecycleMsg.getEntityId().getId()));
} else if (EntityType.ENTITY_VIEW.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
actorContext.getTbEntityViewService().onComponentLifecycleMsg(componentLifecycleMsg);
} else if (EntityType.API_USAGE_STATE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {

27
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -35,10 +35,12 @@ import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.ApiUsageStateId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -58,6 +60,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.controller.HttpValidationCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceProfileService;
@ -113,6 +116,9 @@ public class AccessValidator {
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected AssetService assetService;
@ -211,6 +217,9 @@ public class AccessValidator {
case ASSET:
validateAsset(currentUser, operation, entityId, callback);
return;
case ASSET_PROFILE:
validateAssetProfile(currentUser, operation, entityId, callback);
return;
case RULE_CHAIN:
validateRuleChain(currentUser, operation, entityId, callback);
return;
@ -304,6 +313,24 @@ public class AccessValidator {
}
}
private void validateAssetProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
AssetProfile assetProfile = assetProfileService.findAssetProfileById(currentUser.getTenantId(), new AssetProfileId(entityId.getId()));
if (assetProfile == null) {
callback.onSuccess(ValidationResult.entityNotFound("Asset profile with requested id wasn't found!"));
} else {
try {
accessControlService.checkPermission(currentUser, Resource.ASSET_PROFILE, operation, entityId, assetProfile);
} catch (ThingsboardException e) {
callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
}
callback.onSuccess(ValidationResult.ok(assetProfile));
}
}
}
private void validateApiUsageState(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));

5
application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java

@ -42,7 +42,8 @@ public class CustomerUserPermissions extends AbstractPermissions {
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.EDGE, customerEntityPermissionChecker);
put(Resource.RPC, rpcPermissionChecker);
put(Resource.DEVICE_PROFILE, deviceProfilePermissionChecker);
put(Resource.DEVICE_PROFILE, profilePermissionChecker);
put(Resource.ASSET_PROFILE, profilePermissionChecker);
}
private static final PermissionChecker customerAlarmPermissionChecker = new PermissionChecker() {
@ -154,7 +155,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
}
};
private static final PermissionChecker deviceProfilePermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) {
private static final PermissionChecker profilePermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) {
@Override
@SuppressWarnings("unchecked")

1
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -36,6 +36,7 @@ public enum Resource {
OAUTH2_CONFIGURATION_TEMPLATE(),
TENANT_PROFILE(EntityType.TENANT_PROFILE),
DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
ASSET_PROFILE(EntityType.ASSET_PROFILE),
API_USAGE_STATE(EntityType.API_USAGE_STATE),
TB_RESOURCE(EntityType.TB_RESOURCE),
OTA_PACKAGE(EntityType.OTA_PACKAGE),

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -41,6 +41,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
put(Resource.ASSET_PROFILE, tenantEntityPermissionChecker);
put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker);

2
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -64,7 +64,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
private final TbNotificationEntityService entityNotificationService;
protected static final List<EntityType> SUPPORTED_ENTITY_TYPES = List.of(
EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN,
EntityType.CUSTOMER, EntityType.ASSET_PROFILE, EntityType.ASSET, EntityType.RULE_CHAIN,
EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE,
EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE
);

7
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
@ -36,6 +37,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -183,7 +185,7 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
@Autowired
private void setRemovers(CustomerService customerService, AssetService assetService, RuleChainService ruleChainService,
DashboardService dashboardService, DeviceProfileService deviceProfileService,
DeviceService deviceService, WidgetsBundleService widgetsBundleService) {
AssetProfileService assetProfileService, DeviceService deviceService, WidgetsBundleService widgetsBundleService) {
removers.put(EntityType.CUSTOMER, (tenantId, entityId) -> {
customerService.deleteCustomer(tenantId, (CustomerId) entityId);
});
@ -199,6 +201,9 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
removers.put(EntityType.DEVICE_PROFILE, (tenantId, entityId) -> {
deviceProfileService.deleteDeviceProfile(tenantId, (DeviceProfileId) entityId);
});
removers.put(EntityType.ASSET_PROFILE, (tenantId, entityId) -> {
assetProfileService.deleteAssetProfile(tenantId, (AssetProfileId) entityId);
});
removers.put(EntityType.DEVICE, (tenantId, entityId) -> {
deviceService.deleteDevice(tenantId, (DeviceId) entityId);
});

1
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java

@ -32,6 +32,7 @@ public class AssetExportService extends BaseEntityExportService<AssetId, Asset,
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Asset asset, EntityExportData<Asset> exportData) {
asset.setCustomerId(getExternalIdOrElseInternal(ctx, asset.getCustomerId()));
asset.setAssetProfileId(getExternalIdOrElseInternal(ctx, asset.getAssetProfileId()));
}
@Override

43
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.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.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.Set;
@Service
@TbCoreComponent
public class AssetProfileExportService extends BaseEntityExportService<AssetProfileId, AssetProfile, EntityExportData<AssetProfile>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, AssetProfile assetProfile, EntityExportData<AssetProfile> exportData) {
assetProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultDashboardId()));
assetProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultRuleChainId()));
}
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.ASSET_PROFILE);
}
}

1
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java

@ -41,6 +41,7 @@ public class AssetImportService extends BaseEntityImportService<AssetId, Asset,
@Override
protected Asset prepare(EntitiesImportCtx ctx, Asset asset, Asset old, EntityExportData<Asset> exportData, IdProvider idProvider) {
asset.setAssetProfileId(idProvider.getInternalId(asset.getAssetProfileId()));
return asset;
}

80
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java

@ -0,0 +1,80 @@
/**
* 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.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class AssetProfileImportService extends BaseEntityImportService<AssetProfileId, AssetProfile, EntityExportData<AssetProfile>> {
private final AssetProfileService assetProfileService;
@Override
protected void setOwner(TenantId tenantId, AssetProfile assetProfile, IdProvider idProvider) {
assetProfile.setTenantId(tenantId);
}
@Override
protected AssetProfile prepare(EntitiesImportCtx ctx, AssetProfile assetProfile, AssetProfile old, EntityExportData<AssetProfile> exportData, IdProvider idProvider) {
assetProfile.setDefaultRuleChainId(idProvider.getInternalId(assetProfile.getDefaultRuleChainId()));
assetProfile.setDefaultDashboardId(idProvider.getInternalId(assetProfile.getDefaultDashboardId()));
return assetProfile;
}
@Override
protected AssetProfile saveOrUpdate(EntitiesImportCtx ctx, AssetProfile assetProfile, EntityExportData<AssetProfile> exportData, IdProvider idProvider) {
return assetProfileService.saveAssetProfile(assetProfile);
}
@Override
protected void onEntitySaved(User user, AssetProfile savedAssetProfile, AssetProfile oldAssetProfile) throws ThingsboardException {
clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedAssetProfile.getId(),
oldAssetProfile == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
entityNotificationService.notifyCreateOrUpdateOrDelete(savedAssetProfile.getTenantId(), null,
savedAssetProfile.getId(), savedAssetProfile, user, oldAssetProfile == null ? ActionType.ADDED : ActionType.UPDATED, true, null);
}
@Override
protected AssetProfile deepCopy(AssetProfile assetProfile) {
return new AssetProfile(assetProfile);
}
@Override
protected void cleanupForComparison(AssetProfile assetProfile) {
super.cleanupForComparison(assetProfile);
}
@Override
public EntityType getEntityType() {
return EntityType.ASSET_PROFILE;
}
}

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

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -115,6 +116,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
super.shutdownExecutor();
}
@Override
public ListenableFuture<Void> saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts) {
SettableFuture<Void> future = SettableFuture.create();
saveAndNotify(tenantId, entityId, Collections.singletonList(ts), new VoidFutureCallback(future));
return future;
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) {
saveAndNotify(tenantId, null, entityId, ts, 0L, callback);
@ -332,6 +340,34 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
, System.currentTimeMillis())), callback);
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
@ -436,4 +472,22 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
private static class VoidFutureCallback implements FutureCallback<Void> {
private final SettableFuture<Void> future;
public VoidFutureCallback(SettableFuture<Void> future) {
this.future = future;
}
@Override
public void onSuccess(Void result) {
future.set(null);
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
}
}

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

@ -274,6 +274,8 @@ sql:
# Specify whether to log database queries and their parameters generated by entity query repository
log_queries: "${SQL_LOG_QUERIES:false}"
log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}"
log_tenant_stats: "${SQL_LOG_TENANT_STATS:true}"
log_tenant_stats_interval_ms: "${SQL_LOG_TENANT_STATS_INTERVAL_MS:60000}"
postgres:
# Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
@ -412,6 +414,9 @@ cache:
deviceProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}"
assetProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_ASSET_PROFILES_MAX_SIZE:10000}"
attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
@ -567,6 +572,7 @@ audit-log:
"alarm": "${AUDIT_LOG_MASK_ALARM:W}"
"entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
"device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
"asset_profile": "${AUDIT_LOG_MASK_ASSET_PROFILE:W}"
"edge": "${AUDIT_LOG_MASK_EDGE:W}"
"tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}"
"ota_package": "${AUDIT_LOG_MASK_OTA_PACKAGE:W}"
@ -716,6 +722,8 @@ transport:
dtls:
# Enable/disable DTLS 1.2 support
enabled: "${COAP_DTLS_ENABLED:false}"
# RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000
retransmission_timeout: "${COAP_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}"
# CoAP DTLS bind address
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port
@ -753,6 +761,9 @@ transport:
lwm2m:
# Enable/disable lvm2m transport protocol.
enabled: "${LWM2M_ENABLED:true}"
dtls:
# RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000
retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}"
server:
id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"

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

@ -35,7 +35,7 @@ import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@ -59,6 +59,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
@ -131,8 +132,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected static final String DIFFERENT_CUSTOMER_USER_EMAIL = "testdifferentcustomer@thingsboard.org";
private static final String DIFFERENT_CUSTOMER_USER_PASSWORD = "diffcustomer";
/** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
/**
* See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/
private static final long DEFAULT_TIMEOUT = -1L;
@ -448,6 +450,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return deviceProfile;
}
protected AssetProfile createAssetProfile(String name) {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setName(name);
assetProfile.setDescription(name + " Test");
assetProfile.setDefault(false);
assetProfile.setDefaultRuleChainId(null);
return assetProfile;
}
protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration, boolean sendAckOnValidationException) {
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration();
mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC);
@ -722,23 +733,18 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected <T> void testEntityDaoWithRelationsTransactionalException(Dao<T> dao, EntityId entityIdFrom, EntityId entityTo,
String urlDelete) throws Exception {
entityDaoRemoveByIdWithException (dao);
createEntityRelation(entityIdFrom, entityTo, "TEST_TRANSACTIONAL_TYPE");
assertThat(findRelationsByTo(entityTo)).hasSize(1);
doDelete(urlDelete)
.andExpect(status().isInternalServerError());
assertThat(findRelationsByTo(entityTo)).hasSize(1);
}
Mockito.doThrow(new ConstraintViolationException("mock message", new SQLException(), "MOCK_CONSTRAINT")).when(dao).removeById(any(), any());
try {
createEntityRelation(entityIdFrom, entityTo, "TEST_TRANSACTIONAL_TYPE");
assertThat(findRelationsByTo(entityTo)).hasSize(1);
protected <T> void entityDaoRemoveByIdWithException (Dao<T> dao) throws Exception {
BDDMockito.willThrow(new ConstraintViolationException("mock message", new SQLException(), "MOCK_CONSTRAINT"))
.given(dao).removeById(any(), any());
}
doDelete(urlDelete)
.andExpect(status().isInternalServerError());
protected <T> void afterTestEntityDaoRemoveByIdWithException (Dao<T> dao) throws Exception {
BDDMockito.willCallRealMethod().given(dao).removeById(any(), any());
assertThat(findRelationsByTo(entityTo)).hasSize(1);
} finally {
Mockito.reset(dao);
}
}
protected void createEntityRelation(EntityId entityIdFrom, EntityId entityIdTo, String typeRelation) throws Exception {
@ -751,9 +757,13 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
MvcResult mvcResult = doGet(url).andReturn();
switch (mvcResult.getResponse().getStatus()) {
case 200: return readResponse(mvcResult, new TypeReference<>() {});
case 404: return Collections.emptyList();
case 200:
return readResponse(mvcResult, new TypeReference<>() {
});
case 404:
return Collections.emptyList();
}
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
}

20
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -22,8 +22,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -43,16 +47,24 @@ import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@ContextConfiguration(classes = {BaseAlarmControllerTest.Config.class})
public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public static final String TEST_ALARM_TYPE = "Test";
protected Device customerDevice;
@SpyBean
@Autowired
private AlarmDao alarmDao;
static class Config {
@Bean
@Primary
public AlarmDao alarmDao(AlarmDao alarmDao) {
return Mockito.mock(AlarmDao.class, AdditionalAnswers.delegatesTo(alarmDao));
}
}
@Before
public void setup() throws Exception {
loginTenantAdmin();
@ -72,8 +84,6 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public void teardown() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (alarmDao);
deleteDifferentTenant();
}

45
application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java

@ -21,8 +21,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView;
@ -30,6 +34,7 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AssetId;
@ -50,6 +55,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ContextConfiguration(classes = {BaseAssetControllerTest.Config.class})
public abstract class BaseAssetControllerTest extends AbstractControllerTest {
private IdComparator<Asset> idComparator = new IdComparator<>();
@ -57,9 +63,17 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
private Tenant savedTenant;
private User tenantAdmin;
@SpyBean
@Autowired
private AssetDao assetDao;
static class Config {
@Bean
@Primary
public AssetDao assetDao(AssetDao assetDao) {
return Mockito.mock(AssetDao.class, AdditionalAnswers.delegatesTo(assetDao));
}
}
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -83,8 +97,6 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (assetDao);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@ -187,6 +199,20 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
deleteDifferentTenant();
}
@Test
public void testSaveAssetWithProfileFromDifferentTenant() throws Exception {
loginDifferentTenant();
AssetProfile differentProfile = createAssetProfile("Different profile");
differentProfile = doPost("/api/assetProfile", differentProfile, AssetProfile.class);
loginTenantAdmin();
Asset asset = new Asset();
asset.setName("My device");
asset.setAssetProfileId(differentProfile.getId());
doPost("/api/asset", asset).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Asset can`t be referencing to asset profile from different tenant!")));
}
@Test
public void testFindAssetById() throws Exception {
Asset asset = new Asset();
@ -310,13 +336,12 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset type " + msgErrorShouldBeSpecified;
doPost("/api/asset", asset)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
Assert.assertEquals("default", savedAsset.getType());
testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
}
@Test

463
application/src/test/java/org/thingsboard/server/controller/BaseAssetProfileControllerTest.java

@ -0,0 +1,463 @@
/**
* 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.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.asset.AssetProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ContextConfiguration(classes = {BaseAssetProfileControllerTest.Config.class})
public abstract class BaseAssetProfileControllerTest extends AbstractControllerTest {
private IdComparator<AssetProfile> idComparator = new IdComparator<>();
private IdComparator<AssetProfileInfo> assetProfileInfoIdComparator = new IdComparator<>();
private Tenant savedTenant;
private User tenantAdmin;
@Autowired
private AssetProfileDao assetProfileDao;
static class Config {
@Bean
@Primary
public AssetProfileDao assetProfileDao(AssetProfileDao assetProfileDao) {
return Mockito.mock(AssetProfileDao.class, AdditionalAnswers.delegatesTo(assetProfileDao));
}
}
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveAssetProfile() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
Mockito.reset(tbClusterService, auditLogService);
AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
Assert.assertNotNull(savedAssetProfile);
Assert.assertNotNull(savedAssetProfile.getId());
Assert.assertTrue(savedAssetProfile.getCreatedTime() > 0);
Assert.assertEquals(assetProfile.getName(), savedAssetProfile.getName());
Assert.assertEquals(assetProfile.getDescription(), savedAssetProfile.getDescription());
Assert.assertEquals(assetProfile.isDefault(), savedAssetProfile.isDefault());
Assert.assertEquals(assetProfile.getDefaultRuleChainId(), savedAssetProfile.getDefaultRuleChainId());
testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedAssetProfile, savedAssetProfile.getId(), savedAssetProfile.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
savedAssetProfile.setName("New asset profile");
doPost("/api/assetProfile", savedAssetProfile, AssetProfile.class);
AssetProfile foundAssetProfile = doGet("/api/assetProfile/" + savedAssetProfile.getId().getId().toString(), AssetProfile.class);
Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfile.getName());
testNotifyEntityBroadcastEntityStateChangeEventOneTime(foundAssetProfile, foundAssetProfile.getId(), foundAssetProfile.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UPDATED);
}
@Test
public void saveAssetProfileWithViolationOfValidation() throws Exception {
String msgError = msgErrorFieldLength("name");
Mockito.reset(tbClusterService, auditLogService);
AssetProfile createAssetProfile = this.createAssetProfile(StringUtils.randomAlphabetic(300));
doPost("/api/assetProfile", createAssetProfile)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(createAssetProfile, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@Test
public void testFindAssetProfileById() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
AssetProfile foundAssetProfile = doGet("/api/assetProfile/" + savedAssetProfile.getId().getId().toString(), AssetProfile.class);
Assert.assertNotNull(foundAssetProfile);
Assert.assertEquals(savedAssetProfile, foundAssetProfile);
}
@Test
public void whenGetAssetProfileById_thenPermissionsAreChecked() throws Exception {
AssetProfile assetProfile = createAssetProfile("Asset profile 1");
assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
loginDifferentTenant();
doGet("/api/assetProfile/" + assetProfile.getId())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test
public void testFindAssetProfileInfoById() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
AssetProfileInfo foundAssetProfileInfo = doGet("/api/assetProfileInfo/" + savedAssetProfile.getId().getId().toString(), AssetProfileInfo.class);
Assert.assertNotNull(foundAssetProfileInfo);
Assert.assertEquals(savedAssetProfile.getId(), foundAssetProfileInfo.getId());
Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfileInfo.getName());
Customer customer = new Customer();
customer.setTitle("Customer");
customer.setTenantId(savedTenant.getId());
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER);
customerUser.setTenantId(savedTenant.getId());
customerUser.setCustomerId(savedCustomer.getId());
customerUser.setEmail("customer2@thingsboard.org");
createUserAndLogin(customerUser, "customer");
foundAssetProfileInfo = doGet("/api/assetProfileInfo/" + savedAssetProfile.getId().getId().toString(), AssetProfileInfo.class);
Assert.assertNotNull(foundAssetProfileInfo);
Assert.assertEquals(savedAssetProfile.getId(), foundAssetProfileInfo.getId());
Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfileInfo.getName());
}
@Test
public void whenGetAssetProfileInfoById_thenPermissionsAreChecked() throws Exception {
AssetProfile assetProfile = createAssetProfile("Asset profile 1");
assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
loginDifferentTenant();
doGet("/api/assetProfileInfo/" + assetProfile.getId())
.andExpect(status().isForbidden())
.andExpect(statusReason(containsString(msgErrorPermission)));
}
@Test
public void testFindDefaultAssetProfileInfo() throws Exception {
AssetProfileInfo foundDefaultAssetProfileInfo = doGet("/api/assetProfileInfo/default", AssetProfileInfo.class);
Assert.assertNotNull(foundDefaultAssetProfileInfo);
Assert.assertNotNull(foundDefaultAssetProfileInfo.getId());
Assert.assertNotNull(foundDefaultAssetProfileInfo.getName());
Assert.assertEquals("default", foundDefaultAssetProfileInfo.getName());
}
@Test
public void testSetDefaultAssetProfile() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile 1");
AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
Mockito.reset(tbClusterService, auditLogService);
AssetProfile defaultAssetProfile = doPost("/api/assetProfile/" + savedAssetProfile.getId().getId().toString() + "/default", AssetProfile.class);
Assert.assertNotNull(defaultAssetProfile);
AssetProfileInfo foundDefaultAssetProfile = doGet("/api/assetProfileInfo/default", AssetProfileInfo.class);
Assert.assertNotNull(foundDefaultAssetProfile);
Assert.assertEquals(savedAssetProfile.getName(), foundDefaultAssetProfile.getName());
Assert.assertEquals(savedAssetProfile.getId(), foundDefaultAssetProfile.getId());
testNotifyEntityOneTimeMsgToEdgeServiceNever(defaultAssetProfile, defaultAssetProfile.getId(), defaultAssetProfile.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.UPDATED);
}
@Test
public void testSaveAssetProfileWithEmptyName() throws Exception {
AssetProfile assetProfile = new AssetProfile();
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset profile name " + msgErrorShouldBeSpecified;
doPost("/api/assetProfile", assetProfile)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(assetProfile, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@Test
public void testSaveAssetProfileWithSameName() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
doPost("/api/assetProfile", assetProfile).andExpect(status().isOk());
AssetProfile assetProfile2 = this.createAssetProfile("Asset Profile");
Mockito.reset(tbClusterService, auditLogService);
String msgError = "Asset profile with such name already exists";
doPost("/api/assetProfile", assetProfile2)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(msgError)));
testNotifyEntityEqualsOneTimeServiceNeverError(assetProfile, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
}
@Test
public void testDeleteAssetProfileWithExistingAsset() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
Asset asset = new Asset();
asset.setName("Test asset");
asset.setAssetProfileId(savedAssetProfile.getId());
doPost("/api/asset", asset, Asset.class);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/assetProfile/" + savedAssetProfile.getId().getId().toString())
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("The asset profile referenced by the assets cannot be deleted")));
testNotifyEntityNever(savedAssetProfile.getId(), savedAssetProfile);
}
@Test
public void testSaveAssetProfileWithRuleChainFromDifferentTenant() throws Exception {
loginDifferentTenant();
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Different rule chain");
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
loginTenantAdmin();
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
assetProfile.setDefaultRuleChainId(savedRuleChain.getId());
doPost("/api/assetProfile", assetProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't assign rule chain from different tenant!")));
}
@Test
public void testSaveAssetProfileWithDashboardFromDifferentTenant() throws Exception {
loginDifferentTenant();
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Different dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
loginTenantAdmin();
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
assetProfile.setDefaultDashboardId(savedDashboard.getId());
doPost("/api/assetProfile", assetProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Can't assign dashboard from different tenant!")));
}
@Test
public void testDeleteAssetProfile() throws Exception {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
Mockito.reset(tbClusterService, auditLogService);
doDelete("/api/assetProfile/" + savedAssetProfile.getId().getId().toString())
.andExpect(status().isOk());
String savedAssetProfileIdStr = savedAssetProfile.getId().getId().toString();
testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedAssetProfile, savedAssetProfile.getId(), savedAssetProfile.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, savedAssetProfileIdStr);
doGet("/api/assetProfile/" + savedAssetProfile.getId().getId().toString())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Asset profile", savedAssetProfileIdStr))));
}
@Test
public void testFindAssetProfiles() throws Exception {
List<AssetProfile> assetProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<AssetProfile> pageData = doGetTypedWithPageLink("/api/assetProfiles?",
new TypeReference<>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
assetProfiles.addAll(pageData.getData());
Mockito.reset(tbClusterService, auditLogService);
int cntEntity = 28;
for (int i = 0; i < cntEntity; i++) {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile" + i);
assetProfiles.add(doPost("/api/assetProfile", assetProfile, AssetProfile.class));
}
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new AssetProfile(), new AssetProfile(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity);
Mockito.reset(tbClusterService, auditLogService);
List<AssetProfile> loadedAssetProfiles = new ArrayList<>();
pageLink = new PageLink(17);
do {
pageData = doGetTypedWithPageLink("/api/assetProfiles?",
new TypeReference<>() {
}, pageLink);
loadedAssetProfiles.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(assetProfiles, idComparator);
Collections.sort(loadedAssetProfiles, idComparator);
Assert.assertEquals(assetProfiles, loadedAssetProfiles);
for (AssetProfile assetProfile : loadedAssetProfiles) {
if (!assetProfile.isDefault()) {
doDelete("/api/assetProfile/" + assetProfile.getId().getId().toString())
.andExpect(status().isOk());
}
}
testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(loadedAssetProfiles.get(0), loadedAssetProfiles.get(0),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, cntEntity, loadedAssetProfiles.get(0).getId().getId().toString());
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/assetProfiles?",
new TypeReference<>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@Test
public void testFindAssetProfileInfos() throws Exception {
List<AssetProfile> assetProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<AssetProfile> assetProfilePageData = doGetTypedWithPageLink("/api/assetProfiles?",
new TypeReference<PageData<AssetProfile>>() {
}, pageLink);
Assert.assertFalse(assetProfilePageData.hasNext());
Assert.assertEquals(1, assetProfilePageData.getTotalElements());
assetProfiles.addAll(assetProfilePageData.getData());
for (int i = 0; i < 28; i++) {
AssetProfile assetProfile = this.createAssetProfile("Asset Profile" + i);
assetProfiles.add(doPost("/api/assetProfile", assetProfile, AssetProfile.class));
}
List<AssetProfileInfo> loadedAssetProfileInfos = new ArrayList<>();
pageLink = new PageLink(17);
PageData<AssetProfileInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/assetProfileInfos?",
new TypeReference<>() {
}, pageLink);
loadedAssetProfileInfos.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(assetProfiles, idComparator);
Collections.sort(loadedAssetProfileInfos, assetProfileInfoIdComparator);
List<AssetProfileInfo> assetProfileInfos = assetProfiles.stream().map(assetProfile -> new AssetProfileInfo(assetProfile.getId(),
assetProfile.getName(), assetProfile.getImage(), assetProfile.getDefaultDashboardId())).collect(Collectors.toList());
Assert.assertEquals(assetProfileInfos, loadedAssetProfileInfos);
for (AssetProfile assetProfile : assetProfiles) {
if (!assetProfile.isDefault()) {
doDelete("/api/assetProfile/" + assetProfile.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/assetProfileInfos?",
new TypeReference<PageData<AssetProfileInfo>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@Test
public void testDeleteAssetProfileWithDeleteRelationsOk() throws Exception {
AssetProfileId assetProfileId = savedAssetProfile("AssetProfile for Test WithRelationsOk").getId();
testEntityDaoWithRelationsOk(savedTenant.getId(), assetProfileId, "/api/assetProfile/" + assetProfileId);
}
@Test
public void testDeleteAssetProfileExceptionWithRelationsTransactional() throws Exception {
AssetProfileId assetProfileId = savedAssetProfile("AssetProfile for Test WithRelations Transactional Exception").getId();
testEntityDaoWithRelationsTransactionalException(assetProfileDao, savedTenant.getId(), assetProfileId, "/api/assetProfile/" + assetProfileId);
}
private AssetProfile savedAssetProfile(String name) {
AssetProfile assetProfile = createAssetProfile(name);
return doPost("/api/assetProfile", assetProfile, AssetProfile.class);
}
}

20
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -24,8 +24,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
@ -48,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ContextConfiguration(classes = {BaseCustomerControllerTest.Config.class})
public abstract class BaseCustomerControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<Customer>> PAGE_DATA_CUSTOMER_TYPE_REFERENCE = new TypeReference<>() {
};
@ -57,9 +62,18 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
private Tenant savedTenant;
private User tenantAdmin;
@SpyBean
@Autowired
private CustomerDao customerDao;
static class Config {
@Bean
@Primary
public CustomerDao customerDao(CustomerDao customerDao) {
return Mockito.mock(CustomerDao.class, AdditionalAnswers.delegatesTo(customerDao));
}
}
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
@ -87,8 +101,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (customerDao);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}

20
application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java

@ -21,8 +21,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
@ -46,6 +50,7 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ContextConfiguration(classes = {BaseDashboardControllerTest.Config.class})
public abstract class BaseDashboardControllerTest extends AbstractControllerTest {
private IdComparator<DashboardInfo> idComparator = new IdComparator<>();
@ -53,9 +58,17 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
private Tenant savedTenant;
private User tenantAdmin;
@SpyBean
@Autowired
private DashboardDao dashboardDao;
static class Config {
@Bean
@Primary
public DashboardDao dashboardDao(DashboardDao dashboardDao) {
return Mockito.mock(DashboardDao.class, AdditionalAnswers.delegatesTo(dashboardDao));
}
}
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -79,8 +92,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
public void afterTest() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (dashboardDao);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@ -489,4 +500,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
dashboard.setTitle(title);
return doPost("/api/dashboard", dashboard, Dashboard.class);
}
}

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

@ -25,8 +25,13 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
@ -69,8 +74,10 @@ import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ContextConfiguration(classes = {BaseDeviceControllerTest.Config.class})
public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<Device>> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {};
static final TypeReference<PageData<Device>> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {
};
ListeningExecutorService executor;
@ -83,9 +90,16 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@SpyBean
private GatewayNotificationsService gatewayNotificationsService;
@SpyBean
@Autowired
private DeviceDao deviceDao;
static class Config {
@Bean
@Primary
public DeviceDao deviceDao(DeviceDao deviceDao) {
return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao));
}
}
@Before
public void beforeTest() throws Exception {
@ -114,8 +128,6 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (deviceDao);
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
}
@ -464,9 +476,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String customerIdStr = savedDevice.getId().toString();
doPost("/api/customer/" + customerIdStr
+ "/device/" + savedDevice.getId().getId())
+ "/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
.andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
@ -657,7 +669,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Device", deviceTimeBasedId.toString()))));
.andExpect(statusReason(containsString(msgErrorNoFound("Device", deviceTimeBasedId.toString()))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
@ -1168,7 +1180,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
doPost("/api/edge/" + savedEdge.getId().getId()
+ "/device/" + savedDevice.getId().getId(), Device.class);
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ASSIGNED_TO_EDGE,
savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());

20
application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java

@ -26,8 +26,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
@ -69,6 +73,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
@ContextConfiguration(classes = {BaseDeviceProfileControllerTest.Config.class})
public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest {
private IdComparator<DeviceProfile> idComparator = new IdComparator<>();
@ -77,9 +82,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
private Tenant savedTenant;
private User tenantAdmin;
@SpyBean
@Autowired
private DeviceProfileDao deviceProfileDao;
static class Config {
@Bean
@Primary
public DeviceProfileDao deviceProfileDao(DeviceProfileDao deviceProfileDao) {
return Mockito.mock(DeviceProfileDao.class, AdditionalAnswers.delegatesTo(deviceProfileDao));
}
}
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -103,8 +116,6 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
public void afterTest() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (deviceProfileDao);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@ -1149,4 +1160,5 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
DeviceProfile deviceProfile = createDeviceProfile(name);
return doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
}
}

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

@ -21,8 +21,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
@ -44,6 +48,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
@ -64,6 +69,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@TestPropertySource(properties = {
"edges.enabled=true",
})
@ContextConfiguration(classes = {BaseEdgeControllerTest.Config.class})
public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public static final String EDGE_HOST = "localhost";
@ -75,10 +81,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
private TenantId tenantId;
private User tenantAdmin;
@SpyBean
@Autowired
private EdgeDao edgeDao;
@Before
static class Config {
@Bean
@Primary
public EdgeDao edgeDao(EdgeDao edgeDao) {
return Mockito.mock(EdgeDao.class, AdditionalAnswers.delegatesTo(edgeDao));
}
}
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -102,8 +116,6 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException (edgeDao);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@ -808,7 +820,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.expectMessageAmount(12);
edgeImitator.expectMessageAmount(14);
edgeImitator.connect();
assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue();
@ -816,17 +828,19 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4);
edgeImitator.expectMessageAmount(9);
edgeImitator.expectMessageAmount(11);
doPost("/api/edge/sync/" + edge.getId());
assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue();
assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4);

20
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -31,8 +31,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.ThingsBoardExecutors;
@ -84,6 +88,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
"js.evaluator=mock",
})
@Slf4j
@ContextConfiguration(classes = {BaseEntityViewControllerTest.Config.class})
public abstract class BaseEntityViewControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<EntityView>> PAGE_DATA_ENTITY_VIEW_TYPE_REF = new TypeReference<>() {
};
@ -96,9 +101,17 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
List<ListenableFuture<ResultActions>> deleteFutures = new ArrayList<>();
ListeningExecutorService executor;
@SpyBean
@Autowired
private EntityViewDao entityViewDao;
static class Config {
@Bean
@Primary
public EntityViewDao entityViewDao(EntityViewDao entityViewDao) {
return Mockito.mock(EntityViewDao.class, AdditionalAnswers.delegatesTo(entityViewDao));
}
}
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
@ -120,9 +133,6 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@After
public void afterTest() throws Exception {
afterTestEntityDaoRemoveByIdWithException (entityViewDao);
executor.shutdownNow();
}

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

@ -20,8 +20,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -43,6 +47,7 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ContextConfiguration(classes = {BaseRuleChainControllerTest.Config.class})
public abstract class BaseRuleChainControllerTest extends AbstractControllerTest {
private IdComparator<RuleChain> idComparator = new IdComparator<>();
@ -50,9 +55,17 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
private Tenant savedTenant;
private User tenantAdmin;
@SpyBean
@Autowired
private RuleChainDao ruleChainDao;
static class Config {
@Bean
@Primary
public RuleChainDao ruleChainDao(RuleChainDao ruleChainDao) {
return Mockito.mock(RuleChainDao.class, AdditionalAnswers.delegatesTo(ruleChainDao));
}
}
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -76,8 +89,6 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
public void afterTest() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException(ruleChainDao);
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}

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

@ -21,9 +21,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
@ -51,20 +55,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.SYSTEM_TENANT;
@ContextConfiguration(classes = {BaseUserControllerTest.Config.class})
public abstract class BaseUserControllerTest extends AbstractControllerTest {
private IdComparator<User> idComparator = new IdComparator<>();
private CustomerId customerNUULId = (CustomerId) createEntityId_NULL_UUID(new Customer());
@SpyBean
@Autowired
private UserDao userDao;
static class Config {
@Bean
@Primary
public UserDao userDao(UserDao userDao) {
return Mockito.mock(UserDao.class, AdditionalAnswers.delegatesTo(userDao));
}
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
afterTestEntityDaoRemoveByIdWithException(userDao);
}
@Test

23
application/src/test/java/org/thingsboard/server/controller/sql/AssetProfileControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* 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.controller.sql;
import org.thingsboard.server.controller.BaseAssetProfileControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class AssetProfileControllerSqlTest extends BaseAssetProfileControllerTest {
}

2
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -228,7 +228,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
edgeImitator.expectMessageAmount(15);
edgeImitator.expectMessageAmount(17);
edgeImitator.connect();
requestEdgeRuleChainMetadata();

6
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -28,6 +28,7 @@ import org.thingsboard.edge.rpc.EdgeGrpcClient;
import org.thingsboard.edge.rpc.EdgeRpcClient;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
@ -196,6 +197,11 @@ public class EdgeImitator {
result.add(saveDownlinkMsg(deviceCredentialsUpdateMsg));
}
}
if (downlinkMsg.getAssetProfileUpdateMsgCount() > 0) {
for (AssetProfileUpdateMsg assetProfileUpdateMsg : downlinkMsg.getAssetProfileUpdateMsgList()) {
result.add(saveDownlinkMsg(assetProfileUpdateMsg));
}
}
if (downlinkMsg.getAssetUpdateMsgCount() > 0) {
for (AssetUpdateMsg assetUpdateMsg : downlinkMsg.getAssetUpdateMsgList()) {
result.add(saveDownlinkMsg(assetUpdateMsg));

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

@ -37,12 +37,14 @@ import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -64,6 +66,7 @@ import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -97,6 +100,8 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected AssetService assetService;
@Autowired
protected CustomerService customerService;
@ -206,11 +211,26 @@ public abstract class BaseExportImportServiceTest extends AbstractControllerTest
assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription());
}
protected Asset createAsset(TenantId tenantId, CustomerId customerId, String type, String name) {
protected AssetProfile createAssetProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfile.setName(name);
assetProfile.setDescription("dscrptn");
assetProfile.setDefaultRuleChainId(defaultRuleChainId);
assetProfile.setDefaultDashboardId(defaultDashboardId);
return assetProfileService.saveAssetProfile(assetProfile);
}
protected void checkImportedAssetProfileData(AssetProfile initialProfile, AssetProfile importedProfile) {
assertThat(initialProfile.getName()).isEqualTo(importedProfile.getName());
assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription());
}
protected Asset createAsset(TenantId tenantId, CustomerId customerId, AssetProfileId assetProfileId, String name) {
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setCustomerId(customerId);
asset.setType(type);
asset.setAssetProfileId(assetProfileId);
asset.setName(name);
asset.setLabel("lbl");
asset.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b")));

97
application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java

@ -32,12 +32,13 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -60,7 +61,6 @@ import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.sync.ie.RuleChainExportData;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
@ -77,7 +77,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.verify;
@DaoSqlTest
@ -91,18 +90,30 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
private OtaPackageStateService otaPackageStateService;
@Test
public void testExportImportAsset_betweenTenants() throws Exception {
Asset asset = createAsset(tenantId1, null, "AB", "Asset of tenant 1");
EntityExportData<Asset> exportData = exportEntity(tenantAdmin1, asset.getId());
public void testExportImportAssetWithProfile_betweenTenants() throws Exception {
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile of tenant 1");
Asset asset = createAsset(tenantId1, null, assetProfile.getId(), "Asset of tenant 1");
EntityImportResult<Asset> importResult = importEntity(tenantAdmin2, exportData);
checkImportedEntity(tenantId1, asset, tenantId2, importResult.getSavedEntity());
checkImportedAssetData(asset, importResult.getSavedEntity());
EntityExportData<AssetProfile> profileExportData = exportEntity(tenantAdmin1, assetProfile.getId());
EntityExportData<Asset> assetExportData = exportEntity(tenantAdmin1, asset.getId());
EntityImportResult<AssetProfile> profileImportResult = importEntity(tenantAdmin2, profileExportData);
checkImportedEntity(tenantId1, assetProfile, tenantId2, profileImportResult.getSavedEntity());
checkImportedAssetProfileData(assetProfile, profileImportResult.getSavedEntity());
EntityImportResult<Asset> assetImportResult = importEntity(tenantAdmin2, assetExportData);
Asset importedAsset = assetImportResult.getSavedEntity();
checkImportedEntity(tenantId1, asset, tenantId2, importedAsset);
checkImportedAssetData(asset, importedAsset);
assertThat(importedAsset.getAssetProfileId()).isEqualTo(profileImportResult.getSavedEntity().getId());
}
@Test
public void testExportImportAsset_sameTenant() throws Exception {
Asset asset = createAsset(tenantId1, null, "AB", "Asset v1.0");
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile v1.0");
Asset asset = createAsset(tenantId1, null, assetProfile.getId(), "Asset v1.0");
EntityExportData<Asset> exportData = exportEntity(tenantAdmin1, asset.getId());
EntityImportResult<Asset> importResult = importEntity(tenantAdmin1, exportData);
@ -112,8 +123,9 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testExportImportAsset_sameTenant_withCustomer() throws Exception {
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile v1.0");
Customer customer = createCustomer(tenantId1, "My customer");
Asset asset = createAsset(tenantId1, customer.getId(), "AB", "My asset");
Asset asset = createAsset(tenantId1, customer.getId(), assetProfile.getId(), "My asset");
Asset importedAsset = importEntity(tenantAdmin1, this.<Asset, AssetId>exportEntity(tenantAdmin1, asset.getId())).getSavedEntity();
assertThat(importedAsset.getCustomerId()).isEqualTo(asset.getCustomerId());
@ -238,8 +250,9 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testExportImportDashboard_betweenTenants_withEntityAliases() throws Exception {
Asset asset1 = createAsset(tenantId1, null, "A", "Asset 1");
Asset asset2 = createAsset(tenantId1, null, "A", "Asset 2");
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "A");
Asset asset1 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 1");
Asset asset2 = createAsset(tenantId1, null, assetProfile.getId(), "Asset 2");
Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1");
String entityAliases = "{\n" +
@ -263,10 +276,13 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
dashboard.setConfiguration(dashboardConfiguration);
dashboard = dashboardService.saveDashboard(dashboard);
EntityExportData<AssetProfile> profileExportData = exportEntity(tenantAdmin1, assetProfile.getId());
EntityExportData<Asset> asset1ExportData = exportEntity(tenantAdmin1, asset1.getId());
EntityExportData<Asset> asset2ExportData = exportEntity(tenantAdmin1, asset2.getId());
EntityExportData<Dashboard> dashboardExportData = exportEntity(tenantAdmin1, dashboard.getId());
AssetProfile importedProfile = importEntity(tenantAdmin2, profileExportData).getSavedEntity();
Asset importedAsset1 = importEntity(tenantAdmin2, asset1ExportData).getSavedEntity();
Asset importedAsset2 = importEntity(tenantAdmin2, asset2ExportData).getSavedEntity();
Dashboard importedDashboard = importEntity(tenantAdmin2, dashboardExportData).getSavedEntity();
@ -310,7 +326,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testExportImportWithInboundRelations_betweenTenants() throws Exception {
Asset asset = createAsset(tenantId1, null, "A", "Asset 1");
Asset asset = createAsset(tenantId1, null, null, "Asset 1");
Device device = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation = createRelation(asset.getId(), device.getId());
@ -324,6 +340,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
assertThat(deviceExportData.getRelations().get(0)).matches(entityRelation -> {
return entityRelation.getFrom().equals(asset.getId()) && entityRelation.getTo().equals(device.getId());
});
((Asset) assetExportData.getEntity()).setAssetProfileId(null);
((Device) deviceExportData.getEntity()).setDeviceProfileId(null);
Asset importedAsset = importEntity(tenantAdmin2, assetExportData).getSavedEntity();
@ -344,7 +361,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testExportImportWithRelations_betweenTenants() throws Exception {
Asset asset = createAsset(tenantId1, null, "A", "Asset 1");
Asset asset = createAsset(tenantId1, null, null, "Asset 1");
Device device = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation = createRelation(asset.getId(), device.getId());
@ -353,6 +370,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
.exportRelations(true)
.exportCredentials(false)
.build());
assetExportData.getEntity().setAssetProfileId(null);
deviceExportData.getEntity().setDeviceProfileId(null);
Asset importedAsset = importEntity(tenantAdmin2, assetExportData).getSavedEntity();
@ -371,7 +389,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testExportImportWithRelations_sameTenant() throws Exception {
Asset asset = createAsset(tenantId1, null, "A", "Asset 1");
Asset asset = createAsset(tenantId1, null, null, "Asset 1");
Device device1 = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation1 = createRelation(asset.getId(), device1.getId());
@ -394,7 +412,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void textExportImportWithRelations_sameTenant_removeExisting() throws Exception {
Asset asset1 = createAsset(tenantId1, null, "A", "Asset 1");
Asset asset1 = createAsset(tenantId1, null, null, "Asset 1");
Device device = createDevice(tenantId1, null, null, "Device 1");
EntityRelation relation1 = createRelation(asset1.getId(), device.getId());
@ -403,7 +421,7 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
.build());
assertThat(deviceExportData.getRelations()).size().isOne();
Asset asset2 = createAsset(tenantId1, null, "A", "Asset 2");
Asset asset2 = createAsset(tenantId1, null, null, "Asset 2");
EntityRelation relation2 = createRelation(asset2.getId(), device.getId());
importEntity(tenantAdmin1, deviceExportData, EntityImportSettings.builder()
@ -443,14 +461,15 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testEntityEventsOnImport() throws Exception {
Customer customer = createCustomer(tenantId1, "Customer 1");
Asset asset = createAsset(tenantId1, null, "A", "Asset 1");
RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1");
Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1");
AssetProfile assetProfile = createAssetProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Asset profile 1");
Asset asset = createAsset(tenantId1, null, assetProfile.getId(), "Asset 1");
DeviceProfile deviceProfile = createDeviceProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Device profile 1");
Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device 1");
Map<EntityType, EntityExportData> entitiesExportData = Stream.of(customer.getId(), asset.getId(), device.getId(),
ruleChain.getId(), dashboard.getId(), deviceProfile.getId())
ruleChain.getId(), dashboard.getId(), assetProfile.getId(), deviceProfile.getId())
.map(entityId -> {
try {
return exportEntity(tenantAdmin1, entityId, EntityExportSettings.builder()
@ -480,6 +499,21 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
Mockito.reset(entityActionService);
RuleChain importedRuleChain = (RuleChain) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.RULE_CHAIN)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedRuleChain.getId()), eq(importedRuleChain),
any(), eq(ActionType.ADDED), isNull());
verify(tbClusterService).broadcastEntityStateChangeEvent(any(), eq(importedRuleChain.getId()), eq(ComponentLifecycleEvent.CREATED));
Dashboard importedDashboard = (Dashboard) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DASHBOARD)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedDashboard.getId()), eq(importedDashboard),
any(), eq(ActionType.ADDED), isNull());
AssetProfile importedAssetProfile = (AssetProfile) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.ASSET_PROFILE)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedAssetProfile.getId()), eq(importedAssetProfile),
any(), eq(ActionType.ADDED), isNull());
verify(tbClusterService).broadcastEntityStateChangeEvent(any(), eq(importedAssetProfile.getId()), eq(ComponentLifecycleEvent.CREATED));
verify(tbClusterService).sendNotificationMsgToEdge(any(), any(), eq(importedAssetProfile.getId()), any(), any(), eq(EdgeEventActionType.ADDED));
Asset importedAsset = (Asset) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.ASSET)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedAsset.getId()), eq(importedAsset),
any(), eq(ActionType.ADDED), isNull());
@ -496,15 +530,6 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
any(), eq(ActionType.UPDATED), isNull());
verify(tbClusterService).sendNotificationMsgToEdge(any(), any(), eq(importedAsset.getId()), any(), any(), eq(EdgeEventActionType.UPDATED));
RuleChain importedRuleChain = (RuleChain) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.RULE_CHAIN)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedRuleChain.getId()), eq(importedRuleChain),
any(), eq(ActionType.ADDED), isNull());
verify(tbClusterService).broadcastEntityStateChangeEvent(any(), eq(importedRuleChain.getId()), eq(ComponentLifecycleEvent.CREATED));
Dashboard importedDashboard = (Dashboard) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DASHBOARD)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedDashboard.getId()), eq(importedDashboard),
any(), eq(ActionType.ADDED), isNull());
DeviceProfile importedDeviceProfile = (DeviceProfile) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DEVICE_PROFILE)).getSavedEntity();
verify(entityActionService).logEntityAction(any(), eq(importedDeviceProfile.getId()), eq(importedDeviceProfile),
any(), eq(ActionType.ADDED), isNull());
@ -529,15 +554,21 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
@Test
public void testExternalIdsInExportData() throws Exception {
Customer customer = createCustomer(tenantId1, "Customer 1");
Asset asset = createAsset(tenantId1, customer.getId(), "A", "Asset 1");
AssetProfile assetProfile = createAssetProfile(tenantId1, null, null, "Asset profile 1");
Asset asset = createAsset(tenantId1, customer.getId(), assetProfile.getId(), "Asset 1");
RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1", asset.getId());
Dashboard dashboard = createDashboard(tenantId1, customer.getId(), "Dashboard 1", asset.getId());
assetProfile.setDefaultRuleChainId(ruleChain.getId());
assetProfile.setDefaultDashboardId(dashboard.getId());
assetProfile = assetProfileService.saveAssetProfile(assetProfile);
DeviceProfile deviceProfile = createDeviceProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Device profile 1");
Device device = createDevice(tenantId1, customer.getId(), deviceProfile.getId(), "Device 1");
EntityView entityView = createEntityView(tenantId1, customer.getId(), device.getId(), "Entity view 1");
Map<EntityId, EntityId> ids = new HashMap<>();
for (EntityId entityId : List.of(customer.getId(), asset.getId(), ruleChain.getId(), dashboard.getId(),
for (EntityId entityId : List.of(customer.getId(), ruleChain.getId(), dashboard.getId(), assetProfile.getId(), asset.getId(),
deviceProfile.getId(), device.getId(), entityView.getId(), ruleChain.getId(), dashboard.getId())) {
EntityExportData exportData = exportEntity(getSecurityUser(tenantAdmin1), entityId);
EntityImportResult importResult = importEntity(getSecurityUser(tenantAdmin2), exportData, EntityImportSettings.builder()
@ -546,6 +577,10 @@ public class ExportImportServiceSqlTest extends BaseExportImportServiceTest {
ids.put(entityId, (EntityId) importResult.getSavedEntity().getId());
}
AssetProfile exportedAssetProfile = (AssetProfile) exportEntity(tenantAdmin2, (AssetProfileId) ids.get(assetProfile.getId())).getEntity();
assertThat(exportedAssetProfile.getDefaultRuleChainId()).isEqualTo(ruleChain.getId());
assertThat(exportedAssetProfile.getDefaultDashboardId()).isEqualTo(dashboard.getId());
Asset exportedAsset = (Asset) exportEntity(tenantAdmin2, (AssetId) ids.get(asset.getId())).getEntity();
assertThat(exportedAsset.getCustomerId()).isEqualTo(customer.getId());

17
common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java

@ -16,10 +16,8 @@
package org.thingsboard.server.coapserver;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.config.CertificateAuthenticationMode;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.util.SslContextUtil;
import org.eclipse.californium.scandium.config.DtlsConfig;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider;
@ -40,6 +38,13 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.californium.elements.config.CertificateAuthenticationMode.WANTED;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY;
@Slf4j
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false)
@Component
@ -51,6 +56,9 @@ public class TbCoapDtlsSettings {
@Value("${transport.coap.dtls.bind_port}")
private Integer port;
@Value("${transport.coap.dtls.retransmission_timeout:9000}")
private int dtlsRetransmissionTimeout;
@Bean
@ConfigurationProperties(prefix = "transport.coap.dtls.credentials")
public SslCredentialsConfig coapDtlsCredentials() {
@ -82,8 +90,9 @@ public class TbCoapDtlsSettings {
SslCredentials sslCredentials = this.coapDtlsCredentialsConfig.getCredentials();
SslContextUtil.Credentials serverCredentials =
new SslContextUtil.Credentials(sslCredentials.getPrivateKey(), null, sslCredentials.getCertificateChain());
configBuilder.set(DtlsConfig.DTLS_ROLE, DtlsConfig.DtlsRole.SERVER_ONLY);
configBuilder.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED);
configBuilder.set(DTLS_CLIENT_AUTHENTICATION_MODE, WANTED);
configBuilder.set(DTLS_RETRANSMISSION_TIMEOUT, dtlsRetransmissionTimeout, MILLISECONDS);
configBuilder.set(DTLS_ROLE, SERVER_ONLY);
configBuilder.setAdvancedCertificateVerifier(
new TbCoapDtlsCertificateVerifier(
transportService,

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

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
public interface AssetProfileService {
AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId);
AssetProfile findAssetProfileByName(TenantId tenantId, String profileName);
AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, AssetProfileId assetProfileId);
AssetProfile saveAssetProfile(AssetProfile assetProfile);
void deleteAssetProfile(TenantId tenantId, AssetProfileId assetProfileId);
PageData<AssetProfile> findAssetProfiles(TenantId tenantId, PageLink pageLink);
PageData<AssetProfileInfo> findAssetProfileInfos(TenantId tenantId, PageLink pageLink);
AssetProfile findOrCreateAssetProfile(TenantId tenantId, String profileName);
AssetProfile createDefaultAssetProfile(TenantId tenantId);
AssetProfile findDefaultAssetProfile(TenantId tenantId);
AssetProfileInfo findDefaultAssetProfileInfo(TenantId tenantId);
boolean setDefaultAssetProfile(TenantId tenantId, AssetProfileId assetProfileId);
void deleteAssetProfilesByTenantId(TenantId tenantId);
}

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

@ -21,15 +21,14 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List;
import java.util.Optional;
public interface AssetService {
@ -57,6 +56,8 @@ public interface AssetService {
PageData<AssetInfo> findAssetInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
PageData<AssetInfo> findAssetInfosByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink);
ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
void deleteAssetsByTenantId(TenantId tenantId);
@ -69,6 +70,8 @@ public interface AssetService {
PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(TenantId tenantId, CustomerId customerId, AssetProfileId assetProfileId, PageLink pageLink);
ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds);
void unassignCustomerAssets(TenantId tenantId, CustomerId customerId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java

@ -39,6 +39,8 @@ public interface AttributesService {
ListenableFuture<List<String>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
ListenableFuture<String> save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute);
ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);

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

@ -18,8 +18,7 @@ package org.thingsboard.server.dao.device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
public interface DeviceCredentialsService {
@ -33,6 +32,8 @@ public interface DeviceCredentialsService {
void formatCredentials(DeviceCredentials deviceCredentials);
JsonNode toCredentialsInfo(DeviceCredentials deviceCredentials);
void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials);
}

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

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/**
* @author Andrew Shvayka
@ -37,6 +38,8 @@ public interface TimeseriesService {
ListenableFuture<List<TsKvEntry>> findAll(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries);
ListenableFuture<Optional<TsKvEntry>> findLatest(TenantId tenantId, EntityId entityId, String keys);
ListenableFuture<List<TsKvEntry>> findLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
ListenableFuture<List<TsKvEntry>> findAllLatest(TenantId tenantId, EntityId entityId);

26
common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.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.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SqlDao {
}

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

@ -29,6 +29,8 @@ public class CacheConstants {
public static final String TENANTS_CACHE = "tenants";
public static final String TENANTS_EXIST_CACHE = "tenantsExist";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";
public static final String USERS_UPDATE_TIME_CACHE = "usersUpdateTime";
public static final String OTA_PACKAGE_CACHE = "otaPackages";

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

@ -42,6 +42,8 @@ public final class EdgeUtils {
return EdgeEventType.DEVICE_PROFILE;
case ASSET:
return EdgeEventType.ASSET;
case ASSET_PROFILE:
return EdgeEventType.ASSET_PROFILE;
case ENTITY_VIEW:
return EdgeEventType.ENTITY_VIEW;
case DASHBOARD:

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

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE;
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, ASSET_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE, RPC, QUEUE;
}

19
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
@ -52,6 +53,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
@Length(fieldName = "label")
private String label;
private AssetProfileId assetProfileId;
@Getter @Setter
private AssetId externalId;
@ -70,6 +73,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.name = asset.getName();
this.type = asset.getType();
this.label = asset.getLabel();
this.assetProfileId = asset.getAssetProfileId();
this.externalId = asset.getExternalId();
}
@ -79,6 +83,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.name = asset.getName();
this.type = asset.getType();
this.label = asset.getLabel();
this.assetProfileId = asset.getAssetProfileId();
Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo);
this.externalId = asset.getExternalId();
}
@ -144,12 +149,22 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.label = label;
}
@ApiModelProperty(position = 8, required = true, value = "JSON object with Asset Profile Id.")
public AssetProfileId getAssetProfileId() {
return assetProfileId;
}
public void setAssetProfileId(AssetProfileId assetProfileId) {
this.assetProfileId = assetProfileId;
}
@Override
public String getSearchText() {
return getName();
}
@ApiModelProperty(position = 8, value = "Additional parameters of the asset", dataType = "com.fasterxml.jackson.databind.JsonNode")
@ApiModelProperty(position = 9, value = "Additional parameters of the asset", dataType = "com.fasterxml.jackson.databind.JsonNode")
@Override
public JsonNode getAdditionalInfo() {
return super.getAdditionalInfo();
@ -168,6 +183,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
builder.append(type);
builder.append(", label=");
builder.append(label);
builder.append(", assetProfileId=");
builder.append(assetProfileId);
builder.append(", additionalInfo=");
builder.append(getAdditionalInfo());
builder.append(", createdTime=");

11
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java

@ -24,11 +24,15 @@ import org.thingsboard.server.common.data.id.AssetId;
@Data
public class AssetInfo extends Asset {
@ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@ApiModelProperty(position = 10, value = "Title of the Customer that owns the asset.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String customerTitle;
@ApiModelProperty(position = 10, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@ApiModelProperty(position = 11, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private boolean customerIsPublic;
@ApiModelProperty(position = 12, value = "Name of the corresponding Asset Profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String assetProfileName;
public AssetInfo() {
super();
}
@ -37,9 +41,10 @@ public class AssetInfo extends Asset {
super(assetId);
}
public AssetInfo(Asset asset, String customerTitle, boolean customerIsPublic) {
public AssetInfo(Asset asset, String customerTitle, boolean customerIsPublic, String assetProfileName) {
super(asset);
this.customerTitle = customerTitle;
this.customerIsPublic = customerIsPublic;
this.assetProfileName = assetProfileName;
}
}

118
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java

@ -0,0 +1,118 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.asset;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@Data
@ToString(exclude = {"image"})
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class AssetProfile extends SearchTextBased<AssetProfileId> implements HasName, HasTenantId, ExportableEntity<AssetProfileId> {
private static final long serialVersionUID = 6998485460273302018L;
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId;
@NoXss
@Length(fieldName = "name")
@ApiModelProperty(position = 4, value = "Unique Asset Profile Name in scope of Tenant.", example = "Building")
private String name;
@NoXss
@ApiModelProperty(position = 11, value = "Asset Profile description. ")
private String description;
@Length(fieldName = "image", max = 1000000)
@ApiModelProperty(position = 12, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of asset profiles in the grid view. ")
private String image;
private boolean isDefault;
@ApiModelProperty(position = 7, value = "Reference to the rule chain. " +
"If present, the specified rule chain will be used to process all messages related to asset, including asset updates, telemetry, attribute updates, etc. " +
"Otherwise, the root rule chain will be used to process those messages.")
private RuleChainId defaultRuleChainId;
@ApiModelProperty(position = 6, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to asset details.")
private DashboardId defaultDashboardId;
@NoXss
@ApiModelProperty(position = 8, value = "Rule engine queue name. " +
"If present, the specified queue will be used to store all unprocessed messages related to asset, including asset updates, telemetry, attribute updates, etc. " +
"Otherwise, the 'Main' queue will be used to store those messages.")
private String defaultQueueName;
private AssetProfileId externalId;
public AssetProfile() {
super();
}
public AssetProfile(AssetProfileId assetProfileId) {
super(assetProfileId);
}
public AssetProfile(AssetProfile assetProfile) {
super(assetProfile);
this.tenantId = assetProfile.getTenantId();
this.name = assetProfile.getName();
this.description = assetProfile.getDescription();
this.image = assetProfile.getImage();
this.isDefault = assetProfile.isDefault();
this.defaultRuleChainId = assetProfile.getDefaultRuleChainId();
this.defaultDashboardId = assetProfile.getDefaultDashboardId();
this.defaultQueueName = assetProfile.getDefaultQueueName();
this.externalId = assetProfile.getExternalId();
}
@ApiModelProperty(position = 1, value = "JSON object with the asset profile Id. " +
"Specify this field to update the asset profile. " +
"Referencing non-existing asset profile Id will cause error. " +
"Omit this field to create new asset profile.")
@Override
public AssetProfileId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@Override
public String getSearchText() {
return getName();
}
@ApiModelProperty(position = 5, value = "Used to mark the default profile. Default profile is used when the asset profile is not specified during asset creation.")
public boolean isDefault(){
return isDefault;
}
}

62
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java

@ -0,0 +1,62 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.asset;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.Value;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import java.util.UUID;
@Value
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true, exclude = "image")
public class AssetProfileInfo extends EntityInfo {
@ApiModelProperty(position = 3, value = "Either URL or Base64 data of the icon. Used in the mobile application to visualize set of asset profiles in the grid view. ")
private final String image;
@ApiModelProperty(position = 4, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to asset details.")
private final DashboardId defaultDashboardId;
@JsonCreator
public AssetProfileInfo(@JsonProperty("id") EntityId id,
@JsonProperty("name") String name,
@JsonProperty("image") String image,
@JsonProperty("defaultDashboardId") DashboardId defaultDashboardId) {
super(id, name);
this.image = image;
this.defaultDashboardId = defaultDashboardId;
}
public AssetProfileInfo(UUID uuid, String name, String image, UUID defaultDashboardId) {
super(EntityIdFactory.getByTypeAndUuid(EntityType.ASSET_PROFILE, uuid), name);
this.image = image;
this.defaultDashboardId = defaultDashboardId != null ? new DashboardId(defaultDashboardId) : null;
}
public AssetProfileInfo(AssetProfile profile) {
this(profile.getId(), profile.getName(), profile.getImage(), profile.getDefaultDashboardId());
}
}

1
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java

@ -20,6 +20,7 @@ public enum EdgeEventType {
ASSET,
DEVICE,
DEVICE_PROFILE,
ASSET_PROFILE,
ENTITY_VIEW,
ALARM,
RULE_CHAIN,

43
common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.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.common.data.id;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public class AssetProfileId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public AssetProfileId(@JsonProperty("id") UUID id) {
super(id);
}
public static AssetProfileId fromString(String assetProfileId) {
return new AssetProfileId(UUID.fromString(assetProfileId));
}
@ApiModelProperty(position = 2, required = true, value = "string", example = "ASSET_PROFILE", allowableValues = "ASSET_PROFILE")
@Override
public EntityType getEntityType() {
return EntityType.ASSET_PROFILE;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -65,6 +65,8 @@ public class EntityIdFactory {
return new WidgetTypeId(uuid);
case DEVICE_PROFILE:
return new DeviceProfileId(uuid);
case ASSET_PROFILE:
return new AssetProfileId(uuid);
case TENANT_PROFILE:
return new TenantProfileId(uuid);
case API_USAGE_STATE:
@ -95,6 +97,8 @@ public class EntityIdFactory {
return new DeviceId(uuid);
case DEVICE_PROFILE:
return new DeviceProfileId(uuid);
case ASSET_PROFILE:
return new AssetProfileId(uuid);
case ASSET:
return new AssetId(uuid);
case ALARM:

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
@ -41,6 +42,7 @@ import java.lang.annotation.Target;
@Type(name = "DEVICE", value = Device.class),
@Type(name = "RULE_CHAIN", value = RuleChain.class),
@Type(name = "DEVICE_PROFILE", value = DeviceProfile.class),
@Type(name = "ASSET_PROFILE", value = AssetProfile.class),
@Type(name = "ASSET", value = Asset.class),
@Type(name = "DASHBOARD", value = Dashboard.class),
@Type(name = "CUSTOMER", value = Customer.class),

30
common/edge-api/src/main/proto/edge.proto

@ -223,6 +223,19 @@ message DeviceProfileUpdateMsg {
optional int64 firmwareIdLSB = 17;
}
message AssetProfileUpdateMsg {
UpdateMsgType msgType = 1;
int64 idMSB = 2;
int64 idLSB = 3;
string name = 4;
optional string description = 5;
bool default = 6;
int64 defaultRuleChainIdMSB = 7;
int64 defaultRuleChainIdLSB = 8;
string defaultQueueName = 9;
optional bytes image = 10;
}
message DeviceCredentialsUpdateMsg {
int64 deviceIdMSB = 1;
int64 deviceIdLSB = 2;
@ -237,10 +250,12 @@ message AssetUpdateMsg {
int64 idLSB = 3;
optional int64 customerIdMSB = 4;
optional int64 customerIdLSB = 5;
string name = 6;
string type = 7;
optional string label = 8;
optional string additionalInfo = 9;
optional int64 assetProfileIdMSB = 6;
optional int64 assetProfileIdLSB = 7;
string name = 8;
string type = 9;
optional string label = 10;
optional string additionalInfo = 11;
}
message EntityViewUpdateMsg {
@ -389,6 +404,11 @@ message DeviceProfileDevicesRequestMsg {
int64 deviceProfileIdLSB = 2;
}
message AssetProfileAssetsRequestMsg {
int64 assetProfileIdMSB = 1;
int64 assetProfileIdLSB = 2;
}
message WidgetBundleTypesRequestMsg {
int64 widgetBundleIdMSB = 1;
int64 widgetBundleIdLSB = 2;
@ -496,6 +516,7 @@ message UplinkMsg {
repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13;
repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14;
repeated EntityViewsRequestMsg entityViewsRequestMsg = 15;
repeated AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg = 16;
}
message UplinkResponseMsg {
@ -534,5 +555,6 @@ message DownlinkMsg {
repeated DeviceRpcCallMsg deviceRpcCallMsg = 21;
repeated OtaPackageUpdateMsg otaPackageUpdateMsg = 22;
repeated QueueUpdateMsg queueUpdateMsg = 23;
repeated AssetProfileUpdateMsg assetProfileUpdateMsg = 24;
}

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

@ -122,6 +122,16 @@ public final class TbMsg implements Serializable {
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback);
}
public static TbMsg transformMsgData(TbMsg tbMsg, String data) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
}
public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -37,8 +37,12 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.security.cert.X509Certificate;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY;
import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.PSK_CIPHER_SUITES;
import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig;
@ -88,10 +92,10 @@ public class LwM2MTransportBootstrapService {
/* Create and Set DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
dtlsConfig.set(DtlsConfig.DTLS_ROLE, DtlsConfig.DtlsRole.SERVER_ONLY);
dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups());
dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers());
dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, serverConfig.getDtlsRetransmissionTimeout(), MILLISECONDS);
dtlsConfig.set(DTLS_ROLE, SERVER_ONLY);
setServerWithCredentials(builder, dtlsConfig);
/* Set DTLS Config */

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

@ -37,6 +37,10 @@ import java.util.List;
@ConfigurationProperties(prefix = "transport.lwm2m")
public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Getter
@Value("${transport.lwm2m.dtls.retransmission_timeout:9000}")
private int dtlsRetransmissionTimeout;
@Getter
@Value("${transport.lwm2m.timeout:}")
private Long timeout;

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

@ -41,8 +41,12 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PreDestroy;
import java.security.cert.X509Certificate;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_ROLE;
import static org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole.SERVER_ONLY;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256;
@ -127,13 +131,13 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
builder.setSecurityStore(securityStore);
builder.setRegistrationStore(registrationStore);
/* Create DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(config.getPort(), config.getSecurePort(), config));
dtlsConfig.set(DtlsConfig.DTLS_ROLE, DtlsConfig.DtlsRole.SERVER_ONLY);
dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, config.isRecommendedSupportedGroups());
dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, config.isRecommendedCiphers());
dtlsConfig.set(DTLS_RETRANSMISSION_TIMEOUT, config.getDtlsRetransmissionTimeout(), MILLISECONDS);
dtlsConfig.set(DTLS_ROLE, SERVER_ONLY);
/* Create credentials */
this.setServerWithCredentials(builder, dtlsConfig);

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

@ -42,8 +42,6 @@ import java.util.UUID;
*/
public interface AlarmDao extends Dao<Alarm> {
Boolean deleteAlarm(TenantId tenantId, Alarm alarm);
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, UUID key);

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

@ -153,7 +153,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
deleteEntityRelations(tenantId, alarm.getId());
alarmDao.deleteAlarm(tenantId, alarm);
alarmDao.removeById(tenantId, alarm.getUuidId());
return result;
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);

59
dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStats.java

@ -0,0 +1,59 @@
/**
* 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.aspect;
import lombok.Data;
import org.springframework.data.util.Pair;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@Data
public class DbCallStats {
private final TenantId tenantId;
private final ConcurrentMap<String, MethodCallStats> methodStats = new ConcurrentHashMap<>();
private final AtomicInteger successCalls = new AtomicInteger();
private final AtomicInteger failureCalls = new AtomicInteger();
public void onMethodCall(String methodName, boolean success, long executionTime) {
var methodCallStats = methodStats.computeIfAbsent(methodName, m -> new MethodCallStats());
methodCallStats.getExecutions().incrementAndGet();
methodCallStats.getTiming().addAndGet(executionTime);
if (success) {
successCalls.incrementAndGet();
} else {
failureCalls.incrementAndGet();
methodCallStats.getFailures().incrementAndGet();
}
}
public DbCallStatsSnapshot snapshot() {
return DbCallStatsSnapshot.builder()
.tenantId(tenantId)
.totalSuccess(successCalls.get())
.totalFailure(failureCalls.get())
.methodStats(methodStats.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().snapshot())))
.totalTiming(methodStats.values().stream().map(MethodCallStats::getTiming).map(AtomicLong::get).reduce(0L, Long::sum))
.build();
}
}

38
dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStatsSnapshot.java

@ -0,0 +1,38 @@
/**
* 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.aspect;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Map;
@Data
@Builder
public class DbCallStatsSnapshot {
private final TenantId tenantId;
private final int totalSuccess;
private final int totalFailure;
private final long totalTiming;
private final Map<String, MethodCallStatsSnapshot> methodStats;
public int getTotalCalls() {
return totalSuccess + totalFailure;
}
}

33
dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStats.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.aspect;
import lombok.Data;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Data
public class MethodCallStats {
private final AtomicInteger executions = new AtomicInteger();
private final AtomicInteger failures = new AtomicInteger();
private final AtomicLong timing = new AtomicLong();
public MethodCallStatsSnapshot snapshot() {
return new MethodCallStatsSnapshot(executions.get(), failures.get(), timing.get());
}
}

25
dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStatsSnapshot.java

@ -0,0 +1,25 @@
/**
* 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.aspect;
import lombok.Data;
@Data
public class MethodCallStatsSnapshot {
private final int executions;
private final int failures;
private final long timing;
}

214
dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java

@ -0,0 +1,214 @@
/**
* 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.aspect;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.exception.JDBCConnectionException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Aspect
@ConditionalOnProperty(prefix = "sql", value = "log_tenant_stats", havingValue = "true")
@Component
@Slf4j
public class SqlDaoCallsAspect {
private final Set<String> invalidTenantDbCallMethods = ConcurrentHashMap.newKeySet();
private final ConcurrentMap<TenantId, DbCallStats> statsMap = new ConcurrentHashMap<>();
@Scheduled(initialDelayString = "${sql.log_tenant_stats_interval_ms:60000}",
fixedDelayString = "${sql.log_tenant_stats_interval_ms:60000}")
public void printStats() {
List<DbCallStatsSnapshot> snapshots = snapshot();
if (snapshots.isEmpty()) return;
try {
if (log.isTraceEnabled()) {
logTopNTenants(snapshots, Comparator.comparing(DbCallStatsSnapshot::getTotalTiming).reversed(), 0, snapshot -> {
logSnapshot(snapshot, 0, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), log::trace);
});
Map<String, Map<TenantId, MethodCallStatsSnapshot>> byMethodStats = new HashMap<>();
for (DbCallStatsSnapshot snapshot : snapshots) {
snapshot.getMethodStats().forEach((method, stats) -> {
byMethodStats.computeIfAbsent(method, m -> new HashMap<>())
.put(snapshot.getTenantId(), stats);
});
}
byMethodStats.forEach((method, byTenantStats) -> {
log.trace("Top tenants for method {} by calls:", method);
byTenantStats.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.comparing(MethodCallStatsSnapshot::getExecutions).reversed()))
.limit(10)
.forEach(e -> {
TenantId tenantId = e.getKey();
MethodCallStatsSnapshot methodStats = e.getValue();
log.trace("[{}] calls: {}, failures: {}, timing: {}", tenantId,
methodStats.getExecutions(), methodStats.getFailures(), methodStats.getTiming());
});
});
} else if (log.isDebugEnabled()) {
log.debug("Total calls statistics below:");
logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalCalls).reversed(), 10,
s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getExecutions).reversed(), log::debug));
log.debug("Total timing statistics below:");
logTopNTenants(snapshots, Comparator.comparingLong(DbCallStatsSnapshot::getTotalTiming).reversed(),
10, s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), log::debug));
log.debug("Total errors statistics below:");
logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(),
10, s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), log::debug));
} else if (log.isInfoEnabled()) {
log.info("Total calls statistics below:");
logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(),
3, s -> logSnapshot(s, 3, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), log::info));
}
} finally {
statsMap.clear();
}
}
private void logSnapshot(DbCallStatsSnapshot snapshot, int limit, Comparator<MethodCallStatsSnapshot> methodStatsComparator, Consumer<String> logger) {
logger.accept(String.format("[%s]: calls: %s, failures: %s, exec time: %s ",
snapshot.getTenantId(), snapshot.getTotalCalls(), snapshot.getTotalFailure(), snapshot.getTotalTiming()));
var stream = snapshot.getMethodStats().entrySet().stream()
.sorted(Map.Entry.comparingByValue(methodStatsComparator));
if (limit > 0) {
stream = stream.limit(limit);
}
stream.forEach(e -> {
MethodCallStatsSnapshot methodStats = e.getValue();
logger.accept(String.format("[%s]: method: %s, calls: %s, failures: %s, exec time: %s", snapshot.getTenantId(), e.getKey(),
methodStats.getExecutions(), methodStats.getFailures(), methodStats.getTiming()));
});
}
private List<DbCallStatsSnapshot> snapshot() {
return statsMap.values().stream().map(DbCallStats::snapshot).collect(Collectors.toList());
}
private void logTopNTenants(List<DbCallStatsSnapshot> snapshots, Comparator<DbCallStatsSnapshot> comparator,
int n, Consumer<DbCallStatsSnapshot> logFunction) {
var stream = snapshots.stream().sorted(comparator);
if (n > 0) {
stream = stream.limit(n);
}
stream.forEach(logFunction);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Around("@within(org.thingsboard.server.dao.util.SqlDao)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
var methodName = signature.toShortString();
if (invalidTenantDbCallMethods.contains(methodName)) {
//Simply call the method if tenant is not found
return joinPoint.proceed();
}
var tenantId = getTenantId(signature, methodName, joinPoint.getArgs());
if (tenantId == null || tenantId.isNullUid()) {
//Simply call the method if tenant is null
return joinPoint.proceed();
}
var startTime = System.currentTimeMillis();
try {
var result = joinPoint.proceed();
if (result instanceof ListenableFuture) {
Futures.addCallback((ListenableFuture) result,
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Object result) {
logTenantMethodExecution(tenantId, methodName, true, startTime, null);
}
@Override
public void onFailure(Throwable t) {
logTenantMethodExecution(tenantId, methodName, false, startTime, t);
}
},
MoreExecutors.directExecutor());
} else {
logTenantMethodExecution(tenantId, methodName, true, startTime, null);
}
return result;
} catch (Throwable t) {
logTenantMethodExecution(tenantId, methodName, false, startTime, t);
throw t;
}
}
private void logTenantMethodExecution(TenantId tenantId, String method, boolean success, long startTime, Throwable t) {
if (!success && ExceptionUtils.indexOfThrowable(t, JDBCConnectionException.class) >= 0) {
return;
}
statsMap.computeIfAbsent(tenantId, DbCallStats::new)
.onMethodCall(method, success, System.currentTimeMillis() - startTime);
}
TenantId getTenantId(MethodSignature signature, String methodName, Object[] args) {
if (args == null || args.length == 0) {
addAndLogInvalidMethods(methodName);
return null;
}
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof TenantId) {
return (TenantId) arg;
} else if (arg instanceof UUID) {
if (signature.getParameterNames() != null && StringUtils.equals(signature.getParameterNames()[i], "tenantId")) {
log.trace("Method {} uses UUID for tenantId param instead of TenantId class", methodName);
return TenantId.fromUUID((UUID) arg);
}
}
}
if (ArrayUtils.contains(signature.getParameterTypes(), TenantId.class) ||
ArrayUtils.contains(signature.getParameterNames(), "tenantId")) {
log.debug("Null was submitted as tenantId to method {}. Args: {}", methodName, Arrays.toString(args));
} else {
addAndLogInvalidMethods(methodName);
}
return null;
}
private void addAndLogInvalidMethods(String methodName) {
invalidTenantDbCallMethods.add(methodName);
}
}

33
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java

@ -92,6 +92,16 @@ public interface AssetDao extends Dao<Asset>, TenantEntityDao, ExportableEntityD
*/
PageData<AssetInfo> findAssetInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink);
/**
* Find asset infos by tenantId, assetProfileId and page link.
*
* @param tenantId the tenantId
* @param assetProfileId the assetProfileId
* @param pageLink the page link
* @return the list of asset info objects
*/
PageData<AssetInfo> findAssetInfosByTenantIdAndAssetProfileId(UUID tenantId, UUID assetProfileId, PageLink pageLink);
/**
* Find assets by tenantId and assets Ids.
*
@ -143,6 +153,17 @@ public interface AssetDao extends Dao<Asset>, TenantEntityDao, ExportableEntityD
*/
PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink);
/**
* Find asset infos by tenantId, customerId, assetProfileId and page link.
*
* @param tenantId the tenantId
* @param customerId the customerId
* @param assetProfileId the assetProfileId
* @param pageLink the page link
* @return the list of asset info objects
*/
PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(UUID tenantId, UUID customerId, UUID assetProfileId, PageLink pageLink);
/**
* Find assets by tenantId, customerId and assets Ids.
*
@ -169,6 +190,18 @@ public interface AssetDao extends Dao<Asset>, TenantEntityDao, ExportableEntityD
*/
ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId);
Long countAssetsByAssetProfileId(TenantId tenantId, UUID assetProfileId);
/**
* Find assets by tenantId, profileId and page link.
*
* @param tenantId the tenantId
* @param profileId the profileId
* @param pageLink the page link
* @return the list of device objects
*/
PageData<Asset> findAssetsByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink);
/**
* Find assets by tenantId, edgeId and page link.
*

63
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java

@ -0,0 +1,63 @@
/**
* 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.asset;
import lombok.Data;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import java.io.Serializable;
@Data
public class AssetProfileCacheKey implements Serializable {
private static final long serialVersionUID = 8220455917177676472L;
private final TenantId tenantId;
private final String name;
private final AssetProfileId assetProfileId;
private final boolean defaultProfile;
private AssetProfileCacheKey(TenantId tenantId, String name, AssetProfileId assetProfileId, boolean defaultProfile) {
this.tenantId = tenantId;
this.name = name;
this.assetProfileId = assetProfileId;
this.defaultProfile = defaultProfile;
}
public static AssetProfileCacheKey fromName(TenantId tenantId, String name) {
return new AssetProfileCacheKey(tenantId, name, null, false);
}
public static AssetProfileCacheKey fromId(AssetProfileId id) {
return new AssetProfileCacheKey(null, null, id, false);
}
public static AssetProfileCacheKey defaultProfile(TenantId tenantId) {
return new AssetProfileCacheKey(tenantId, null, null, true);
}
@Override
public String toString() {
if (assetProfileId != null) {
return assetProfileId.toString();
} else if (defaultProfile) {
return tenantId.toString();
} else {
return tenantId + "_" + name;
}
}
}

33
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.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.asset;
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.asset.AssetProfile;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("AssetProfileCache")
public class AssetProfileCaffeineCache extends CaffeineTbTransactionalCache<AssetProfileCacheKey, AssetProfile> {
public AssetProfileCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.ASSET_PROFILE_CACHE);
}
}

46
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java

@ -0,0 +1,46 @@
/**
* 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.asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.UUID;
public interface AssetProfileDao extends Dao<AssetProfile>, ExportableEntityDao<AssetProfileId, AssetProfile> {
AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, UUID assetProfileId);
AssetProfile save(TenantId tenantId, AssetProfile assetProfile);
AssetProfile saveAndFlush(TenantId tenantId, AssetProfile assetProfile);
PageData<AssetProfile> findAssetProfiles(TenantId tenantId, PageLink pageLink);
PageData<AssetProfileInfo> findAssetProfileInfos(TenantId tenantId, PageLink pageLink);
AssetProfile findDefaultAssetProfile(TenantId tenantId);
AssetProfileInfo findDefaultAssetProfileInfo(TenantId tenantId);
AssetProfile findByName(TenantId tenantId, String profileName);
}

31
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.asset;
import lombok.Data;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class AssetProfileEvictEvent {
private final TenantId tenantId;
private final String newName;
private final String oldName;
private final AssetProfileId assetProfileId;
private final boolean defaultProfile;
}

35
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.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.asset;
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.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.asset.AssetProfile;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("AssetProfileCache")
public class AssetProfileRedisCache extends RedisTbTransactionalCache<AssetProfileCacheKey, AssetProfile> {
public AssetProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

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

@ -0,0 +1,286 @@
/**
* 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.asset;
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.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetProfileCacheKey, AssetProfile, AssetProfileEvictEvent> implements AssetProfileService {
private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private static final String INCORRECT_ASSET_PROFILE_ID = "Incorrect assetProfileId ";
private static final String INCORRECT_ASSET_PROFILE_NAME = "Incorrect assetProfileName ";
private static final String ASSET_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS = "Asset profile with such name already exists!";
@Autowired
private AssetProfileDao assetProfileDao;
@Autowired
private AssetDao assetDao;
@Autowired
private AssetService assetService;
@Autowired
private DataValidator<AssetProfile> assetProfileValidator;
@TransactionalEventListener(classes = AssetProfileEvictEvent.class)
@Override
public void handleEvictEvent(AssetProfileEvictEvent event) {
List<AssetProfileCacheKey> keys = new ArrayList<>(2);
keys.add(AssetProfileCacheKey.fromName(event.getTenantId(), event.getNewName()));
if (event.getAssetProfileId() != null) {
keys.add(AssetProfileCacheKey.fromId(event.getAssetProfileId()));
}
if (event.isDefaultProfile()) {
keys.add(AssetProfileCacheKey.defaultProfile(event.getTenantId()));
}
if (StringUtils.isNotEmpty(event.getOldName()) && !event.getOldName().equals(event.getNewName())) {
keys.add(AssetProfileCacheKey.fromName(event.getTenantId(), event.getOldName()));
}
cache.evict(keys);
}
@Override
public AssetProfile findAssetProfileById(TenantId tenantId, AssetProfileId assetProfileId) {
log.trace("Executing findAssetProfileById [{}]", assetProfileId);
Validator.validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
return cache.getAndPutInTransaction(AssetProfileCacheKey.fromId(assetProfileId),
() -> assetProfileDao.findById(tenantId, assetProfileId.getId()), true);
}
@Override
public AssetProfile findAssetProfileByName(TenantId tenantId, String profileName) {
log.trace("Executing findAssetProfileByName [{}][{}]", tenantId, profileName);
Validator.validateString(profileName, INCORRECT_ASSET_PROFILE_NAME + profileName);
return cache.getAndPutInTransaction(AssetProfileCacheKey.fromName(tenantId, profileName),
() -> assetProfileDao.findByName(tenantId, profileName), true);
}
@Override
public AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, AssetProfileId assetProfileId) {
log.trace("Executing findAssetProfileInfoById [{}]", assetProfileId);
Validator.validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
return toAssetProfileInfo(findAssetProfileById(tenantId, assetProfileId));
}
@Override
public AssetProfile saveAssetProfile(AssetProfile assetProfile) {
log.trace("Executing saveAssetProfile [{}]", assetProfile);
AssetProfile oldAssetProfile = assetProfileValidator.validate(assetProfile, AssetProfile::getTenantId);
AssetProfile savedAssetProfile;
try {
savedAssetProfile = assetProfileDao.saveAndFlush(assetProfile.getTenantId(), assetProfile);
publishEvictEvent(new AssetProfileEvictEvent(savedAssetProfile.getTenantId(), savedAssetProfile.getName(),
oldAssetProfile != null ? oldAssetProfile.getName() : null, savedAssetProfile.getId(), savedAssetProfile.isDefault()));
} catch (Exception t) {
handleEvictEvent(new AssetProfileEvictEvent(assetProfile.getTenantId(), assetProfile.getName(),
oldAssetProfile != null ? oldAssetProfile.getName() : null, null, assetProfile.isDefault()));
checkConstraintViolation(t,
Map.of("asset_profile_name_unq_key", ASSET_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS,
"asset_profile_external_id_unq_key", "Asset profile with such external id already exists!"));
throw t;
}
if (oldAssetProfile != null && !oldAssetProfile.getName().equals(assetProfile.getName())) {
PageLink pageLink = new PageLink(100);
PageData<Asset> pageData;
do {
pageData = assetDao.findAssetsByTenantIdAndProfileId(assetProfile.getTenantId().getId(), assetProfile.getUuidId(), pageLink);
for (Asset asset : pageData.getData()) {
asset.setType(assetProfile.getName());
assetService.saveAsset(asset);
}
pageLink = pageLink.nextPageLink();
} while (pageData.hasNext());
}
return savedAssetProfile;
}
@Override
@Transactional
public void deleteAssetProfile(TenantId tenantId, AssetProfileId assetProfileId) {
log.trace("Executing deleteAssetProfile [{}]", assetProfileId);
Validator.validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
AssetProfile assetProfile = assetProfileDao.findById(tenantId, assetProfileId.getId());
if (assetProfile != null && assetProfile.isDefault()) {
throw new DataValidationException("Deletion of Default Asset Profile is prohibited!");
}
this.removeAssetProfile(tenantId, assetProfile);
}
private void removeAssetProfile(TenantId tenantId, AssetProfile assetProfile) {
AssetProfileId assetProfileId = assetProfile.getId();
try {
deleteEntityRelations(tenantId, assetProfileId);
assetProfileDao.removeById(tenantId, assetProfileId.getId());
publishEvictEvent(new AssetProfileEvictEvent(assetProfile.getTenantId(), assetProfile.getName(),
null, assetProfile.getId(), assetProfile.isDefault()));
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_asset_profile")) {
throw new DataValidationException("The asset profile referenced by the assets cannot be deleted!");
} else {
throw t;
}
}
}
@Override
public PageData<AssetProfile> findAssetProfiles(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAssetProfiles tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validatePageLink(pageLink);
return assetProfileDao.findAssetProfiles(tenantId, pageLink);
}
@Override
public PageData<AssetProfileInfo> findAssetProfileInfos(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAssetProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validatePageLink(pageLink);
return assetProfileDao.findAssetProfileInfos(tenantId, pageLink);
}
@Override
public AssetProfile findOrCreateAssetProfile(TenantId tenantId, String name) {
log.trace("Executing findOrCreateAssetProfile");
AssetProfile assetProfile = findAssetProfileByName(tenantId, name);
if (assetProfile == null) {
try {
assetProfile = this.doCreateDefaultAssetProfile(tenantId, name, name.equals("default"));
} catch (DataValidationException e) {
if (ASSET_PROFILE_WITH_SUCH_NAME_ALREADY_EXISTS.equals(e.getMessage())) {
assetProfile = findAssetProfileByName(tenantId, name);
} else {
throw e;
}
}
}
return assetProfile;
}
@Override
public AssetProfile createDefaultAssetProfile(TenantId tenantId) {
log.trace("Executing createDefaultAssetProfile tenantId [{}]", tenantId);
return doCreateDefaultAssetProfile(tenantId, "default", true);
}
private AssetProfile doCreateDefaultAssetProfile(TenantId tenantId, String profileName, boolean defaultProfile) {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
AssetProfile assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfile.setDefault(defaultProfile);
assetProfile.setName(profileName);
assetProfile.setDescription("Default asset profile");
return saveAssetProfile(assetProfile);
}
@Override
public AssetProfile findDefaultAssetProfile(TenantId tenantId) {
log.trace("Executing findDefaultAssetProfile tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return cache.getAndPutInTransaction(AssetProfileCacheKey.defaultProfile(tenantId),
() -> assetProfileDao.findDefaultAssetProfile(tenantId), true);
}
@Override
public AssetProfileInfo findDefaultAssetProfileInfo(TenantId tenantId) {
log.trace("Executing findDefaultAssetProfileInfo tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return toAssetProfileInfo(findDefaultAssetProfile(tenantId));
}
@Override
public boolean setDefaultAssetProfile(TenantId tenantId, AssetProfileId assetProfileId) {
log.trace("Executing setDefaultAssetProfile [{}]", assetProfileId);
Validator.validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
AssetProfile assetProfile = assetProfileDao.findById(tenantId, assetProfileId.getId());
if (!assetProfile.isDefault()) {
assetProfile.setDefault(true);
AssetProfile previousDefaultAssetProfile = findDefaultAssetProfile(tenantId);
boolean changed = false;
if (previousDefaultAssetProfile == null) {
assetProfileDao.save(tenantId, assetProfile);
publishEvictEvent(new AssetProfileEvictEvent(assetProfile.getTenantId(), assetProfile.getName(), null, assetProfile.getId(), true));
changed = true;
} else if (!previousDefaultAssetProfile.getId().equals(assetProfile.getId())) {
previousDefaultAssetProfile.setDefault(false);
assetProfileDao.save(tenantId, previousDefaultAssetProfile);
assetProfileDao.save(tenantId, assetProfile);
publishEvictEvent(new AssetProfileEvictEvent(previousDefaultAssetProfile.getTenantId(), previousDefaultAssetProfile.getName(), null, previousDefaultAssetProfile.getId(), false));
publishEvictEvent(new AssetProfileEvictEvent(assetProfile.getTenantId(), assetProfile.getName(), null, assetProfile.getId(), true));
changed = true;
}
return changed;
}
return false;
}
@Override
public void deleteAssetProfilesByTenantId(TenantId tenantId) {
log.trace("Executing deleteAssetProfilesByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
tenantAssetProfilesRemover.removeEntities(tenantId, tenantId);
}
private PaginatedRemover<TenantId, AssetProfile> tenantAssetProfilesRemover =
new PaginatedRemover<>() {
@Override
protected PageData<AssetProfile> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return assetProfileDao.findAssetProfiles(id, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, AssetProfile entity) {
removeAssetProfile(tenantId, entity);
}
};
private AssetProfileInfo toAssetProfileInfo(AssetProfile profile) {
return profile == null ? null : new AssetProfileInfo(profile.getId(), profile.getName(), profile.getImage(),
profile.getDefaultDashboardId());
}
}

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

@ -30,9 +30,11 @@ import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
@ -64,6 +66,8 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey, Asset, AssetCacheEvictEvent> implements AssetService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_ASSET_PROFILE_ID = "Incorrect assetProfileId ";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_ASSET_ID = "Incorrect assetId ";
public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
@ -71,6 +75,9 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
@Autowired
private AssetDao assetDao;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private DataValidator<Asset> assetValidator;
@ -122,6 +129,24 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
Asset savedAsset;
AssetCacheEvictEvent evictEvent = new AssetCacheEvictEvent(asset.getTenantId(), asset.getName(), oldAsset != null ? oldAsset.getName() : null);
try {
AssetProfile assetProfile;
if (asset.getAssetProfileId() == null) {
if (!StringUtils.isEmpty(asset.getType())) {
assetProfile = this.assetProfileService.findOrCreateAssetProfile(asset.getTenantId(), asset.getType());
} else {
assetProfile = this.assetProfileService.findDefaultAssetProfile(asset.getTenantId());
}
asset.setAssetProfileId(new AssetProfileId(assetProfile.getId().getId()));
} else {
assetProfile = this.assetProfileService.findAssetProfileById(asset.getTenantId(), asset.getAssetProfileId());
if (assetProfile == null) {
throw new DataValidationException("Asset is referencing non existing asset profile!");
}
if (!assetProfile.getTenantId().equals(asset.getTenantId())) {
throw new DataValidationException("Asset can`t be referencing to asset profile from different tenant!");
}
}
asset.setType(assetProfile.getName());
savedAsset = assetDao.saveAndFlush(asset.getTenantId(), asset);
publishEvictEvent(evictEvent);
} catch (Exception t) {
@ -200,6 +225,15 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return assetDao.findAssetInfosByTenantIdAndType(tenantId.getId(), type, pageLink);
}
@Override
public PageData<AssetInfo> findAssetInfosByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink) {
log.trace("Executing findAssetInfosByTenantIdAndAssetProfileId, tenantId [{}], assetProfileId [{}], pageLink [{}]", tenantId, assetProfileId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
validatePageLink(pageLink);
return assetDao.findAssetInfosByTenantIdAndAssetProfileId(tenantId.getId(), assetProfileId.getId(), pageLink);
}
@Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdAndIdsAsync, tenantId [{}], assetIds [{}]", tenantId, assetIds);
@ -253,6 +287,16 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return assetDao.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
}
@Override
public PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(TenantId tenantId, CustomerId customerId, AssetProfileId assetProfileId, PageLink pageLink) {
log.trace("Executing findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId, tenantId [{}], customerId [{}], assetProfileId [{}], pageLink [{}]", tenantId, customerId, assetProfileId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
validateId(assetProfileId, INCORRECT_ASSET_PROFILE_ID + assetProfileId);
validatePageLink(pageLink);
return assetDao.findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(tenantId.getId(), customerId.getId(), assetProfileId.getId(), pageLink);
}
@Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdAndCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], assetIds [{}]", tenantId, customerId, assetIds);

7
dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java

@ -79,6 +79,13 @@ public class BaseAttributesService implements AttributesService {
return attributesDao.findAllKeysByEntityIds(tenantId, entityType, entityIds);
}
@Override
public ListenableFuture<String> save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) {
validate(entityId, scope);
AttributeUtils.validate(attribute);
return attributesDao.save(tenantId, entityId, scope, attribute);
}
@Override
public ListenableFuture<List<String>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope);

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

Loading…
Cancel
Save