diff --git a/application/pom.xml b/application/pom.xml
index 3693fe6dbe..96c9ab7b39 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -286,7 +286,6 @@
com.jayway.jsonpath
json-path
- test
com.jayway.jsonpath
diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_after.sql b/application/src/main/data/upgrade/3.4.1/schema_update_after.sql
new file mode 100644
index 0000000000..78df4d2fd2
--- /dev/null
+++ b/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);
diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql b/application/src/main/data/upgrade/3.4.1/schema_update_before.sql
new file mode 100644
index 0000000000..59566e42b5
--- /dev/null
+++ b/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;
+$$;
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index b4c308db86..f4e7eb2463 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/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;
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 92fdfc6e48..08b711015f 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/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 profileListener, BiConsumer 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());
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
index 75e77d4719..8450c67ea3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java
+++ b/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));
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
new file mode 100644
index 0000000000..2e84dd6634
--- /dev/null
+++ b/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 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 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);
+ }
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 1e9958cc25..21edd71ddd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/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);
diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
index d4199006d0..e00ccaa4bc 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
+++ b/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. " +
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
index 0baf89e0dc..bd2a5a2ad9 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
+++ b/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")
diff --git a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
index ff89c3dd06..f93c6e34e4 100644
--- a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
+++ b/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 {
private final AssetService assetService;
private final TbAssetService tbAssetService;
+ private final AssetProfileService assetProfileService;
@Override
protected void setEntityFields(Asset entity, Map fields) {
@@ -66,6 +70,13 @@ public class AssetBulkImportService extends AbstractBulkImportService {
@Override
@SneakyThrows
protected Asset saveEntity(SecurityUser user, Asset entity, Map 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);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
index 5fa58a5bb5..23fb8fd4be 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
+++ b/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:
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
index 29ff49d626..8bc781b5fc 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
+++ b/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;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
index 3f8950f281..5f1c03b35a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
+++ b/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));
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
index 1df44dd589..d6bfff76f6 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
+++ b/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());
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
index 0a78afa6b7..fb84d7f85a 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java
+++ b/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()));
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java
new file mode 100644
index 0000000000..78f7b0327e
--- /dev/null
+++ b/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();
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetProfilesEdgeEventFetcher.java
new file mode 100644
index 0000000000..efade1b235
--- /dev/null
+++ b/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 {
+
+ private final AssetProfileService assetProfileService;
+
+ @Override
+ PageData 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);
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java
new file mode 100644
index 0000000000..ec11764fea
--- /dev/null
+++ b/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;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
index 9d6199bdcd..018acb7668 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
+++ b/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;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
index ad6446cb95..f3bb328886 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
+++ b/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 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 processPostTelemetry(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData metaData) {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
index e66d7d5595..8db5c4f4c7 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
+++ b/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 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 syncAssets(TenantId tenantId, Edge edge, String assetType) {
+ log.trace("[{}] syncAssets [{}][{}]", tenantId, edge.getName(), assetType);
+ List> futures = new ArrayList<>();
+ try {
+ PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
+ PageData 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 processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge,
WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java
index 2ba1b3c9d3..7140ac52a5 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java
+++ b/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 processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg);
+ ListenableFuture processAssetProfileAssetsRequestMsg(TenantId tenantId, Edge edge, AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg);
+
ListenableFuture processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg);
ListenableFuture processEntityViewsRequestMsg(TenantId tenantId, Edge edge, EntityViewsRequestMsg entityViewsRequestMsg);
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java
new file mode 100644
index 0000000000..d053b99a3b
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/TbAssetProfileService.java
new file mode 100644
index 0000000000..e451b3245a
--- /dev/null
+++ b/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 setDefaultAssetProfile(AssetProfile assetProfile, AssetProfile previousDefaultAssetProfile, User user) throws ThingsboardException;
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index c3b697f002..092726d0c8 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
-import org.thingsboard.common.util.ThrowingConsumer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
@@ -33,6 +32,8 @@ import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
+import org.thingsboard.server.dao.asset.AssetProfileService;
+import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
@@ -108,9 +109,15 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
@Autowired
private DeviceService deviceService;
+ @Autowired
+ private AssetService assetService;
+
@Autowired
private DeviceProfileService deviceProfileService;
+ @Autowired
+ private AssetProfileService assetProfileService;
+
@Autowired
private ApiUsageStateService apiUsageStateService;
@@ -601,26 +608,57 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
}
break;
case "3.4.1":
- execute(connection -> {
+ try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
- runSchemaUpdateScript(connection, "3.4.1");
- connection.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004002;");
+ runSchemaUpdateScript(conn, "3.4.1");
+ if (isOldSchema(conn, 3004001)) {
+ try {
+ conn.createStatement().execute("ALTER TABLE asset ADD COLUMN asset_profile_id uuid");
+ } catch (Exception e) {
+ }
+
+ schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_before.sql");
+ loadSql(schemaUpdateFile, conn);
+
+ log.info("Creating default asset profiles...");
+ PageLink pageLink = new PageLink(100);
+ PageData pageData;
+ do {
+ pageData = tenantService.findTenants(pageLink);
+ for (Tenant tenant : pageData.getData()) {
+ List assetTypes = assetService.findAssetTypesByTenantId(tenant.getId()).get();
+ try {
+ assetProfileService.createDefaultAssetProfile(tenant.getId());
+ } catch (Exception e) {
+ }
+ for (EntitySubtype assetType : assetTypes) {
+ try {
+ assetProfileService.findOrCreateAssetProfile(tenant.getId(), assetType.getType());
+ } catch (Exception e) {
+ }
+ }
+ }
+ pageLink = pageLink.nextPageLink();
+ } while (pageData.hasNext());
+
+ log.info("Updating asset profiles...");
+ conn.createStatement().execute("call update_asset_profiles()");
+
+ schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_after.sql");
+ loadSql(schemaUpdateFile, conn);
+
+ conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004002;");
+ }
log.info("Schema updated.");
- });
+ } catch (Exception e) {
+ log.error("Failed updating schema!!!", e);
+ }
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
}
- private void execute(ThrowingConsumer function) {
- try (Connection connection = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
- function.accept(connection);
- } catch (Exception e) {
- log.error("Failed to update schema!", e);
- }
- }
-
private void runSchemaUpdateScript(Connection connection, String version) throws Exception {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, connection);
diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java
new file mode 100644
index 0000000000..23d0e1b616
--- /dev/null
+++ b/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 assetProfilesMap = new ConcurrentHashMap<>();
+ private final ConcurrentMap assetsMap = new ConcurrentHashMap<>();
+ private final ConcurrentMap>> profileListeners = new ConcurrentHashMap<>();
+ private final ConcurrentMap>> 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 profileListener,
+ BiConsumer 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> tenantListeners = profileListeners.get(tenantId);
+ if (tenantListeners != null) {
+ tenantListeners.remove(listenerId);
+ }
+ ConcurrentMap> assetListeners = assetProfileListeners.get(tenantId);
+ if (assetListeners != null) {
+ assetListeners.remove(listenerId);
+ }
+ }
+
+ private void notifyProfileListeners(AssetProfile profile) {
+ ConcurrentMap> 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> tenantListeners = assetProfileListeners.get(tenantId);
+ if (tenantListeners != null) {
+ tenantListeners.forEach((id, listener) -> listener.accept(assetId, profile));
+ }
+ }
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java b/application/src/main/java/org/thingsboard/server/service/profile/TbAssetProfileCache.java
new file mode 100644
index 0000000000..6c332e63ff
--- /dev/null
+++ b/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);
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
index 8ec29672de..ca654ec802 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
+++ b/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)
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
index a3bae8bc4e..9cdf3e4aa2 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
+++ b/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> nfConsumer) {
+ TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
+ PartitionService partitionService, TbQueueConsumer> 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 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 callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
index ff57aec801..cdd84b0478 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
+++ b/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")
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
index 3b1e4f5fa2..a9484a2bd3 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
+++ b/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),
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
index fe61f16ad3..ef7991e5de 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
+++ b/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);
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
index 0f43159e30..f4e4e3dc14 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
+++ b/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 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
);
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
index 6e06144ae2..b5f5e6afd9 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
+++ b/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);
});
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java
index 8875491d42..5ae496558e 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java
@@ -32,6 +32,7 @@ public class AssetExportService extends BaseEntityExportService ctx, Asset asset, EntityExportData exportData) {
asset.setCustomerId(getExternalIdOrElseInternal(ctx, asset.getCustomerId()));
+ asset.setAssetProfileId(getExternalIdOrElseInternal(ctx, asset.getAssetProfileId()));
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java
new file mode 100644
index 0000000000..a333767b33
--- /dev/null
+++ b/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> {
+
+ @Override
+ protected void setRelatedEntities(EntitiesExportCtx> ctx, AssetProfile assetProfile, EntityExportData exportData) {
+ assetProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultDashboardId()));
+ assetProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultRuleChainId()));
+ }
+
+ @Override
+ public Set getSupportedEntityTypes() {
+ return Set.of(EntityType.ASSET_PROFILE);
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
index 3225668004..fa3558520e 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
@@ -41,6 +41,7 @@ public class AssetImportService extends BaseEntityImportService exportData, IdProvider idProvider) {
+ asset.setAssetProfileId(idProvider.getInternalId(asset.getAssetProfileId()));
return asset;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java
new file mode 100644
index 0000000000..c6b33a5bd2
--- /dev/null
+++ b/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> {
+
+ 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 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 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;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
index 947754e405..a5f66968da 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
@@ -19,6 +19,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -115,6 +116,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
super.shutdownExecutor();
}
+ @Override
+ public ListenableFuture saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts) {
+ SettableFuture future = SettableFuture.create();
+ saveAndNotify(tenantId, entityId, Collections.singletonList(ts), new VoidFutureCallback(future));
+ return future;
+ }
+
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) {
saveAndNotify(tenantId, null, entityId, ts, 0L, callback);
@@ -332,6 +340,34 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
, System.currentTimeMillis())), callback);
}
+ @Override
+ public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value) {
+ SettableFuture future = SettableFuture.create();
+ saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
+ return future;
+ }
+
+ @Override
+ public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value) {
+ SettableFuture future = SettableFuture.create();
+ saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
+ return future;
+ }
+
+ @Override
+ public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value) {
+ SettableFuture future = SettableFuture.create();
+ saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
+ return future;
+ }
+
+ @Override
+ public ListenableFuture saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value) {
+ SettableFuture future = SettableFuture.create();
+ saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
+ return future;
+ }
+
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
@@ -436,4 +472,22 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
+ private static class VoidFutureCallback implements FutureCallback {
+ private final SettableFuture future;
+
+ public VoidFutureCallback(SettableFuture future) {
+ this.future = future;
+ }
+
+ @Override
+ public void onSuccess(Void result) {
+ future.set(null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ future.setException(t);
+ }
+ }
+
}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index da76dae28a..96438470a0 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -274,6 +274,8 @@ sql:
# Specify whether to log database queries and their parameters generated by entity query repository
log_queries: "${SQL_LOG_QUERIES:false}"
log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}"
+ log_tenant_stats: "${SQL_LOG_TENANT_STATS:true}"
+ log_tenant_stats_interval_ms: "${SQL_LOG_TENANT_STATS_INTERVAL_MS:60000}"
postgres:
# Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
@@ -412,6 +414,9 @@ cache:
deviceProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}"
+ assetProfiles:
+ timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}"
+ maxSize: "${CACHE_SPECS_ASSET_PROFILES_MAX_SIZE:10000}"
attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
@@ -567,6 +572,7 @@ audit-log:
"alarm": "${AUDIT_LOG_MASK_ALARM:W}"
"entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
"device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
+ "asset_profile": "${AUDIT_LOG_MASK_ASSET_PROFILE:W}"
"edge": "${AUDIT_LOG_MASK_EDGE:W}"
"tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}"
"ota_package": "${AUDIT_LOG_MASK_OTA_PACKAGE:W}"
@@ -716,6 +722,8 @@ transport:
dtls:
# Enable/disable DTLS 1.2 support
enabled: "${COAP_DTLS_ENABLED:false}"
+ # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000
+ retransmission_timeout: "${COAP_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}"
# CoAP DTLS bind address
bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}"
# CoAP DTLS bind port
@@ -753,6 +761,9 @@ transport:
lwm2m:
# Enable/disable lvm2m transport protocol.
enabled: "${LWM2M_ENABLED:true}"
+ dtls:
+ # RFC7925_RETRANSMISSION_TIMEOUT_IN_MILLISECONDS = 9000
+ retransmission_timeout: "${LWM2M_DTLS_RETRANSMISSION_TIMEOUT_MS:9000}"
server:
id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index 09c40f263c..3783b746f3 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -35,7 +35,7 @@ import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
-import org.mockito.BDDMockito;
+import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -59,6 +59,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
@@ -131,8 +132,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected static final String DIFFERENT_CUSTOMER_USER_EMAIL = "testdifferentcustomer@thingsboard.org";
private static final String DIFFERENT_CUSTOMER_USER_PASSWORD = "diffcustomer";
- /** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
- * and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
+ /**
+ * See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)}
+ * and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()}
*/
private static final long DEFAULT_TIMEOUT = -1L;
@@ -448,6 +450,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return deviceProfile;
}
+ protected AssetProfile createAssetProfile(String name) {
+ AssetProfile assetProfile = new AssetProfile();
+ assetProfile.setName(name);
+ assetProfile.setDescription(name + " Test");
+ assetProfile.setDefault(false);
+ assetProfile.setDefaultRuleChainId(null);
+ return assetProfile;
+ }
+
protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration, boolean sendAckOnValidationException) {
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration();
mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC);
@@ -722,23 +733,18 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected void testEntityDaoWithRelationsTransactionalException(Dao dao, EntityId entityIdFrom, EntityId entityTo,
String urlDelete) throws Exception {
- entityDaoRemoveByIdWithException (dao);
- createEntityRelation(entityIdFrom, entityTo, "TEST_TRANSACTIONAL_TYPE");
- assertThat(findRelationsByTo(entityTo)).hasSize(1);
-
- doDelete(urlDelete)
- .andExpect(status().isInternalServerError());
-
- assertThat(findRelationsByTo(entityTo)).hasSize(1);
- }
+ Mockito.doThrow(new ConstraintViolationException("mock message", new SQLException(), "MOCK_CONSTRAINT")).when(dao).removeById(any(), any());
+ try {
+ createEntityRelation(entityIdFrom, entityTo, "TEST_TRANSACTIONAL_TYPE");
+ assertThat(findRelationsByTo(entityTo)).hasSize(1);
- protected void entityDaoRemoveByIdWithException (Dao dao) throws Exception {
- BDDMockito.willThrow(new ConstraintViolationException("mock message", new SQLException(), "MOCK_CONSTRAINT"))
- .given(dao).removeById(any(), any());
- }
+ doDelete(urlDelete)
+ .andExpect(status().isInternalServerError());
- protected void afterTestEntityDaoRemoveByIdWithException (Dao dao) throws Exception {
- BDDMockito.willCallRealMethod().given(dao).removeById(any(), any());
+ assertThat(findRelationsByTo(entityTo)).hasSize(1);
+ } finally {
+ Mockito.reset(dao);
+ }
}
protected void createEntityRelation(EntityId entityIdFrom, EntityId entityIdTo, String typeRelation) throws Exception {
@@ -751,9 +757,13 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
MvcResult mvcResult = doGet(url).andReturn();
switch (mvcResult.getResponse().getStatus()) {
- case 200: return readResponse(mvcResult, new TypeReference<>() {});
- case 404: return Collections.emptyList();
+ case 200:
+ return readResponse(mvcResult, new TypeReference<>() {
+ });
+ case 404:
+ return Collections.emptyList();
}
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
index a2b8b908d5..4419e0e425 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
@@ -22,8 +22,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@@ -43,16 +47,24 @@ import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
+@ContextConfiguration(classes = {BaseAlarmControllerTest.Config.class})
public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public static final String TEST_ALARM_TYPE = "Test";
protected Device customerDevice;
-
- @SpyBean
+ @Autowired
private AlarmDao alarmDao;
+ static class Config {
+ @Bean
+ @Primary
+ public AlarmDao alarmDao(AlarmDao alarmDao) {
+ return Mockito.mock(AlarmDao.class, AdditionalAnswers.delegatesTo(alarmDao));
+ }
+ }
+
@Before
public void setup() throws Exception {
loginTenantAdmin();
@@ -72,8 +84,6 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public void teardown() throws Exception {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (alarmDao);
-
deleteDifferentTenant();
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
index 4e9c01e4cc..37cc703d55 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
@@ -21,8 +21,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView;
@@ -30,6 +34,7 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AssetId;
@@ -50,6 +55,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+@ContextConfiguration(classes = {BaseAssetControllerTest.Config.class})
public abstract class BaseAssetControllerTest extends AbstractControllerTest {
private IdComparator idComparator = new IdComparator<>();
@@ -57,9 +63,17 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
private Tenant savedTenant;
private User tenantAdmin;
- @SpyBean
+ @Autowired
private AssetDao assetDao;
+ static class Config {
+ @Bean
+ @Primary
+ public AssetDao assetDao(AssetDao assetDao) {
+ return Mockito.mock(AssetDao.class, AdditionalAnswers.delegatesTo(assetDao));
+ }
+ }
+
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@@ -83,8 +97,6 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (assetDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@@ -187,6 +199,20 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
deleteDifferentTenant();
}
+ @Test
+ public void testSaveAssetWithProfileFromDifferentTenant() throws Exception {
+ loginDifferentTenant();
+ AssetProfile differentProfile = createAssetProfile("Different profile");
+ differentProfile = doPost("/api/assetProfile", differentProfile, AssetProfile.class);
+
+ loginTenantAdmin();
+ Asset asset = new Asset();
+ asset.setName("My device");
+ asset.setAssetProfileId(differentProfile.getId());
+ doPost("/api/asset", asset).andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Asset can`t be referencing to asset profile from different tenant!")));
+ }
+
@Test
public void testFindAssetById() throws Exception {
Asset asset = new Asset();
@@ -310,13 +336,12 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
- String msgError = "Asset type " + msgErrorShouldBeSpecified;
- doPost("/api/asset", asset)
- .andExpect(status().isBadRequest())
- .andExpect(statusReason(containsString(msgError)));
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+ Assert.assertEquals("default", savedAsset.getType());
- testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(),
- tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
+ testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(),
+ savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
+ ActionType.ADDED);
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetProfileControllerTest.java
new file mode 100644
index 0000000000..a911df798c
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetProfileControllerTest.java
@@ -0,0 +1,463 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalAnswers;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.AssetProfile;
+import org.thingsboard.server.common.data.asset.AssetProfileInfo;
+import org.thingsboard.server.common.data.audit.ActionType;
+import org.thingsboard.server.common.data.id.AssetProfileId;
+import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.dao.asset.AssetProfileDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@ContextConfiguration(classes = {BaseAssetProfileControllerTest.Config.class})
+public abstract class BaseAssetProfileControllerTest extends AbstractControllerTest {
+
+ private IdComparator idComparator = new IdComparator<>();
+ private IdComparator assetProfileInfoIdComparator = new IdComparator<>();
+
+ private Tenant savedTenant;
+ private User tenantAdmin;
+
+ @Autowired
+ private AssetProfileDao assetProfileDao;
+
+ static class Config {
+ @Bean
+ @Primary
+ public AssetProfileDao assetProfileDao(AssetProfileDao assetProfileDao) {
+ return Mockito.mock(AssetProfileDao.class, AdditionalAnswers.delegatesTo(assetProfileDao));
+ }
+ }
+
+ @Before
+ public void beforeTest() throws Exception {
+ loginSysAdmin();
+
+ Tenant tenant = new Tenant();
+ tenant.setTitle("My tenant");
+ savedTenant = doPost("/api/tenant", tenant, Tenant.class);
+ Assert.assertNotNull(savedTenant);
+
+ tenantAdmin = new User();
+ tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
+ tenantAdmin.setTenantId(savedTenant.getId());
+ tenantAdmin.setEmail("tenant2@thingsboard.org");
+ tenantAdmin.setFirstName("Joe");
+ tenantAdmin.setLastName("Downs");
+
+ tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
+ }
+
+ @After
+ public void afterTest() throws Exception {
+ loginSysAdmin();
+
+ doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testSaveAssetProfile() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+ Assert.assertNotNull(savedAssetProfile);
+ Assert.assertNotNull(savedAssetProfile.getId());
+ Assert.assertTrue(savedAssetProfile.getCreatedTime() > 0);
+ Assert.assertEquals(assetProfile.getName(), savedAssetProfile.getName());
+ Assert.assertEquals(assetProfile.getDescription(), savedAssetProfile.getDescription());
+ Assert.assertEquals(assetProfile.isDefault(), savedAssetProfile.isDefault());
+ Assert.assertEquals(assetProfile.getDefaultRuleChainId(), savedAssetProfile.getDefaultRuleChainId());
+
+ testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedAssetProfile, savedAssetProfile.getId(), savedAssetProfile.getId(),
+ savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
+ ActionType.ADDED);
+
+ savedAssetProfile.setName("New asset profile");
+ doPost("/api/assetProfile", savedAssetProfile, AssetProfile.class);
+ AssetProfile foundAssetProfile = doGet("/api/assetProfile/" + savedAssetProfile.getId().getId().toString(), AssetProfile.class);
+ Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfile.getName());
+
+ testNotifyEntityBroadcastEntityStateChangeEventOneTime(foundAssetProfile, foundAssetProfile.getId(), foundAssetProfile.getId(),
+ savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
+ ActionType.UPDATED);
+ }
+
+ @Test
+ public void saveAssetProfileWithViolationOfValidation() throws Exception {
+ String msgError = msgErrorFieldLength("name");
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ AssetProfile createAssetProfile = this.createAssetProfile(StringUtils.randomAlphabetic(300));
+ doPost("/api/assetProfile", createAssetProfile)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString(msgError)));
+
+ testNotifyEntityEqualsOneTimeServiceNeverError(createAssetProfile, savedTenant.getId(),
+ tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
+ }
+
+ @Test
+ public void testFindAssetProfileById() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+ AssetProfile foundAssetProfile = doGet("/api/assetProfile/" + savedAssetProfile.getId().getId().toString(), AssetProfile.class);
+ Assert.assertNotNull(foundAssetProfile);
+ Assert.assertEquals(savedAssetProfile, foundAssetProfile);
+ }
+
+ @Test
+ public void whenGetAssetProfileById_thenPermissionsAreChecked() throws Exception {
+ AssetProfile assetProfile = createAssetProfile("Asset profile 1");
+ assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+
+ loginDifferentTenant();
+
+ doGet("/api/assetProfile/" + assetProfile.getId())
+ .andExpect(status().isForbidden())
+ .andExpect(statusReason(containsString(msgErrorPermission)));
+ }
+
+ @Test
+ public void testFindAssetProfileInfoById() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+ AssetProfileInfo foundAssetProfileInfo = doGet("/api/assetProfileInfo/" + savedAssetProfile.getId().getId().toString(), AssetProfileInfo.class);
+ Assert.assertNotNull(foundAssetProfileInfo);
+ Assert.assertEquals(savedAssetProfile.getId(), foundAssetProfileInfo.getId());
+ Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfileInfo.getName());
+
+ Customer customer = new Customer();
+ customer.setTitle("Customer");
+ customer.setTenantId(savedTenant.getId());
+ Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
+
+ User customerUser = new User();
+ customerUser.setAuthority(Authority.CUSTOMER_USER);
+ customerUser.setTenantId(savedTenant.getId());
+ customerUser.setCustomerId(savedCustomer.getId());
+ customerUser.setEmail("customer2@thingsboard.org");
+
+ createUserAndLogin(customerUser, "customer");
+
+ foundAssetProfileInfo = doGet("/api/assetProfileInfo/" + savedAssetProfile.getId().getId().toString(), AssetProfileInfo.class);
+ Assert.assertNotNull(foundAssetProfileInfo);
+ Assert.assertEquals(savedAssetProfile.getId(), foundAssetProfileInfo.getId());
+ Assert.assertEquals(savedAssetProfile.getName(), foundAssetProfileInfo.getName());
+ }
+
+ @Test
+ public void whenGetAssetProfileInfoById_thenPermissionsAreChecked() throws Exception {
+ AssetProfile assetProfile = createAssetProfile("Asset profile 1");
+ assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+
+ loginDifferentTenant();
+ doGet("/api/assetProfileInfo/" + assetProfile.getId())
+ .andExpect(status().isForbidden())
+ .andExpect(statusReason(containsString(msgErrorPermission)));
+ }
+
+ @Test
+ public void testFindDefaultAssetProfileInfo() throws Exception {
+ AssetProfileInfo foundDefaultAssetProfileInfo = doGet("/api/assetProfileInfo/default", AssetProfileInfo.class);
+ Assert.assertNotNull(foundDefaultAssetProfileInfo);
+ Assert.assertNotNull(foundDefaultAssetProfileInfo.getId());
+ Assert.assertNotNull(foundDefaultAssetProfileInfo.getName());
+ Assert.assertEquals("default", foundDefaultAssetProfileInfo.getName());
+ }
+
+ @Test
+ public void testSetDefaultAssetProfile() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile 1");
+ AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ AssetProfile defaultAssetProfile = doPost("/api/assetProfile/" + savedAssetProfile.getId().getId().toString() + "/default", AssetProfile.class);
+ Assert.assertNotNull(defaultAssetProfile);
+ AssetProfileInfo foundDefaultAssetProfile = doGet("/api/assetProfileInfo/default", AssetProfileInfo.class);
+ Assert.assertNotNull(foundDefaultAssetProfile);
+ Assert.assertEquals(savedAssetProfile.getName(), foundDefaultAssetProfile.getName());
+ Assert.assertEquals(savedAssetProfile.getId(), foundDefaultAssetProfile.getId());
+
+ testNotifyEntityOneTimeMsgToEdgeServiceNever(defaultAssetProfile, defaultAssetProfile.getId(), defaultAssetProfile.getId(),
+ savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
+ ActionType.UPDATED);
+ }
+
+ @Test
+ public void testSaveAssetProfileWithEmptyName() throws Exception {
+ AssetProfile assetProfile = new AssetProfile();
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ String msgError = "Asset profile name " + msgErrorShouldBeSpecified;
+ doPost("/api/assetProfile", assetProfile)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString(msgError)));
+
+ testNotifyEntityEqualsOneTimeServiceNeverError(assetProfile, savedTenant.getId(),
+ tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
+ }
+
+ @Test
+ public void testSaveAssetProfileWithSameName() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ doPost("/api/assetProfile", assetProfile).andExpect(status().isOk());
+ AssetProfile assetProfile2 = this.createAssetProfile("Asset Profile");
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ String msgError = "Asset profile with such name already exists";
+ doPost("/api/assetProfile", assetProfile2)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString(msgError)));
+
+ testNotifyEntityEqualsOneTimeServiceNeverError(assetProfile, savedTenant.getId(),
+ tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError));
+ }
+
+ @Test
+ public void testDeleteAssetProfileWithExistingAsset() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+
+ Asset asset = new Asset();
+ asset.setName("Test asset");
+ asset.setAssetProfileId(savedAssetProfile.getId());
+
+ doPost("/api/asset", asset, Asset.class);
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ doDelete("/api/assetProfile/" + savedAssetProfile.getId().getId().toString())
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("The asset profile referenced by the assets cannot be deleted")));
+
+ testNotifyEntityNever(savedAssetProfile.getId(), savedAssetProfile);
+ }
+
+ @Test
+ public void testSaveAssetProfileWithRuleChainFromDifferentTenant() throws Exception {
+ loginDifferentTenant();
+ RuleChain ruleChain = new RuleChain();
+ ruleChain.setName("Different rule chain");
+ RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
+
+ loginTenantAdmin();
+
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ assetProfile.setDefaultRuleChainId(savedRuleChain.getId());
+ doPost("/api/assetProfile", assetProfile).andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Can't assign rule chain from different tenant!")));
+ }
+
+ @Test
+ public void testSaveAssetProfileWithDashboardFromDifferentTenant() throws Exception {
+ loginDifferentTenant();
+ Dashboard dashboard = new Dashboard();
+ dashboard.setTitle("Different dashboard");
+ Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
+
+ loginTenantAdmin();
+
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ assetProfile.setDefaultDashboardId(savedDashboard.getId());
+ doPost("/api/assetProfile", assetProfile).andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Can't assign dashboard from different tenant!")));
+ }
+
+ @Test
+ public void testDeleteAssetProfile() throws Exception {
+ AssetProfile assetProfile = this.createAssetProfile("Asset Profile");
+ AssetProfile savedAssetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class);
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ doDelete("/api/assetProfile/" + savedAssetProfile.getId().getId().toString())
+ .andExpect(status().isOk());
+
+ String savedAssetProfileIdStr = savedAssetProfile.getId().getId().toString();
+ testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedAssetProfile, savedAssetProfile.getId(), savedAssetProfile.getId(),
+ savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
+ ActionType.DELETED, savedAssetProfileIdStr);
+
+ doGet("/api/assetProfile/" + savedAssetProfile.getId().getId().toString())
+ .andExpect(status().isNotFound())
+ .andExpect(statusReason(containsString(msgErrorNoFound("Asset profile", savedAssetProfileIdStr))));
+ }
+
+ @Test
+ public void testFindAssetProfiles() throws Exception {
+ List assetProfiles = new ArrayList<>();
+ PageLink pageLink = new PageLink(17);
+ PageData 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 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 assetProfiles = new ArrayList<>();
+ PageLink pageLink = new PageLink(17);
+ PageData assetProfilePageData = doGetTypedWithPageLink("/api/assetProfiles?",
+ new TypeReference>() {
+ }, 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 loadedAssetProfileInfos = new ArrayList<>();
+ pageLink = new PageLink(17);
+ PageData 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 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>() {
+ }, 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);
+ }
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
index d3cc1761c7..029d3c98e6 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
@@ -24,8 +24,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
@@ -48,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+@ContextConfiguration(classes = {BaseCustomerControllerTest.Config.class})
public abstract class BaseCustomerControllerTest extends AbstractControllerTest {
static final TypeReference> PAGE_DATA_CUSTOMER_TYPE_REFERENCE = new TypeReference<>() {
};
@@ -57,9 +62,18 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
private Tenant savedTenant;
private User tenantAdmin;
- @SpyBean
+ @Autowired
private CustomerDao customerDao;
+ static class Config {
+ @Bean
+ @Primary
+ public CustomerDao customerDao(CustomerDao customerDao) {
+ return Mockito.mock(CustomerDao.class, AdditionalAnswers.delegatesTo(customerDao));
+ }
+ }
+
+
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
@@ -87,8 +101,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (customerDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
index 50050c6306..c6bf570284 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
@@ -21,8 +21,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
@@ -46,6 +50,7 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+@ContextConfiguration(classes = {BaseDashboardControllerTest.Config.class})
public abstract class BaseDashboardControllerTest extends AbstractControllerTest {
private IdComparator idComparator = new IdComparator<>();
@@ -53,9 +58,17 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
private Tenant savedTenant;
private User tenantAdmin;
- @SpyBean
+ @Autowired
private DashboardDao dashboardDao;
+ static class Config {
+ @Bean
+ @Primary
+ public DashboardDao dashboardDao(DashboardDao dashboardDao) {
+ return Mockito.mock(DashboardDao.class, AdditionalAnswers.delegatesTo(dashboardDao));
+ }
+ }
+
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@@ -79,8 +92,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
public void afterTest() throws Exception {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (dashboardDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@@ -489,4 +500,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
dashboard.setTitle(title);
return doPost("/api/dashboard", dashboard, Dashboard.class);
}
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
index abe8b78918..b97b346bd9 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
@@ -25,8 +25,13 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
@@ -69,8 +74,10 @@ import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+@ContextConfiguration(classes = {BaseDeviceControllerTest.Config.class})
public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
- static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {};
+ static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {
+ };
ListeningExecutorService executor;
@@ -83,9 +90,16 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@SpyBean
private GatewayNotificationsService gatewayNotificationsService;
- @SpyBean
+ @Autowired
private DeviceDao deviceDao;
+ static class Config {
+ @Bean
+ @Primary
+ public DeviceDao deviceDao(DeviceDao deviceDao) {
+ return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao));
+ }
+ }
@Before
public void beforeTest() throws Exception {
@@ -114,8 +128,6 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (deviceDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk());
}
@@ -464,9 +476,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String customerIdStr = savedDevice.getId().toString();
doPost("/api/customer/" + customerIdStr
- + "/device/" + savedDevice.getId().getId())
+ + "/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound())
- .andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
+ .andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
@@ -657,7 +669,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isNotFound())
- .andExpect(statusReason(containsString(msgErrorNoFound("Device", deviceTimeBasedId.toString()))));
+ .andExpect(statusReason(containsString(msgErrorNoFound("Device", deviceTimeBasedId.toString()))));
testNotifyEntityNever(savedDevice.getId(), savedDevice);
testNotificationUpdateGatewayNever();
@@ -1168,7 +1180,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
doPost("/api/edge/" + savedEdge.getId().getId()
+ "/device/" + savedDevice.getId().getId(), Device.class);
- testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
+ testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ASSIGNED_TO_EDGE,
savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
index 0c042d5006..b811497b9f 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
@@ -26,8 +26,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
@@ -69,6 +73,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
+@ContextConfiguration(classes = {BaseDeviceProfileControllerTest.Config.class})
public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest {
private IdComparator idComparator = new IdComparator<>();
@@ -77,9 +82,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
private Tenant savedTenant;
private User tenantAdmin;
- @SpyBean
+ @Autowired
private DeviceProfileDao deviceProfileDao;
+ static class Config {
+ @Bean
+ @Primary
+ public DeviceProfileDao deviceProfileDao(DeviceProfileDao deviceProfileDao) {
+ return Mockito.mock(DeviceProfileDao.class, AdditionalAnswers.delegatesTo(deviceProfileDao));
+ }
+ }
+
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@@ -103,8 +116,6 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
public void afterTest() throws Exception {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (deviceProfileDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@@ -1149,4 +1160,5 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
DeviceProfile deviceProfile = createDeviceProfile(name);
return doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
}
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
index c3d747cb35..4d852051fc 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
@@ -21,8 +21,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
@@ -44,6 +48,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
+import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
@@ -64,6 +69,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@TestPropertySource(properties = {
"edges.enabled=true",
})
+@ContextConfiguration(classes = {BaseEdgeControllerTest.Config.class})
public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public static final String EDGE_HOST = "localhost";
@@ -75,10 +81,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
private TenantId tenantId;
private User tenantAdmin;
- @SpyBean
+ @Autowired
private EdgeDao edgeDao;
- @Before
+ static class Config {
+ @Bean
+ @Primary
+ public EdgeDao edgeDao(EdgeDao edgeDao) {
+ return Mockito.mock(EdgeDao.class, AdditionalAnswers.delegatesTo(edgeDao));
+ }
+ }
+
+ @Before
public void beforeTest() throws Exception {
loginSysAdmin();
@@ -102,8 +116,6 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public void afterTest() throws Exception {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException (edgeDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@@ -808,7 +820,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
- edgeImitator.expectMessageAmount(12);
+ edgeImitator.expectMessageAmount(14);
edgeImitator.connect();
assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue();
@@ -816,17 +828,19 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(1);
+ assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4);
- edgeImitator.expectMessageAmount(9);
+ edgeImitator.expectMessageAmount(11);
doPost("/api/edge/sync/" + edge.getId());
assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue();
assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(1);
+ assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(2);
assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1);
assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4);
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
index 5bddb9b3a7..fe3fefc4e7 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
@@ -31,8 +31,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.ThingsBoardExecutors;
@@ -84,6 +88,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
"js.evaluator=mock",
})
@Slf4j
+@ContextConfiguration(classes = {BaseEntityViewControllerTest.Config.class})
public abstract class BaseEntityViewControllerTest extends AbstractControllerTest {
static final TypeReference> PAGE_DATA_ENTITY_VIEW_TYPE_REF = new TypeReference<>() {
};
@@ -96,9 +101,17 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
List> deleteFutures = new ArrayList<>();
ListeningExecutorService executor;
- @SpyBean
+ @Autowired
private EntityViewDao entityViewDao;
+ static class Config {
+ @Bean
+ @Primary
+ public EntityViewDao entityViewDao(EntityViewDao entityViewDao) {
+ return Mockito.mock(EntityViewDao.class, AdditionalAnswers.delegatesTo(entityViewDao));
+ }
+ }
+
@Before
public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
@@ -120,9 +133,6 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@After
public void afterTest() throws Exception {
-
- afterTestEntityDaoRemoveByIdWithException (entityViewDao);
-
executor.shutdownNow();
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
index bec15dac8c..d32b1baa6b 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java
@@ -20,8 +20,12 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@@ -43,6 +47,7 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+@ContextConfiguration(classes = {BaseRuleChainControllerTest.Config.class})
public abstract class BaseRuleChainControllerTest extends AbstractControllerTest {
private IdComparator idComparator = new IdComparator<>();
@@ -50,9 +55,17 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
private Tenant savedTenant;
private User tenantAdmin;
- @SpyBean
+ @Autowired
private RuleChainDao ruleChainDao;
+ static class Config {
+ @Bean
+ @Primary
+ public RuleChainDao ruleChainDao(RuleChainDao ruleChainDao) {
+ return Mockito.mock(RuleChainDao.class, AdditionalAnswers.delegatesTo(ruleChainDao));
+ }
+ }
+
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@@ -76,8 +89,6 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest
public void afterTest() throws Exception {
loginSysAdmin();
- afterTestEntityDaoRemoveByIdWithException(ruleChainDao);
-
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
index 141896ed4b..9c01f4323d 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
@@ -21,9 +21,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.AdditionalAnswers;
import org.mockito.Mockito;
-import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
@@ -51,20 +55,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.SYSTEM_TENANT;
+@ContextConfiguration(classes = {BaseUserControllerTest.Config.class})
public abstract class BaseUserControllerTest extends AbstractControllerTest {
private IdComparator idComparator = new IdComparator<>();
private CustomerId customerNUULId = (CustomerId) createEntityId_NULL_UUID(new Customer());
- @SpyBean
+ @Autowired
private UserDao userDao;
+ static class Config {
+ @Bean
+ @Primary
+ public UserDao userDao(UserDao userDao) {
+ return Mockito.mock(UserDao.class, AdditionalAnswers.delegatesTo(userDao));
+ }
+ }
+
@After
public void afterTest() throws Exception {
loginSysAdmin();
-
- afterTestEntityDaoRemoveByIdWithException(userDao);
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/AssetProfileControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/AssetProfileControllerSqlTest.java
new file mode 100644
index 0000000000..9913369db6
--- /dev/null
+++ b/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 {
+}
diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
index dcd354ace5..e2a3b6e2d1 100644
--- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
+++ b/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();
diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
index 1c0436d329..aacea38b12 100644
--- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
+++ b/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));
diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java
index e516d5e131..48eb0c9e27 100644
--- a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java
+++ b/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")));
diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
index 8cf57e8e37..cc27763435 100644
--- a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
+++ b/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 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 importResult = importEntity(tenantAdmin2, exportData);
- checkImportedEntity(tenantId1, asset, tenantId2, importResult.getSavedEntity());
- checkImportedAssetData(asset, importResult.getSavedEntity());
+ EntityExportData profileExportData = exportEntity(tenantAdmin1, assetProfile.getId());
+
+ EntityExportData assetExportData = exportEntity(tenantAdmin1, asset.getId());
+
+ EntityImportResult profileImportResult = importEntity(tenantAdmin2, profileExportData);
+ checkImportedEntity(tenantId1, assetProfile, tenantId2, profileImportResult.getSavedEntity());
+ checkImportedAssetProfileData(assetProfile, profileImportResult.getSavedEntity());
+
+ EntityImportResult 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 exportData = exportEntity(tenantAdmin1, asset.getId());
EntityImportResult 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.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 profileExportData = exportEntity(tenantAdmin1, assetProfile.getId());
+
EntityExportData asset1ExportData = exportEntity(tenantAdmin1, asset1.getId());
EntityExportData asset2ExportData = exportEntity(tenantAdmin1, asset2.getId());
EntityExportData 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 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 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());
diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
index 7d5c17bf6d..cf8b47c82e 100644
--- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java
+++ b/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,
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetProfileService.java
new file mode 100644
index 0000000000..c86463336a
--- /dev/null
+++ b/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 findAssetProfiles(TenantId tenantId, PageLink pageLink);
+
+ PageData 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);
+
+}
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
index b86abd81c7..955444ac52 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
+++ b/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 findAssetInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
+ PageData findAssetInfosByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink);
+
ListenableFuture> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List assetIds);
void deleteAssetsByTenantId(TenantId tenantId);
@@ -69,6 +70,8 @@ public interface AssetService {
PageData findAssetInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
+ PageData findAssetInfosByTenantIdAndCustomerIdAndAssetProfileId(TenantId tenantId, CustomerId customerId, AssetProfileId assetProfileId, PageLink pageLink);
+
ListenableFuture> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List assetIds);
void unassignCustomerAssets(TenantId tenantId, CustomerId customerId);
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
index 6497778676..5a5a78c2a2 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
@@ -39,6 +39,8 @@ public interface AttributesService {
ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes);
+ ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute);
+
ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys);
List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
index 2fc2d7dcfb..42d6d10a1a 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
+++ b/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);
}
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
index cf17eec88d..19d9cef81e 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
/**
* @author Andrew Shvayka
@@ -37,6 +38,8 @@ public interface TimeseriesService {
ListenableFuture> findAll(TenantId tenantId, EntityId entityId, List queries);
+ ListenableFuture> findLatest(TenantId tenantId, EntityId entityId, String keys);
+
ListenableFuture> findLatest(TenantId tenantId, EntityId entityId, Collection keys);
ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId);
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java
new file mode 100644
index 0000000000..801d4af1a6
--- /dev/null
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SqlDao {
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
index 3d5578adf8..0c5ed80cd1 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
+++ b/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";
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
index 0155aecc96..44eff251c7 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
+++ b/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:
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
index 1ef5bb433a..0798647903 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
+++ b/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;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
index f4ad335a43..e8eeaf75e9 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
+++ b/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 implements
@Length(fieldName = "label")
private String label;
+ private AssetProfileId assetProfileId;
+
@Getter @Setter
private AssetId externalId;
@@ -70,6 +73,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo 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 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 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 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=");
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
index a6a532cbe6..df2d71d436 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
+++ b/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;
}
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java
new file mode 100644
index 0000000000..1bd9d9c6a5
--- /dev/null
+++ b/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 implements HasName, HasTenantId, ExportableEntity {
+
+ 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;
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfileInfo.java
new file mode 100644
index 0000000000..e2f37ad679
--- /dev/null
+++ b/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());
+ }
+
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
index 660fa945ca..2403a4ebbf 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
+++ b/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,
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java
new file mode 100644
index 0000000000..46971d87c4
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
index f8bc8fc089..da9a5c49eb 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
+++ b/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:
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
index 0e2929bef8..8dccecbc29 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
+++ b/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),
diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto
index 7272e6f96a..04d447cebf 100644
--- a/common/edge-api/src/main/proto/edge.proto
+++ b/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;
}
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
index 55b9f4dbd5..6d756af9dd 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
@@ -122,6 +122,16 @@ public final class TbMsg implements Serializable {
data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback);
}
+ public static TbMsg transformMsgData(TbMsg tbMsg, String data) {
+ return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType,
+ data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
+ }
+
+ public static TbMsg transformMsg(TbMsg tbMsg, TbMsgMetaData metadata) {
+ return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, metadata.copy(), tbMsg.dataType,
+ tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
+ }
+
public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) {
return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType,
tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback());
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
index b4572121a5..0afd4692e3 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
+++ b/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 */
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
index a9f75ff660..70d8c43cc6 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
+++ b/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;
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
index e4a9a2492f..7f8c333820 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
+++ b/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);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
index d842554ce0..35766e707a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
@@ -42,8 +42,6 @@ import java.util.UUID;
*/
public interface AlarmDao extends Dao {
- Boolean deleteAlarm(TenantId tenantId, Alarm alarm);
-
ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture findAlarmByIdAsync(TenantId tenantId, UUID key);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index f83a66fce9..7addad7051 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
@@ -153,7 +153,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm)));
deleteEntityRelations(tenantId, alarm.getId());
- alarmDao.deleteAlarm(tenantId, alarm);
+ alarmDao.removeById(tenantId, alarm.getUuidId());
return result;
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStats.java b/dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStats.java
new file mode 100644
index 0000000000..06efa38cc5
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStats.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.aspect;
+
+import lombok.Data;
+import org.springframework.data.util.Pair;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+@Data
+public class DbCallStats {
+
+ private final TenantId tenantId;
+ private final ConcurrentMap methodStats = new ConcurrentHashMap<>();
+ private final AtomicInteger successCalls = new AtomicInteger();
+ private final AtomicInteger failureCalls = new AtomicInteger();
+
+ public void onMethodCall(String methodName, boolean success, long executionTime) {
+ var methodCallStats = methodStats.computeIfAbsent(methodName, m -> new MethodCallStats());
+ methodCallStats.getExecutions().incrementAndGet();
+ methodCallStats.getTiming().addAndGet(executionTime);
+ if (success) {
+ successCalls.incrementAndGet();
+ } else {
+ failureCalls.incrementAndGet();
+ methodCallStats.getFailures().incrementAndGet();
+ }
+ }
+
+ public DbCallStatsSnapshot snapshot() {
+ return DbCallStatsSnapshot.builder()
+ .tenantId(tenantId)
+ .totalSuccess(successCalls.get())
+ .totalFailure(failureCalls.get())
+ .methodStats(methodStats.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().snapshot())))
+ .totalTiming(methodStats.values().stream().map(MethodCallStats::getTiming).map(AtomicLong::get).reduce(0L, Long::sum))
+ .build();
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStatsSnapshot.java b/dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStatsSnapshot.java
new file mode 100644
index 0000000000..b29e3a1a7a
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/aspect/DbCallStatsSnapshot.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.aspect;
+
+import lombok.Builder;
+import lombok.Data;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.Map;
+
+@Data
+@Builder
+public class DbCallStatsSnapshot {
+
+ private final TenantId tenantId;
+ private final int totalSuccess;
+ private final int totalFailure;
+ private final long totalTiming;
+ private final Map methodStats;
+
+ public int getTotalCalls() {
+ return totalSuccess + totalFailure;
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStats.java b/dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStats.java
new file mode 100644
index 0000000000..22d9086964
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStats.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.aspect;
+
+import lombok.Data;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+@Data
+public class MethodCallStats {
+ private final AtomicInteger executions = new AtomicInteger();
+ private final AtomicInteger failures = new AtomicInteger();
+ private final AtomicLong timing = new AtomicLong();
+
+ public MethodCallStatsSnapshot snapshot() {
+ return new MethodCallStatsSnapshot(executions.get(), failures.get(), timing.get());
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStatsSnapshot.java b/dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStatsSnapshot.java
new file mode 100644
index 0000000000..75eff38c6d
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/aspect/MethodCallStatsSnapshot.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.aspect;
+
+import lombok.Data;
+
+@Data
+public class MethodCallStatsSnapshot {
+ private final int executions;
+ private final int failures;
+ private final long timing;
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java b/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java
new file mode 100644
index 0000000000..ec8c97e19f
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java
@@ -0,0 +1,214 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.aspect;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.hibernate.exception.JDBCConnectionException;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@Aspect
+@ConditionalOnProperty(prefix = "sql", value = "log_tenant_stats", havingValue = "true")
+@Component
+@Slf4j
+public class SqlDaoCallsAspect {
+
+ private final Set invalidTenantDbCallMethods = ConcurrentHashMap.newKeySet();
+ private final ConcurrentMap statsMap = new ConcurrentHashMap<>();
+
+ @Scheduled(initialDelayString = "${sql.log_tenant_stats_interval_ms:60000}",
+ fixedDelayString = "${sql.log_tenant_stats_interval_ms:60000}")
+ public void printStats() {
+ List snapshots = snapshot();
+ if (snapshots.isEmpty()) return;
+ try {
+ if (log.isTraceEnabled()) {
+ logTopNTenants(snapshots, Comparator.comparing(DbCallStatsSnapshot::getTotalTiming).reversed(), 0, snapshot -> {
+ logSnapshot(snapshot, 0, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), log::trace);
+ });
+
+ Map> byMethodStats = new HashMap<>();
+ for (DbCallStatsSnapshot snapshot : snapshots) {
+ snapshot.getMethodStats().forEach((method, stats) -> {
+ byMethodStats.computeIfAbsent(method, m -> new HashMap<>())
+ .put(snapshot.getTenantId(), stats);
+ });
+ }
+ byMethodStats.forEach((method, byTenantStats) -> {
+ log.trace("Top tenants for method {} by calls:", method);
+ byTenantStats.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue(Comparator.comparing(MethodCallStatsSnapshot::getExecutions).reversed()))
+ .limit(10)
+ .forEach(e -> {
+ TenantId tenantId = e.getKey();
+ MethodCallStatsSnapshot methodStats = e.getValue();
+ log.trace("[{}] calls: {}, failures: {}, timing: {}", tenantId,
+ methodStats.getExecutions(), methodStats.getFailures(), methodStats.getTiming());
+ });
+ });
+ } else if (log.isDebugEnabled()) {
+ log.debug("Total calls statistics below:");
+ logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalCalls).reversed(), 10,
+ s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getExecutions).reversed(), log::debug));
+ log.debug("Total timing statistics below:");
+ logTopNTenants(snapshots, Comparator.comparingLong(DbCallStatsSnapshot::getTotalTiming).reversed(),
+ 10, s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), log::debug));
+ log.debug("Total errors statistics below:");
+ logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(),
+ 10, s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), log::debug));
+ } else if (log.isInfoEnabled()) {
+ log.info("Total calls statistics below:");
+ logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(),
+ 3, s -> logSnapshot(s, 3, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), log::info));
+ }
+ } finally {
+ statsMap.clear();
+ }
+ }
+
+ private void logSnapshot(DbCallStatsSnapshot snapshot, int limit, Comparator methodStatsComparator, Consumer logger) {
+ logger.accept(String.format("[%s]: calls: %s, failures: %s, exec time: %s ",
+ snapshot.getTenantId(), snapshot.getTotalCalls(), snapshot.getTotalFailure(), snapshot.getTotalTiming()));
+ var stream = snapshot.getMethodStats().entrySet().stream()
+ .sorted(Map.Entry.comparingByValue(methodStatsComparator));
+ if (limit > 0) {
+ stream = stream.limit(limit);
+ }
+ stream.forEach(e -> {
+ MethodCallStatsSnapshot methodStats = e.getValue();
+ logger.accept(String.format("[%s]: method: %s, calls: %s, failures: %s, exec time: %s", snapshot.getTenantId(), e.getKey(),
+ methodStats.getExecutions(), methodStats.getFailures(), methodStats.getTiming()));
+ });
+ }
+
+ private List snapshot() {
+ return statsMap.values().stream().map(DbCallStats::snapshot).collect(Collectors.toList());
+ }
+
+ private void logTopNTenants(List snapshots, Comparator comparator,
+ int n, Consumer logFunction) {
+ var stream = snapshots.stream().sorted(comparator);
+ if (n > 0) {
+ stream = stream.limit(n);
+ }
+ stream.forEach(logFunction);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Around("@within(org.thingsboard.server.dao.util.SqlDao)")
+ public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ var methodName = signature.toShortString();
+ if (invalidTenantDbCallMethods.contains(methodName)) {
+ //Simply call the method if tenant is not found
+ return joinPoint.proceed();
+ }
+ var tenantId = getTenantId(signature, methodName, joinPoint.getArgs());
+ if (tenantId == null || tenantId.isNullUid()) {
+ //Simply call the method if tenant is null
+ return joinPoint.proceed();
+ }
+ var startTime = System.currentTimeMillis();
+ try {
+ var result = joinPoint.proceed();
+ if (result instanceof ListenableFuture) {
+ Futures.addCallback((ListenableFuture) result,
+ new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable Object result) {
+ logTenantMethodExecution(tenantId, methodName, true, startTime, null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logTenantMethodExecution(tenantId, methodName, false, startTime, t);
+ }
+ },
+ MoreExecutors.directExecutor());
+ } else {
+ logTenantMethodExecution(tenantId, methodName, true, startTime, null);
+ }
+ return result;
+ } catch (Throwable t) {
+ logTenantMethodExecution(tenantId, methodName, false, startTime, t);
+ throw t;
+ }
+ }
+
+ private void logTenantMethodExecution(TenantId tenantId, String method, boolean success, long startTime, Throwable t) {
+ if (!success && ExceptionUtils.indexOfThrowable(t, JDBCConnectionException.class) >= 0) {
+ return;
+ }
+ statsMap.computeIfAbsent(tenantId, DbCallStats::new)
+ .onMethodCall(method, success, System.currentTimeMillis() - startTime);
+ }
+
+ TenantId getTenantId(MethodSignature signature, String methodName, Object[] args) {
+ if (args == null || args.length == 0) {
+ addAndLogInvalidMethods(methodName);
+ return null;
+ }
+ for (int i = 0; i < args.length; i++) {
+ Object arg = args[i];
+ if (arg instanceof TenantId) {
+ return (TenantId) arg;
+ } else if (arg instanceof UUID) {
+ if (signature.getParameterNames() != null && StringUtils.equals(signature.getParameterNames()[i], "tenantId")) {
+ log.trace("Method {} uses UUID for tenantId param instead of TenantId class", methodName);
+ return TenantId.fromUUID((UUID) arg);
+ }
+ }
+ }
+ if (ArrayUtils.contains(signature.getParameterTypes(), TenantId.class) ||
+ ArrayUtils.contains(signature.getParameterNames(), "tenantId")) {
+ log.debug("Null was submitted as tenantId to method {}. Args: {}", methodName, Arrays.toString(args));
+ } else {
+ addAndLogInvalidMethods(methodName);
+ }
+ return null;
+ }
+
+ private void addAndLogInvalidMethods(String methodName) {
+ invalidTenantDbCallMethods.add(methodName);
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
index e3c51b48a1..4d67a794b6 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
@@ -92,6 +92,16 @@ public interface AssetDao extends Dao, TenantEntityDao, ExportableEntityD
*/
PageData 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 findAssetInfosByTenantIdAndAssetProfileId(UUID tenantId, UUID assetProfileId, PageLink pageLink);
+
/**
* Find assets by tenantId and assets Ids.
*
@@ -143,6 +153,17 @@ public interface AssetDao extends Dao, TenantEntityDao, ExportableEntityD
*/
PageData 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 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, TenantEntityDao, ExportableEntityD
*/
ListenableFuture> 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 findAssetsByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink);
+
/**
* Find assets by tenantId, edgeId and page link.
*
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCacheKey.java
new file mode 100644
index 0000000000..2950dca45e
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileCaffeineCache.java
new file mode 100644
index 0000000000..02c4ece8bc
--- /dev/null
+++ b/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 {
+
+ public AssetProfileCaffeineCache(CacheManager cacheManager) {
+ super(cacheManager, CacheConstants.ASSET_PROFILE_CACHE);
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileDao.java
new file mode 100644
index 0000000000..4e36aa5023
--- /dev/null
+++ b/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, ExportableEntityDao {
+
+ AssetProfileInfo findAssetProfileInfoById(TenantId tenantId, UUID assetProfileId);
+
+ AssetProfile save(TenantId tenantId, AssetProfile assetProfile);
+
+ AssetProfile saveAndFlush(TenantId tenantId, AssetProfile assetProfile);
+
+ PageData findAssetProfiles(TenantId tenantId, PageLink pageLink);
+
+ PageData findAssetProfileInfos(TenantId tenantId, PageLink pageLink);
+
+ AssetProfile findDefaultAssetProfile(TenantId tenantId);
+
+ AssetProfileInfo findDefaultAssetProfileInfo(TenantId tenantId);
+
+ AssetProfile findByName(TenantId tenantId, String profileName);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileEvictEvent.java
new file mode 100644
index 0000000000..880ca83b7d
--- /dev/null
+++ b/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;
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileRedisCache.java
new file mode 100644
index 0000000000..ed917317ad
--- /dev/null
+++ b/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 {
+
+ public AssetProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
+ super(CacheConstants.ASSET_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
new file mode 100644
index 0000000000..3b0f2d32e6
--- /dev/null
+++ b/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 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 assetProfileValidator;
+
+ @TransactionalEventListener(classes = AssetProfileEvictEvent.class)
+ @Override
+ public void handleEvictEvent(AssetProfileEvictEvent event) {
+ List 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 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 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 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 tenantAssetProfilesRemover =
+ new PaginatedRemover<>() {
+
+ @Override
+ protected PageData 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());
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index 3ffcc999c3..15d6df1327 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/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 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 assetValidator;
@@ -122,6 +129,24 @@ public class BaseAssetService extends AbstractCachedEntityService 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> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List assetIds) {
log.trace("Executing findAssetsByTenantIdAndIdsAsync, tenantId [{}], assetIds [{}]", tenantId, assetIds);
@@ -253,6 +287,16 @@ public class BaseAssetService extends AbstractCachedEntityService 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> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List assetIds) {
log.trace("Executing findAssetsByTenantIdAndCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], assetIds [{}]", tenantId, customerId, assetIds);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java
index 97c263ad97..d9b7251f21 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java
@@ -79,6 +79,13 @@ public class BaseAttributesService implements AttributesService {
return attributesDao.findAllKeysByEntityIds(tenantId, entityType, entityIds);
}
+ @Override
+ public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) {
+ validate(entityId, scope);
+ AttributeUtils.validate(attribute);
+ return attributesDao.save(tenantId, entityId, scope, attribute);
+ }
+
@Override
public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) {
validate(entityId, scope);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
index 802e746122..18c35741ff 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
@@ -205,6 +205,14 @@ public class CachedAttributesService implements AttributesService {
return attributesDao.findAllKeysByEntityIds(tenantId, entityType, entityIds);
}
+ @Override
+ public ListenableFuture save(TenantId tenantId, EntityId entityId, String scope, AttributeKvEntry attribute) {
+ validate(entityId, scope);
+ AttributeUtils.validate(attribute);
+ ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute);
+ return Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor);
+ }
+
@Override
public ListenableFuture> save(TenantId tenantId, EntityId entityId, String scope, List attributes) {
validate(entityId, scope);
@@ -213,17 +221,19 @@ public class CachedAttributesService implements AttributesService {
List> futures = new ArrayList<>(attributes.size());
for (var attribute : attributes) {
ListenableFuture future = attributesDao.save(tenantId, entityId, scope, attribute);
- futures.add(Futures.transform(future, key -> {
- log.trace("[{}][{}][{}] Before cache evict: {}", entityId, scope, key, attribute);
- cache.evictOrPut(new AttributeCacheKey(scope, entityId, key), attribute);
- log.trace("[{}][{}][{}] after cache evict.", entityId, scope, key);
- return key;
- }, cacheExecutor));
+ futures.add(Futures.transform(future, key -> evict(entityId, scope, attribute, key), cacheExecutor));
}
return Futures.allAsList(futures);
}
+ private String evict(EntityId entityId, String scope, AttributeKvEntry attribute, String key) {
+ log.trace("[{}][{}][{}] Before cache evict: {}", entityId, scope, key, attribute);
+ cache.evictOrPut(new AttributeCacheKey(scope, entityId, key), attribute);
+ log.trace("[{}][{}][{}] after cache evict.", entityId, scope, key);
+ return key;
+ }
+
@Override
public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) {
validate(entityId, scope);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
index b6ea2b8c4f..c4cbabfb3d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
+++ b/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 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 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 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 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);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java
index a0c4dd0563..bdcf1bce35 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java
@@ -30,10 +30,12 @@ public class AssetInfoEntity extends AbstractAssetEntity {
public static final Map 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 {
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 {
} 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);
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetProfileEntity.java
new file mode 100644
index 0000000000..36f7b40487
--- /dev/null
+++ b/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 implements SearchTextEntity {
+
+ @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;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
index 9bf729ad25..28223f3e7f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
+++ b/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> {
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_.\\-]+$");
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
index b7685a0476..6c9e7792ac 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
@@ -73,9 +73,6 @@ public class AssetDataValidator extends DataValidator {
@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!");
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetProfileDataValidator.java
new file mode 100644
index 0000000000..c880474629
--- /dev/null
+++ b/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 {
+
+ @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;
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
index ad6bb5ccec..81fc5d5ad9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
@@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseEntity;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.Collection;
import java.util.List;
@@ -35,6 +36,7 @@ import java.util.UUID;
* @author Valerii Sosliuk
*/
@Slf4j
+@SqlDao
public abstract class JpaAbstractDao, D>
extends JpaAbstractDaoListeningExecutorService
implements Dao {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
index 8e165898a2..dc08d755b2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.EntityAlarmEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.query.AlarmQueryRepository;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.Collection;
import java.util.Collections;
@@ -55,6 +56,7 @@ import java.util.UUID;
*/
@Slf4j
@Component
+@SqlDao
public class JpaAlarmDao extends JpaAbstractDao implements AlarmDao {
@Autowired
@@ -76,11 +78,6 @@ public class JpaAlarmDao extends JpaAbstractDao implements A
return alarmRepository;
}
- @Override
- public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) {
- return removeById(tenantId, alarm.getUuidId());
- }
-
@Override
public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return service.submit(() -> {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetProfileRepository.java
new file mode 100644
index 0000000000..34545fb04c
--- /dev/null
+++ b/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, ExportableEntityRepository {
+
+ @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 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 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);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
index 6d4667d8ec..eadf0d2b84 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
+++ b/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, ExportableEntityRepository {
- @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, 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 findAssetInfosByTenantId(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch,
Pageable pageable);
@@ -61,9 +66,18 @@ public interface AssetRepository extends JpaRepository, 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 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, 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 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 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, 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, 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 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 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' " +
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
index b18d6c5f51..489e15502e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
@@ -33,6 +33,7 @@ import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.model.sql.AssetEntity;
import org.thingsboard.server.dao.model.sql.AssetInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,6 +48,7 @@ import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE
* Created by Valerii Sosliuk on 5/19/2017.
*/
@Component
+@SqlDao
@Slf4j
public class JpaAssetDao extends JpaAbstractSearchTextDao implements AssetDao {
@@ -144,6 +146,16 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
+ @Override
+ public PageData 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 findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) {
return DaoUtil.toPageData(assetRepository
@@ -166,11 +178,37 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
+ @Override
+ public PageData 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> 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 findAssetsByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink) {
+ return DaoUtil.toPageData(
+ assetRepository.findByTenantIdAndProfileId(
+ tenantId,
+ profileId,
+ Objects.toString(pageLink.getTextSearch(), ""),
+ DaoUtil.toPageable(pageLink)));
+ }
+
private List convertTenantAssetTypesToDto(UUID tenantId, List types) {
List list = Collections.emptyList();
if (types != null && !types.isEmpty()) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetProfileDao.java
new file mode 100644
index 0000000000..9f71b97da2
--- /dev/null
+++ b/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 implements AssetProfileDao {
+
+ @Autowired
+ private AssetProfileRepository assetProfileRepository;
+
+ @Override
+ protected Class getEntityClass() {
+ return AssetProfileEntity.class;
+ }
+
+ @Override
+ protected JpaRepository 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 findAssetProfiles(TenantId tenantId, PageLink pageLink) {
+ return DaoUtil.toPageData(
+ assetProfileRepository.findAssetProfiles(
+ tenantId.getId(),
+ Objects.toString(pageLink.getTextSearch(), ""),
+ DaoUtil.toPageable(pageLink)));
+ }
+
+ @Override
+ public PageData 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 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;
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
index 169ef55fbe..e040789f65 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
@@ -37,6 +37,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
+import org.thingsboard.server.dao.util.SqlDao;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@@ -50,6 +51,7 @@ import java.util.stream.Collectors;
@Component
@Slf4j
+@SqlDao
public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService implements AttributesDao {
@Autowired
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
index 94e9e6718f..a3ee6ea843 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
@@ -34,6 +34,7 @@ import org.thingsboard.server.dao.audit.AuditLogDao;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.AuditLogEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
+import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import java.util.List;
@@ -42,6 +43,7 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
+@SqlDao
@RequiredArgsConstructor
@Slf4j
public class JpaAuditLogDao extends JpaAbstractDao implements AuditLogDao {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
index ab3a4b4f8d..2db855db28 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.model.sql.CustomerEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.Objects;
import java.util.Optional;
@@ -37,6 +38,7 @@ import java.util.UUID;
* Created by Valerii Sosliuk on 5/6/2017.
*/
@Component
+@SqlDao
public class JpaCustomerDao extends JpaAbstractSearchTextDao implements CustomerDao {
@Autowired
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
index 425c54c647..2e72155363 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.model.sql.DashboardEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.Optional;
@@ -37,6 +38,7 @@ import java.util.UUID;
* Created by Valerii Sosliuk on 5/6/2017.
*/
@Component
+@SqlDao
public class JpaDashboardDao extends JpaAbstractSearchTextDao implements DashboardDao {
@Autowired
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
index d98312e7d5..98efbe917e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
@@ -27,6 +27,7 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.List;
@@ -38,6 +39,7 @@ import java.util.UUID;
*/
@Slf4j
@Component
+@SqlDao
public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao implements DashboardInfoDao {
@Autowired
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java
index e88b09a2bc..b1ffacc0e9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java
@@ -25,6 +25,7 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.device.DeviceCredentialsDao;
import org.thingsboard.server.dao.model.sql.DeviceCredentialsEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.UUID;
@@ -32,6 +33,7 @@ import java.util.UUID;
* Created by Valerii Sosliuk on 5/6/2017.
*/
@Component
+@SqlDao
public class JpaDeviceCredentialsDao extends JpaAbstractDao implements DeviceCredentialsDao {
@Autowired
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
index 62516232d2..a00a1ba48f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
@@ -41,6 +41,7 @@ import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.model.sql.DeviceEntity;
import org.thingsboard.server.dao.model.sql.DeviceInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.Collections;
@@ -53,6 +54,7 @@ import java.util.UUID;
* Created by Valerii Sosliuk on 5/6/2017.
*/
@Component
+@SqlDao
@Slf4j
public class JpaDeviceDao extends JpaAbstractSearchTextDao implements DeviceDao {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
index 6f4cd8587a..1a30c6627d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java
@@ -32,12 +32,14 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
+import org.thingsboard.server.dao.util.SqlDao;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@Component
+@SqlDao
public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao implements DeviceProfileDao {
@Autowired
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java
index 2f05f66cdf..df182659bf 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java
@@ -36,6 +36,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
+import org.thingsboard.server.dao.util.SqlDao;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@@ -53,6 +54,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Slf4j
@Component
+@SqlDao
public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao