Browse Source

Merge branch 'master' into feature/math-rule-node

pull/7348/head
Igor Kulikov 4 years ago
parent
commit
f14a337c73
  1. 21
      application/src/main/data/upgrade/3.4.1/schema_update_after.sql
  2. 45
      application/src/main/data/upgrade/3.4.1/schema_update_before.sql
  3. 10
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 44
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 16
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  6. 227
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  7. 25
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  8. 10
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  9. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  10. 3
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.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. 55
      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. 9
      application/src/main/resources/thingsboard.yml
  44. 10
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  45. 26
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  46. 452
      application/src/test/java/org/thingsboard/server/controller/BaseAssetProfileControllerTest.java
  47. 7
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  48. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AssetProfileControllerSqlTest.java
  49. 2
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  50. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  51. 24
      application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java
  52. 97
      application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
  53. 17
      common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
  54. 53
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java
  55. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  56. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
  57. 2
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  58. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
  59. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  60. 19
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  61. 11
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
  62. 118
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java
  63. 62
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java
  64. 1
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  65. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java
  66. 4
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  67. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  68. 30
      common/edge-api/src/main/proto/edge.proto
  69. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  70. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  71. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  72. 33
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  73. 63
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java
  74. 33
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java
  75. 46
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java
  76. 31
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java
  77. 35
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java
  78. 286
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  79. 44
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  80. 13
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  81. 1
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  82. 1
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  83. 15
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  84. 11
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java
  85. 8
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java
  86. 136
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java
  87. 2
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  88. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  89. 109
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java
  90. 63
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java
  91. 62
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  92. 36
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  93. 126
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java
  94. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  95. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java
  96. 6
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  97. 51
      dao/src/main/resources/sql/schema-entities.sql
  98. 15
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  99. 279
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetProfileServiceTest.java
  100. 160
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java

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")

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

@ -225,6 +225,9 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.4.0 to 3.4.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.0");
dataUpdateService.updateData("3.4.0");
case "3.4.1":
log.info("Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;

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

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

@ -39,6 +39,8 @@ import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
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;
@ -117,9 +119,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;
@ -609,6 +617,53 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e);
}
break;
case "3.4.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
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);
}

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

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

@ -406,6 +406,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}"
@ -561,6 +564,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}"
@ -710,6 +714,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
@ -747,6 +753,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}"

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

@ -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;
@ -448,6 +449,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);

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

@ -30,6 +30,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;
@ -187,6 +188,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 +325,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

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

@ -0,0 +1,452 @@
/**
* 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.Mockito;
import org.springframework.boot.test.mock.mockito.SpyBean;
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;
public abstract class BaseAssetProfileControllerTest extends AbstractControllerTest {
private IdComparator<AssetProfile> idComparator = new IdComparator<>();
private IdComparator<AssetProfileInfo> assetProfileInfoIdComparator = new IdComparator<>();
private Tenant savedTenant;
private User tenantAdmin;
@SpyBean
private AssetProfileDao 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();
afterTestEntityDaoRemoveByIdWithException (assetProfileDao);
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);
}
}

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

@ -44,6 +44,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;
@ -808,7 +809,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 +817,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);

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

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

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

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

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

13
dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.dao.device;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.util.SecurityUtil;
@ -136,6 +138,17 @@ public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService<St
}
}
@Override
public JsonNode toCredentialsInfo(DeviceCredentials deviceCredentials) {
switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN:
return JacksonUtil.valueToTree(deviceCredentials.getCredentialsId());
case X509_CERTIFICATE:
return JacksonUtil.valueToTree(deviceCredentials.getCredentialsValue());
}
return JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), JsonNode.class);
}
private void formatSimpleMqttCredentials(DeviceCredentials deviceCredentials) {
BasicMqttCredentials mqttCredentials;
try {

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

@ -402,6 +402,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
switch (entityId.getEntityType()) {
case TENANT:
case DEVICE_PROFILE:
case ASSET_PROFILE:
case OTA_PACKAGE:
return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink));
case CUSTOMER:

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

@ -188,6 +188,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
case WIDGET_TYPE:
case TENANT_PROFILE:
case DEVICE_PROFILE:
case ASSET_PROFILE:
case API_USAGE_STATE:
case TB_RESOURCE:
case OTA_PACKAGE:

15
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -186,6 +186,19 @@ public class ModelConstants {
public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id";
public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id";
/**
* Asset profile constants.
*/
public static final String ASSET_PROFILE_COLUMN_FAMILY_NAME = "asset_profile";
public static final String ASSET_PROFILE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ASSET_PROFILE_NAME_PROPERTY = "name";
public static final String ASSET_PROFILE_IMAGE_PROPERTY = "image";
public static final String ASSET_PROFILE_DESCRIPTION_PROPERTY = "description";
public static final String ASSET_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
public static final String ASSET_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
public static final String ASSET_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY = "default_dashboard_id";
public static final String ASSET_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
/**
* Cassandra entityView constants.
*/
@ -242,6 +255,8 @@ public class ModelConstants {
public static final String ASSET_LABEL_PROPERTY = "label";
public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ASSET_ASSET_PROFILE_ID_PROPERTY = "asset_profile_id";
public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text";
public static final String ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_by_type_and_search_text";
public static final String ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_and_search_text";

11
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java

@ -22,6 +22,7 @@ import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.asset.Asset;
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.dao.model.BaseSqlEntity;
@ -69,6 +70,9 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
@Column(name = ModelConstants.ASSET_ASSET_PROFILE_ID_PROPERTY, columnDefinition = "uuid")
private UUID assetProfileId;
@Column(name = EXTERNAL_ID_PROPERTY)
private UUID externalId;
@ -87,6 +91,9 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
if (asset.getCustomerId() != null) {
this.customerId = asset.getCustomerId().getId();
}
if (asset.getAssetProfileId() != null) {
this.assetProfileId = asset.getAssetProfileId().getId();
}
this.name = asset.getName();
this.type = asset.getType();
this.label = asset.getLabel();
@ -101,6 +108,7 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
this.setCreatedTime(assetEntity.getCreatedTime());
this.tenantId = assetEntity.getTenantId();
this.customerId = assetEntity.getCustomerId();
this.assetProfileId = assetEntity.getAssetProfileId();
this.type = assetEntity.getType();
this.name = assetEntity.getName();
this.label = assetEntity.getLabel();
@ -132,6 +140,9 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
if (customerId != null) {
asset.setCustomerId(new CustomerId(customerId));
}
if (assetProfileId != null) {
asset.setAssetProfileId(new AssetProfileId(assetProfileId));
}
asset.setName(name);
asset.setType(type);
asset.setLabel(label);

8
dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java

@ -30,10 +30,12 @@ public class AssetInfoEntity extends AbstractAssetEntity<AssetInfo> {
public static final Map<String,String> assetInfoColumnMap = new HashMap<>();
static {
assetInfoColumnMap.put("customerTitle", "c.title");
assetInfoColumnMap.put("assetProfileName", "p.name");
}
private String customerTitle;
private boolean customerIsPublic;
private String assetProfileName;
public AssetInfoEntity() {
super();
@ -41,7 +43,8 @@ public class AssetInfoEntity extends AbstractAssetEntity<AssetInfo> {
public AssetInfoEntity(AssetEntity assetEntity,
String customerTitle,
Object customerAdditionalInfo) {
Object customerAdditionalInfo,
String assetProfileName) {
super(assetEntity);
this.customerTitle = customerTitle;
if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) {
@ -49,10 +52,11 @@ public class AssetInfoEntity extends AbstractAssetEntity<AssetInfo> {
} else {
this.customerIsPublic = false;
}
this.assetProfileName = assetProfileName;
}
@Override
public AssetInfo toData() {
return new AssetInfo(super.toAsset(), customerTitle, customerIsPublic);
return new AssetInfo(super.toAsset(), customerTitle, customerIsPublic, assetProfileName);
}
}

136
dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java

@ -0,0 +1,136 @@
/**
* 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.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.asset.AssetProfile;
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.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ModelConstants.ASSET_PROFILE_COLUMN_FAMILY_NAME)
public final class AssetProfileEntity extends BaseSqlEntity<AssetProfile> implements SearchTextEntity<AssetProfile> {
@Column(name = ModelConstants.ASSET_PROFILE_TENANT_ID_PROPERTY)
private UUID tenantId;
@Column(name = ModelConstants.ASSET_PROFILE_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.ASSET_PROFILE_IMAGE_PROPERTY)
private String image;
@Column(name = ModelConstants.ASSET_PROFILE_DESCRIPTION_PROPERTY)
private String description;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = ModelConstants.ASSET_PROFILE_IS_DEFAULT_PROPERTY)
private boolean isDefault;
@Column(name = ModelConstants.ASSET_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid")
private UUID defaultRuleChainId;
@Column(name = ModelConstants.ASSET_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY)
private UUID defaultDashboardId;
@Column(name = ModelConstants.ASSET_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY)
private String defaultQueueName;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
public AssetProfileEntity() {
super();
}
public AssetProfileEntity(AssetProfile assetProfile) {
if (assetProfile.getId() != null) {
this.setUuid(assetProfile.getId().getId());
}
if (assetProfile.getTenantId() != null) {
this.tenantId = assetProfile.getTenantId().getId();
}
this.setCreatedTime(assetProfile.getCreatedTime());
this.name = assetProfile.getName();
this.image = assetProfile.getImage();
this.description = assetProfile.getDescription();
this.isDefault = assetProfile.isDefault();
if (assetProfile.getDefaultRuleChainId() != null) {
this.defaultRuleChainId = assetProfile.getDefaultRuleChainId().getId();
}
if (assetProfile.getDefaultDashboardId() != null) {
this.defaultDashboardId = assetProfile.getDefaultDashboardId().getId();
}
this.defaultQueueName = assetProfile.getDefaultQueueName();
if (assetProfile.getExternalId() != null) {
this.externalId = assetProfile.getExternalId().getId();
}
}
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
public String getSearchText() {
return searchText;
}
@Override
public AssetProfile toData() {
AssetProfile assetProfile = new AssetProfile(new AssetProfileId(this.getUuid()));
assetProfile.setCreatedTime(createdTime);
if (tenantId != null) {
assetProfile.setTenantId(TenantId.fromUUID(tenantId));
}
assetProfile.setName(name);
assetProfile.setImage(image);
assetProfile.setDescription(description);
assetProfile.setDefault(isDefault);
assetProfile.setDefaultQueueName(defaultQueueName);
if (defaultRuleChainId != null) {
assetProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId));
}
if (defaultDashboardId != null) {
assetProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId));
}
if (externalId != null) {
assetProfile.setExternalId(new AssetProfileId(externalId));
}
return assetProfile;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -35,7 +35,7 @@ import java.util.regex.Pattern;
@Slf4j
public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
Pattern.compile("^[A-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE);
private static final Pattern QUEUE_PATTERN = Pattern.compile("^[a-zA-Z0-9_.\\-]+$");

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

@ -73,9 +73,6 @@ public class AssetDataValidator extends DataValidator<Asset> {
@Override
protected void validateDataImpl(TenantId tenantId, Asset asset) {
if (StringUtils.isEmpty(asset.getType())) {
throw new DataValidationException("Asset type should be specified!");
}
if (StringUtils.isEmpty(asset.getName())) {
throw new DataValidationException("Asset name should be specified!");
}

109
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java

@ -0,0 +1,109 @@
/**
* 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.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.asset.AssetProfileDao;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantService;
@Component
public class AssetProfileDataValidator extends DataValidator<AssetProfile> {
@Autowired
private AssetProfileDao assetProfileDao;
@Autowired
@Lazy
private AssetProfileService assetProfileService;
@Autowired
private TenantService tenantService;
@Lazy
@Autowired
private QueueService queueService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private DashboardService dashboardService;
@Override
protected void validateDataImpl(TenantId tenantId, AssetProfile assetProfile) {
if (StringUtils.isEmpty(assetProfile.getName())) {
throw new DataValidationException("Asset profile name should be specified!");
}
if (assetProfile.getTenantId() == null) {
throw new DataValidationException("Asset profile should be assigned to tenant!");
} else {
if (!tenantService.tenantExists(assetProfile.getTenantId())) {
throw new DataValidationException("Asset profile is referencing to non-existent tenant!");
}
}
if (assetProfile.isDefault()) {
AssetProfile defaultAssetProfile = assetProfileService.findDefaultAssetProfile(tenantId);
if (defaultAssetProfile != null && !defaultAssetProfile.getId().equals(assetProfile.getId())) {
throw new DataValidationException("Another default asset profile is present in scope of current tenant!");
}
}
if (StringUtils.isNotEmpty(assetProfile.getDefaultQueueName())) {
Queue queue = queueService.findQueueByTenantIdAndName(tenantId, assetProfile.getDefaultQueueName());
if (queue == null) {
throw new DataValidationException("Asset profile is referencing to non-existent queue!");
}
}
if (assetProfile.getDefaultRuleChainId() != null) {
RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, assetProfile.getDefaultRuleChainId());
if (ruleChain == null) {
throw new DataValidationException("Can't assign non-existent rule chain!");
}
if (!ruleChain.getTenantId().equals(assetProfile.getTenantId())) {
throw new DataValidationException("Can't assign rule chain from different tenant!");
}
}
if (assetProfile.getDefaultDashboardId() != null) {
DashboardInfo dashboard = dashboardService.findDashboardInfoById(tenantId, assetProfile.getDefaultDashboardId());
if (dashboard == null) {
throw new DataValidationException("Can't assign non-existent dashboard!");
}
if (!dashboard.getTenantId().equals(assetProfile.getTenantId())) {
throw new DataValidationException("Can't assign dashboard from different tenant!");
}
}
}
@Override
protected AssetProfile validateUpdate(TenantId tenantId, AssetProfile assetProfile) {
AssetProfile old = assetProfileDao.findById(assetProfile.getTenantId(), assetProfile.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing asset profile!");
}
return old;
}
}

63
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.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.sql.asset;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.asset.AssetProfileInfo;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.AssetProfileEntity;
import java.util.UUID;
public interface AssetProfileRepository extends JpaRepository<AssetProfileEntity, UUID>, ExportableEntityRepository<AssetProfileEntity> {
@Query("SELECT new org.thingsboard.server.common.data.asset.AssetProfileInfo(a.id, a.name, a.image, a.defaultDashboardId) " +
"FROM AssetProfileEntity a " +
"WHERE a.id = :assetProfileId")
AssetProfileInfo findAssetProfileInfoById(@Param("assetProfileId") UUID assetProfileId);
@Query("SELECT a FROM AssetProfileEntity a WHERE " +
"a.tenantId = :tenantId AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
Page<AssetProfileEntity> findAssetProfiles(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.common.data.asset.AssetProfileInfo(a.id, a.name, a.image, a.defaultDashboardId) " +
"FROM AssetProfileEntity a WHERE " +
"a.tenantId = :tenantId AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
Page<AssetProfileInfo> findAssetProfileInfos(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT a FROM AssetProfileEntity a " +
"WHERE a.tenantId = :tenantId AND a.isDefault = true")
AssetProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId);
@Query("SELECT new org.thingsboard.server.common.data.asset.AssetProfileInfo(a.id, a.name, a.image, a.defaultDashboardId) " +
"FROM AssetProfileEntity a " +
"WHERE a.tenantId = :tenantId AND a.isDefault = true")
AssetProfileInfo findDefaultAssetProfileInfo(@Param("tenantId") UUID tenantId);
AssetProfileEntity findByTenantIdAndName(UUID id, String profileName);
@Query("SELECT externalId FROM AssetProfileEntity WHERE id = :id")
UUID getExternalIdById(@Param("id") UUID id);
}

62
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -32,9 +32,10 @@ import java.util.UUID;
*/
public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, ExportableEntityRepository<AssetEntity> {
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " +
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.id = :assetId")
AssetInfoEntity findAssetInfoById(@Param("assetId") UUID assetId);
@ -44,11 +45,15 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " +
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.tenantId = :tenantId " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
"AND (LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(a.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(p.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<AssetInfoEntity> findAssetInfosByTenantId(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
@ -61,9 +66,18 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " +
@Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " +
"AND a.assetProfileId = :profileId " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))")
Page<AssetEntity> findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId,
@Param("profileId") UUID profileId,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))")
@ -86,17 +100,34 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " +
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.tenantId = :tenantId " +
"AND a.type = :type " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
"AND (LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(a.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<AssetInfoEntity> findAssetInfosByTenantIdAndType(@Param("tenantId") UUID tenantId,
@Param("type") String type,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.tenantId = :tenantId " +
"AND a.assetProfileId = :assetProfileId " +
"AND (LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(a.label) LIKE LOWER(CONCAT('%', :textSearch, '%')) " +
"OR LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%')))")
Page<AssetInfoEntity> findAssetInfosByTenantIdAndAssetProfileId(@Param("tenantId") UUID tenantId,
@Param("assetProfileId") UUID assetProfileId,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId AND a.type = :type " +
@ -107,9 +138,10 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " +
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId " +
"AND a.type = :type " +
@ -120,9 +152,25 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " +
"FROM AssetEntity a " +
"LEFT JOIN CustomerEntity c on c.id = a.customerId " +
"LEFT JOIN AssetProfileEntity p on p.id = a.assetProfileId " +
"WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId " +
"AND a.assetProfileId = :assetProfileId " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
Page<AssetInfoEntity> findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId,
@Param("assetProfileId") UUID assetProfileId,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId")
List<String> findTenantAssetTypes(@Param("tenantId") UUID tenantId);
Long countByAssetProfileId(UUID assetProfileId);
@Query("SELECT a FROM AssetEntity a, RelationEntity re WHERE a.tenantId = :tenantId " +
"AND a.id = re.toId AND re.toType = 'ASSET' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +

36
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -144,6 +144,16 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
@Override
public PageData<AssetInfo> findAssetInfosByTenantIdAndAssetProfileId(UUID tenantId, UUID assetProfileId, PageLink pageLink) {
return DaoUtil.toPageData(
assetRepository.findAssetInfosByTenantIdAndAssetProfileId(
tenantId,
assetProfileId,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
@Override
public PageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) {
return DaoUtil.toPageData(assetRepository
@ -166,11 +176,37 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
@Override
public PageData<AssetInfo> findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(UUID tenantId, UUID customerId, UUID assetProfileId, PageLink pageLink) {
return DaoUtil.toPageData(
assetRepository.findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(
tenantId,
customerId,
assetProfileId,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
@Override
public ListenableFuture<List<EntitySubtype>> findTenantAssetTypesAsync(UUID tenantId) {
return service.submit(() -> convertTenantAssetTypesToDto(tenantId, assetRepository.findTenantAssetTypes(tenantId)));
}
@Override
public Long countAssetsByAssetProfileId(TenantId tenantId, UUID assetProfileId) {
return assetRepository.countByAssetProfileId(assetProfileId);
}
@Override
public PageData<Asset> findAssetsByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink) {
return DaoUtil.toPageData(
assetRepository.findByTenantIdAndProfileId(
tenantId,
profileId,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
private List<EntitySubtype> convertTenantAssetTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) {

126
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java

@ -0,0 +1,126 @@
/**
* 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.sql.asset;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
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.DaoUtil;
import org.thingsboard.server.dao.asset.AssetProfileDao;
import org.thingsboard.server.dao.model.sql.AssetProfileEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@Component
public class JpaAssetProfileDao extends JpaAbstractSearchTextDao<AssetProfileEntity, AssetProfile> implements AssetProfileDao {
@Autowired
private AssetProfileRepository assetProfileRepository;
@Override
protected Class<AssetProfileEntity> getEntityClass() {
return AssetProfileEntity.class;
}
@Override
protected JpaRepository<AssetProfileEntity, UUID> getRepository() {
return assetProfileRepository;
}
@Override
public AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, UUID assetProfileId) {
return assetProfileRepository.findAssetProfileInfoById(assetProfileId);
}
@Transactional
@Override
public AssetProfile saveAndFlush(TenantId tenantId, AssetProfile assetProfile) {
AssetProfile result = save(tenantId, assetProfile);
assetProfileRepository.flush();
return result;
}
@Override
public PageData<AssetProfile> findAssetProfiles(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(
assetProfileRepository.findAssetProfiles(
tenantId.getId(),
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<AssetProfileInfo> findAssetProfileInfos(TenantId tenantId, PageLink pageLink) {
return DaoUtil.pageToPageData(
assetProfileRepository.findAssetProfileInfos(
tenantId.getId(),
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
@Override
public AssetProfile findDefaultAssetProfile(TenantId tenantId) {
return DaoUtil.getData(assetProfileRepository.findByDefaultTrueAndTenantId(tenantId.getId()));
}
@Override
public AssetProfileInfo findDefaultAssetProfileInfo(TenantId tenantId) {
return assetProfileRepository.findDefaultAssetProfileInfo(tenantId.getId());
}
@Override
public AssetProfile findByName(TenantId tenantId, String profileName) {
return DaoUtil.getData(assetProfileRepository.findByTenantIdAndName(tenantId.getId(), profileName));
}
@Override
public AssetProfile findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(assetProfileRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public AssetProfile findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(assetProfileRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public PageData<AssetProfile> findByTenantId(UUID tenantId, PageLink pageLink) {
return findAssetProfiles(TenantId.fromUUID(tenantId), pageLink);
}
@Override
public AssetProfileId getExternalIdByInternal(AssetProfileId internalId) {
return Optional.ofNullable(assetProfileRepository.getExternalIdById(internalId.getId()))
.map(AssetProfileId::new).orElse(null);
}
@Override
public EntityType getEntityType() {
return EntityType.ASSET_PROFILE;
}
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -240,6 +240,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
entityTableMap.put(EntityType.EDGE, "edge");
entityTableMap.put(EntityType.RULE_CHAIN, "rule_chain");
entityTableMap.put(EntityType.DEVICE_PROFILE, "device_profile");
entityTableMap.put(EntityType.ASSET_PROFILE, "asset_profile");
}
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{

1
dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

@ -103,6 +103,7 @@ public class EntityKeyMapping {
allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields));
allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields);
allowedEntityFieldMap.put(EntityType.DEVICE_PROFILE, Set.of(CREATED_TIME, NAME, TYPE));
allowedEntityFieldMap.put(EntityType.ASSET_PROFILE, Set.of(CREATED_TIME, NAME));
entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY);
entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY);

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

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@ -76,6 +77,9 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
@Autowired
private AssetService assetService;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private DeviceService deviceService;
@ -165,6 +169,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), create));
if (tenant.getId() == null) {
deviceProfileService.createDefaultDeviceProfile(savedTenant.getId());
assetProfileService.createDefaultAssetProfile(savedTenant.getId());
apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null);
}
return savedTenant;
@ -182,6 +187,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
entityViewService.deleteEntityViewsByTenantId(tenantId);
widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
assetService.deleteAssetsByTenantId(tenantId);
assetProfileService.deleteAssetProfilesByTenantId(tenantId);
deviceService.deleteDevicesByTenantId(tenantId);
deviceProfileService.deleteDeviceProfilesByTenantId(tenantId);
dashboardService.deleteDashboardsByTenantId(tenantId);

51
dao/src/main/resources/sql/schema-entities.sql

@ -73,21 +73,6 @@ CREATE TABLE IF NOT EXISTS entity_alarm (
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS asset (
id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
customer_id uuid,
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id uuid,
type varchar(255),
external_id uuid,
CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id)
);
CREATE TABLE IF NOT EXISTS audit_log (
id uuid NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY,
created_time bigint NOT NULL,
@ -240,6 +225,42 @@ CREATE TABLE IF NOT EXISTS queue (
additional_info varchar
);
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 TABLE IF NOT EXISTS asset (
id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
customer_id uuid,
asset_profile_id uuid NOT NULL,
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id uuid,
type varchar(255),
external_id uuid,
CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id),
CONSTRAINT fk_asset_profile FOREIGN KEY (asset_profile_id) REFERENCES asset_profile(id)
);
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,

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

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.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;
@ -47,6 +48,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
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.audit.AuditLogLevelFilter;
import org.thingsboard.server.dao.audit.AuditLogLevelMask;
@ -166,6 +168,9 @@ public abstract class AbstractServiceTest {
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected AssetProfileService assetProfileService;
@Autowired
protected ResourceService resourceService;
@ -250,6 +255,16 @@ public abstract class AbstractServiceTest {
return deviceProfile;
}
protected AssetProfile createAssetProfile(TenantId tenantId, String name) {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfile.setName(name);
assetProfile.setDescription(name + " Test");
assetProfile.setDefault(false);
assetProfile.setDefaultRuleChainId(null);
return assetProfile;
}
public TenantId createTenant() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant " + Uuids.timeBased());

279
dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetProfileServiceTest.java

@ -0,0 +1,279 @@
/**
* 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.service;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Tenant;
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.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public abstract class BaseAssetProfileServiceTest extends AbstractServiceTest {
private IdComparator<AssetProfile> idComparator = new IdComparator<>();
private IdComparator<AssetProfileInfo> assetProfileInfoIdComparator = new IdComparator<>();
private TenantId tenantId;
@Before
public void before() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
}
@Test
public void testSaveAssetProfile() {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile");
AssetProfile savedAssetProfile = assetProfileService.saveAssetProfile(assetProfile);
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());
savedAssetProfile.setName("New asset profile");
assetProfileService.saveAssetProfile(savedAssetProfile);
AssetProfile foundAssetProfile = assetProfileService.findAssetProfileById(tenantId, savedAssetProfile.getId());
Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfile.getName());
}
@Test
public void testFindAssetProfileById() {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile");
AssetProfile savedAssetProfile = assetProfileService.saveAssetProfile(assetProfile);
AssetProfile foundAssetProfile = assetProfileService.findAssetProfileById(tenantId, savedAssetProfile.getId());
Assert.assertNotNull(foundAssetProfile);
Assert.assertEquals(savedAssetProfile, foundAssetProfile);
}
@Test
public void testFindAssetProfileInfoById() {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile");
AssetProfile savedAssetProfile = assetProfileService.saveAssetProfile(assetProfile);
AssetProfileInfo foundAssetProfileInfo = assetProfileService.findAssetProfileInfoById(tenantId, savedAssetProfile.getId());
Assert.assertNotNull(foundAssetProfileInfo);
Assert.assertEquals(savedAssetProfile.getId(), foundAssetProfileInfo.getId());
Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfileInfo.getName());
}
@Test
public void testFindDefaultAssetProfile() {
AssetProfile foundDefaultAssetProfile = assetProfileService.findDefaultAssetProfile(tenantId);
Assert.assertNotNull(foundDefaultAssetProfile);
Assert.assertNotNull(foundDefaultAssetProfile.getId());
Assert.assertNotNull(foundDefaultAssetProfile.getName());
}
@Test
public void testFindDefaultAssetProfileInfo() {
AssetProfileInfo foundDefaultAssetProfileInfo = assetProfileService.findDefaultAssetProfileInfo(tenantId);
Assert.assertNotNull(foundDefaultAssetProfileInfo);
Assert.assertNotNull(foundDefaultAssetProfileInfo.getId());
Assert.assertNotNull(foundDefaultAssetProfileInfo.getName());
}
@Test
public void testFindOrCreateAssetProfile() throws ExecutionException, InterruptedException {
ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100, ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope")));
try {
List<ListenableFuture<AssetProfile>> futures = new ArrayList<>();
for (int i = 0; i < 50; i++) {
futures.add(testExecutor.submit(() -> assetProfileService.findOrCreateAssetProfile(tenantId, "Asset Profile 1")));
futures.add(testExecutor.submit(() -> assetProfileService.findOrCreateAssetProfile(tenantId, "Asset Profile 2")));
}
List<AssetProfile> assetProfiles = Futures.allAsList(futures).get();
assetProfiles.forEach(Assert::assertNotNull);
} finally {
testExecutor.shutdownNow();
}
}
@Test
public void testSetDefaultAssetProfile() {
AssetProfile assetProfile1 = this.createAssetProfile(tenantId, "Asset Profile 1");
AssetProfile assetProfile2 = this.createAssetProfile(tenantId, "Asset Profile 2");
AssetProfile savedAssetProfile1 = assetProfileService.saveAssetProfile(assetProfile1);
AssetProfile savedAssetProfile2 = assetProfileService.saveAssetProfile(assetProfile2);
boolean result = assetProfileService.setDefaultAssetProfile(tenantId, savedAssetProfile1.getId());
Assert.assertTrue(result);
AssetProfile defaultAssetProfile = assetProfileService.findDefaultAssetProfile(tenantId);
Assert.assertNotNull(defaultAssetProfile);
Assert.assertEquals(savedAssetProfile1.getId(), defaultAssetProfile.getId());
result = assetProfileService.setDefaultAssetProfile(tenantId, savedAssetProfile2.getId());
Assert.assertTrue(result);
defaultAssetProfile = assetProfileService.findDefaultAssetProfile(tenantId);
Assert.assertNotNull(defaultAssetProfile);
Assert.assertEquals(savedAssetProfile2.getId(), defaultAssetProfile.getId());
}
@Test(expected = DataValidationException.class)
public void testSaveAssetProfileWithEmptyName() {
AssetProfile assetProfile = new AssetProfile();
assetProfile.setTenantId(tenantId);
assetProfileService.saveAssetProfile(assetProfile);
}
@Test(expected = DataValidationException.class)
public void testSaveAssetProfileWithSameName() {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile");
assetProfileService.saveAssetProfile(assetProfile);
AssetProfile assetProfile2 = this.createAssetProfile(tenantId, "Asset Profile");
assetProfileService.saveAssetProfile(assetProfile2);
}
@Test(expected = DataValidationException.class)
public void testDeleteAssetProfileWithExistingAsset() {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile");
AssetProfile savedAssetProfile = assetProfileService.saveAssetProfile(assetProfile);
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("Test asset");
asset.setAssetProfileId(savedAssetProfile.getId());
assetService.saveAsset(asset);
assetProfileService.deleteAssetProfile(tenantId, savedAssetProfile.getId());
}
@Test
public void testDeleteAssetProfile() {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile");
AssetProfile savedAssetProfile = assetProfileService.saveAssetProfile(assetProfile);
assetProfileService.deleteAssetProfile(tenantId, savedAssetProfile.getId());
AssetProfile foundAssetProfile = assetProfileService.findAssetProfileById(tenantId, savedAssetProfile.getId());
Assert.assertNull(foundAssetProfile);
}
@Test
public void testFindAssetProfiles() {
List<AssetProfile> assetProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<AssetProfile> pageData = assetProfileService.findAssetProfiles(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
assetProfiles.addAll(pageData.getData());
for (int i = 0; i < 28; i++) {
AssetProfile assetProfile = this.createAssetProfile(tenantId, "Asset Profile" + i);
assetProfiles.add(assetProfileService.saveAssetProfile(assetProfile));
}
List<AssetProfile> loadedAssetProfiles = new ArrayList<>();
pageLink = new PageLink(17);
do {
pageData = assetProfileService.findAssetProfiles(tenantId, 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()) {
assetProfileService.deleteAssetProfile(tenantId, assetProfile.getId());
}
}
pageLink = new PageLink(17);
pageData = assetProfileService.findAssetProfiles(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
@Test
public void testFindAssetProfileInfos() {
List<AssetProfile> assetProfiles = new ArrayList<>();
PageLink pageLink = new PageLink(17);
PageData<AssetProfile> assetProfilePageData = assetProfileService.findAssetProfiles(tenantId, 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(tenantId, "Asset Profile" + i);
assetProfiles.add(assetProfileService.saveAssetProfile(assetProfile));
}
List<AssetProfileInfo> loadedAssetProfileInfos = new ArrayList<>();
pageLink = new PageLink(17);
PageData<AssetProfileInfo> pageData;
do {
pageData = assetProfileService.findAssetProfileInfos(tenantId, 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()) {
assetProfileService.deleteAssetProfile(tenantId, assetProfile.getId());
}
}
pageLink = new PageLink(17);
pageData = assetProfileService.findAssetProfileInfos(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
}

160
dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java

@ -262,7 +262,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
asset.setName(name);
asset.setType("default");
assetsTitle1.add(new AssetInfo(assetService.saveAsset(asset), null, false));
assetsTitle1.add(new AssetInfo(assetService.saveAsset(asset), null, false, "default"));
}
String title2 = "Asset title 2";
List<AssetInfo> assetsTitle2 = new ArrayList<>();
@ -274,7 +274,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
asset.setName(name);
asset.setType("default");
assetsTitle2.add(new AssetInfo(assetService.saveAsset(asset), null, false));
assetsTitle2.add(new AssetInfo(assetService.saveAsset(asset), null, false, "default"));
}
List<AssetInfo> loadedAssetsTitle1 = new ArrayList<>();
@ -427,7 +427,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest {
asset.setName("Asset"+i);
asset.setType("default");
asset = assetService.saveAsset(asset);
assets.add(new AssetInfo(assetService.assignAssetToCustomer(tenantId, asset.getId(), customerId), customer.getTitle(), customer.isPublic()));
assets.add(new AssetInfo(assetService.assignAssetToCustomer(tenantId, asset.getId(), customerId), customer.getTitle(), customer.isPublic(), "default"));
}
List<AssetInfo> loadedAssets = new ArrayList<>();
@ -653,4 +653,158 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest {
Assert.assertNull("Can't find asset by name in cache if it was renamed", renamedAsset);
assetService.deleteAsset(tenantId, savedAsset.getId());
}
@Test
public void testFindAssetInfoByTenantId() {
Customer customer = new Customer();
customer.setTitle("Customer X");
customer.setTenantId(tenantId);
Customer savedCustomer = customerService.saveCustomer(customer);
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("default");
asset.setType("default");
asset.setLabel("label");
asset.setCustomerId(savedCustomer.getId());
Asset savedAsset = assetService.saveAsset(asset);
PageLink pageLinkWithLabel = new PageLink(100, 0, "label");
List<AssetInfo> assetInfosWithLabel = assetService
.findAssetInfosByTenantId(tenantId, pageLinkWithLabel).getData();
Assert.assertFalse(assetInfosWithLabel.isEmpty());
Assert.assertTrue(
assetInfosWithLabel.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getLabel().equals(savedAsset.getLabel())
)
);
PageLink pageLinkWithCustomer = new PageLink(100, 0, savedCustomer.getSearchText());
List<AssetInfo> assetInfosWithCustomer = assetService
.findAssetInfosByTenantId(tenantId, pageLinkWithCustomer).getData();
Assert.assertFalse(assetInfosWithCustomer.isEmpty());
Assert.assertTrue(
assetInfosWithCustomer.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getCustomerId().equals(savedCustomer.getId())
&& d.getCustomerTitle().equals(savedCustomer.getTitle())
)
);
PageLink pageLinkWithType = new PageLink(100, 0, asset.getType());
List<AssetInfo> assetInfosWithType = assetService
.findAssetInfosByTenantId(tenantId, pageLinkWithType).getData();
Assert.assertFalse(assetInfosWithType.isEmpty());
Assert.assertTrue(
assetInfosWithType.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getType().equals(asset.getType())
)
);
}
@Test
public void testFindAssetInfoByTenantIdAndType() {
Customer customer = new Customer();
customer.setTitle("Customer X");
customer.setTenantId(tenantId);
Customer savedCustomer = customerService.saveCustomer(customer);
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("default");
asset.setType("default");
asset.setLabel("label");
asset.setCustomerId(savedCustomer.getId());
Asset savedAsset = assetService.saveAsset(asset);
PageLink pageLinkWithLabel = new PageLink(100, 0, "label");
List<AssetInfo> assetInfosWithLabel = assetService
.findAssetInfosByTenantIdAndType(tenantId, asset.getType(), pageLinkWithLabel).getData();
Assert.assertFalse(assetInfosWithLabel.isEmpty());
Assert.assertTrue(
assetInfosWithLabel.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getAssetProfileName().equals(savedAsset.getType())
&& d.getLabel().equals(savedAsset.getLabel())
)
);
PageLink pageLinkWithCustomer = new PageLink(100, 0, savedCustomer.getSearchText());
List<AssetInfo> assetInfosWithCustomer = assetService
.findAssetInfosByTenantIdAndType(tenantId, asset.getType(), pageLinkWithCustomer).getData();
Assert.assertFalse(assetInfosWithCustomer.isEmpty());
Assert.assertTrue(
assetInfosWithCustomer.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getAssetProfileName().equals(savedAsset.getType())
&& d.getCustomerId().equals(savedCustomer.getId())
&& d.getCustomerTitle().equals(savedCustomer.getTitle())
)
);
}
@Test
public void testFindAssetInfoByTenantIdAndAssetProfileId() {
Customer customer = new Customer();
customer.setTitle("Customer X");
customer.setTenantId(tenantId);
Customer savedCustomer = customerService.saveCustomer(customer);
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("default");
asset.setLabel("label");
asset.setCustomerId(savedCustomer.getId());
Asset savedAsset = assetService.saveAsset(asset);
PageLink pageLinkWithLabel = new PageLink(100, 0, "label");
List<AssetInfo> assetInfosWithLabel = assetService
.findAssetInfosByTenantIdAndAssetProfileId(tenantId, savedAsset.getAssetProfileId(), pageLinkWithLabel).getData();
Assert.assertFalse(assetInfosWithLabel.isEmpty());
Assert.assertTrue(
assetInfosWithLabel.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getAssetProfileId().equals(savedAsset.getAssetProfileId())
&& d.getLabel().equals(savedAsset.getLabel())
)
);
PageLink pageLinkWithCustomer = new PageLink(100, 0, savedCustomer.getSearchText());
List<AssetInfo> assetInfosWithCustomer = assetService
.findAssetInfosByTenantIdAndAssetProfileId(tenantId, savedAsset.getAssetProfileId(), pageLinkWithCustomer).getData();
Assert.assertFalse(assetInfosWithCustomer.isEmpty());
Assert.assertTrue(
assetInfosWithCustomer.stream()
.anyMatch(
d -> d.getId().equals(savedAsset.getId())
&& d.getTenantId().equals(tenantId)
&& d.getAssetProfileId().equals(savedAsset.getAssetProfileId())
&& d.getCustomerId().equals(savedCustomer.getId())
&& d.getCustomerTitle().equals(savedCustomer.getTitle())
)
);
}
}

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

Loading…
Cancel
Save