From e2dd5b96aef5aca4c6a21012a5bbbda9b489169d Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Tue, 9 Mar 2021 19:45:28 +0200 Subject: [PATCH] added resource dao support (#4213) * Version set to 3.3.0-SNAPSHOT * added resource dao * added resource support in transport lvl (get resources and "update", "delete" notifications) * refactoring * added resource table to hsql schema * added check for models dir in InstallScripts * added pageLink support to getResources --- .../server/controller/ResourceController.java | 90 +++++++++++++++++ .../install/ThingsboardInstallService.java | 7 ++ .../DefaultSystemDataLoaderService.java | 5 + .../service/install/InstallScripts.java | 48 +++++++++- .../install/SqlDatabaseUpgradeService.java | 19 ++++ .../install/SystemDataLoaderService.java | 2 + .../service/lwm2m/LwM2MModelsRepository.java | 1 + .../queue/DefaultTbClusterService.java | 29 ++++++ .../service/queue/TbClusterService.java | 5 + .../transport/DefaultTransportApiService.java | 57 ++++++++++- .../server/dao/resource/ResourceService.java | 35 +++++++ .../data/transport/resource/Resource.java | 37 +++++++ .../data/transport/resource/ResourceType.java | 20 ++++ .../queue/util/TbLwM2mTransportComponent.java | 2 +- common/queue/src/main/proto/queue.proto | 37 +++++++ .../common/transport/TransportService.java | 4 + .../service/DefaultTransportService.java | 17 ++++ .../server/dao/model/ModelConstants.java | 9 ++ .../dao/model/sql/ResourceCompositeKey.java | 45 +++++++++ .../server/dao/model/sql/ResourceEntity.java | 77 +++++++++++++++ .../dao/resource/BaseResourceService.java | 96 +++++++++++++++++++ .../server/dao/resource/ResourceDao.java | 35 +++++++ .../dao/sql/resource/ResourceDaoImpl.java | 77 +++++++++++++++ .../dao/sql/resource/ResourceRepository.java | 31 ++++++ .../server/dao/tenant/TenantServiceImpl.java | 5 + .../resources/sql/schema-entities-hsql.sql | 8 ++ .../main/resources/sql/schema-entities.sql | 8 ++ .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 3 +- 29 files changed, 801 insertions(+), 9 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/ResourceController.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceCompositeKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/resource/ResourceDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceDaoImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceRepository.java diff --git a/application/src/main/java/org/thingsboard/server/controller/ResourceController.java b/application/src/main/java/org/thingsboard/server/controller/ResourceController.java new file mode 100644 index 0000000000..8939fa28b7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/ResourceController.java @@ -0,0 +1,90 @@ +/** + * Copyright © 2016-2021 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 lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +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.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +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.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; +import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Slf4j +@RestController +@TbCoreComponent +@RequestMapping("/api") +public class ResourceController extends BaseController { + + private final ResourceService resourceService; + + public ResourceController(ResourceService resourceService) { + this.resourceService = resourceService; + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource", method = RequestMethod.POST) + @ResponseBody + public Resource saveResource(Resource resource) throws ThingsboardException { + try { + resource.setTenantId(getTenantId()); + Resource savedResource = checkNotNull(resourceService.saveResource(resource)); + tbClusterService.onResourceChange(savedResource, null); + return savedResource; + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource", method = RequestMethod.GET) + @ResponseBody + public PageData getResources(@RequestParam(required = false) boolean system, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder); + return checkNotNull(resourceService.findResourcesByTenantId(system ? TenantId.SYS_TENANT_ID : getTenantId(), pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource/{resourceType}/{resourceId}", method = RequestMethod.DELETE) + @ResponseBody + public void deleteResource(@PathVariable("resourceType") ResourceType resourceType, + @PathVariable("resourceId") String resourceId) throws ThingsboardException { + try { + Resource resource = checkNotNull(resourceService.getResource(getTenantId(), resourceType, resourceId)); + resourceService.deleteResource(getTenantId(), resourceType, resourceId); + tbClusterService.onResourceDeleted(resource, null); + } catch (Exception e) { + throw handleException(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 8b4a5b77a3..63eea2a7fc 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -194,6 +194,12 @@ public class ThingsboardInstallService { log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; + case "3.2.2": + log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); + + log.info("Updating system data..."); + break; default: throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); @@ -226,6 +232,7 @@ public class ThingsboardInstallService { systemDataLoaderService.createAdminSettings(); systemDataLoaderService.loadSystemWidgets(); systemDataLoaderService.createOAuth2Templates(); + systemDataLoaderService.loadSystemLwm2mResources(); // systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemRules(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 9028fe279e..6f257a367e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -445,6 +445,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { installScripts.loadSystemWidgets(); } + @Override + public void loadSystemLwm2mResources() throws Exception { + installScripts.loadSystemLwm2mResources(); + } + private User createUser(Authority authority, TenantId tenantId, CustomerId customerId, diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 47f8529c11..36b85da719 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -24,15 +24,17 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -42,6 +44,7 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; import java.util.Optional; import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; @@ -66,8 +69,11 @@ public class InstallScripts { public static final String WIDGET_BUNDLES_DIR = "widget_bundles"; public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates"; public static final String DASHBOARDS_DIR = "dashboards"; + public static final String MODELS_DIR = "models"; + public static final String CREDENTIALS_DIR = "credentials"; public static final String JSON_EXT = ".json"; + public static final String XML_EXT = ".xml"; @Value("${install.data_dir:}") private String dataDir; @@ -87,6 +93,9 @@ public class InstallScripts { @Autowired private OAuth2ConfigTemplateService oAuth2TemplateService; + @Autowired + private ResourceService resourceService; + public Path getTenantRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); } @@ -186,6 +195,42 @@ public class InstallScripts { } } + public void loadSystemLwm2mResources() throws Exception { + Path modelsDir = Paths.get(getDataDir(), MODELS_DIR); + if (Files.isDirectory(modelsDir)) { + try (DirectoryStream dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) { + dirStream.forEach( + path -> { + try { + Resource resource = new Resource(); + resource.setTenantId(TenantId.SYS_TENANT_ID); + resource.setResourceType(ResourceType.LWM2M_MODEL); + resource.setResourceId(path.getFileName().toString()); + resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(path))); + resourceService.saveResource(resource); + } catch (Exception e) { + log.error("Unable to load lwm2m model [{}]", path.toString()); + throw new RuntimeException("Unable to load lwm2m model", e); + } + } + ); + } + } + + Path jksPath = Paths.get(getDataDir(), CREDENTIALS_DIR, "serverKeyStore.jks"); + try { + Resource resource = new Resource(); + resource.setTenantId(TenantId.SYS_TENANT_ID); + resource.setResourceType(ResourceType.JKS); + resource.setResourceId(jksPath.getFileName().toString()); + resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(jksPath))); + resourceService.saveResource(resource); + } catch (Exception e) { + log.error("Unable to load lwm2m serverKeyStore [{}]", jksPath.toString()); + throw new RuntimeException("Unable to load l2m2m serverKeyStore", e); + } + } + public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); try (DirectoryStream dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { @@ -208,7 +253,6 @@ public class InstallScripts { } } - public void loadDemoRuleChains(TenantId tenantId) throws Exception { try { createDefaultRuleChains(tenantId); 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 c3495d960b..a56cc7ca25 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 @@ -434,6 +434,25 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.info("Schema updated."); } break; + case "3.2.2": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + try { + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource (" + + " tenant_id uuid NOT NULL," + + " resource_type varchar(32) NOT NULL," + + " resource_id varchar(255) NOT NULL," + + " resource_value varchar," + + " CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id)" + + " );"); + + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;"); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); + } + log.info("Schema updated."); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 73e2b6ea57..a6f33f476f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -33,4 +33,6 @@ public interface SystemDataLoaderService { void deleteSystemWidgetBundle(String bundleAlias) throws Exception; + void loadSystemLwm2mResources() throws Exception; + } diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java index 5057893d5c..ddadc6c95c 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import java.math.BigInteger; 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 133447022b..3405ab28d8 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 @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.transport.resource.Resource; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -247,6 +248,34 @@ public class DefaultTbClusterService implements TbClusterService { onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); } + @Override + public void onResourceChange(Resource resource, TbQueueCallback callback) { + TenantId tenantId = resource.getTenantId(); + log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceId()); + TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setResourceType(resource.getResourceType().name()) + .setResourceId(resource.getResourceId()) + .build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceUpdateMsg(resourceUpdateMsg).build(); + broadcast(transportMsg, callback); + } + + @Override + public void onResourceDeleted(Resource resource, TbQueueCallback callback) { + TenantId tenantId = resource.getTenantId(); + log.trace("[{}][{}][{}] Processing delete resource", tenantId, resource.getResourceType(), resource.getResourceId()); + TransportProtos.ResourceDeleteMsg resourceUpdateMsg = TransportProtos.ResourceDeleteMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setResourceType(resource.getResourceType().name()) + .setResourceId(resource.getResourceId()) + .build(); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceDeleteMsg(resourceUpdateMsg).build(); + broadcast(transportMsg, callback); + } + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index 92401ea9c0..c1cafa3379 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.transport.resource.Resource; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; @@ -71,4 +72,8 @@ public interface TbClusterService { void onDeviceChange(Device device, TbQueueCallback callback); void onDeviceDeleted(Device device, TbQueueCallback callback); + + void onResourceChange(Resource resource, TbQueueCallback callback); + + void onResourceDeleted(Resource resource, TbQueueCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index b157e0d0a7..eea7af582c 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -25,6 +25,7 @@ import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -38,9 +39,12 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -53,14 +57,15 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetResourcesRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; @@ -77,11 +82,14 @@ import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; +import java.util.Collections; +import java.util.List; import java.util.UUID; 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.stream.Collectors; /** * Created by ashvayka on 05.10.18. @@ -104,6 +112,7 @@ public class DefaultTransportApiService implements TransportApiService { private final TbClusterService tbClusterService; private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; + private final ResourceService resourceService; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -112,7 +121,7 @@ public class DefaultTransportApiService implements TransportApiService { RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, - DeviceProvisionService deviceProvisionService) { + DeviceProvisionService deviceProvisionService, ResourceService resourceService) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.apiUsageStateService = apiUsageStateService; @@ -124,6 +133,7 @@ public class DefaultTransportApiService implements TransportApiService { this.tbClusterService = tbClusterService; this.dataDecodingEncodingService = dataDecodingEncodingService; this.deviceProvisionService = deviceProvisionService; + this.resourceService = resourceService; } @Override @@ -157,6 +167,9 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + } else if (transportApiRequestMsg.hasResourcesRequestMsg()) { + return Futures.transform(handle(transportApiRequestMsg.getResourcesRequestMsg()), + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); } return Futures.transform(getEmptyTransportApiResponseFuture(), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); @@ -315,9 +328,9 @@ public class DefaultTransportApiService implements TransportApiService { return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build(); } TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() - .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) - .setStatus(status); - switch (deviceCredentials.getCredentialsType()){ + .setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name())) + .setStatus(status); + switch (deviceCredentials.getCredentialsType()) { case ACCESS_TOKEN: provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsId()); break; @@ -353,6 +366,40 @@ public class DefaultTransportApiService implements TransportApiService { return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build()); } + private ListenableFuture handle(GetResourcesRequestMsg requestMsg) { + TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); + TransportProtos.GetResourcesResponseMsg.Builder builder = TransportProtos.GetResourcesResponseMsg.newBuilder(); + String resourceType = requestMsg.getResourceType(); + String resourceId = requestMsg.getResourceId(); + + List resources; + + if (resourceType != null && resourceId != null) { + resources = Collections.singletonList(toProto( + resourceService.getResource(tenantId, ResourceType.valueOf(resourceType), resourceId))); + } else { + //TODO: add page link params to request proto if need or remove this + resources = resourceService.findResourcesByTenantId(tenantId, new PageLink(100)) + .getData() + .stream() + .map(this::toProto) + .collect(Collectors.toList()); + } + + builder.addAllResources(resources); + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourcesResponseMsg(builder).build()); + } + + private TransportProtos.ResourceMsg toProto(Resource resource) { + return TransportProtos.ResourceMsg.newBuilder() + .setTenantIdMSB(resource.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(resource.getTenantId().getId().getLeastSignificantBits()) + .setResourceType(resource.getResourceType().name()) + .setResourceId(resource.getResourceId()) + .setValue(resource.getValue()) + .build(); + } + private ListenableFuture getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> { if (device == null) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java new file mode 100644 index 0000000000..d07b090494 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.resource; + +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.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; + + +public interface ResourceService { + Resource saveResource(Resource resource); + + Resource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); + + PageData findResourcesByTenantId(TenantId tenantId, PageLink pageLink); + + void deleteResource(TenantId tenantId, ResourceType resourceType, String resourceId); + + void deleteResourcesByTenantId(TenantId tenantId); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java new file mode 100644 index 0000000000..c29b704b04 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 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.transport.resource; + +import lombok.Data; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +public class Resource implements HasTenantId { + private TenantId tenantId; + private ResourceType resourceType; + private String resourceId; + private String value; + + @Override + public String toString() { + return "Resource{" + + "tenantId=" + tenantId + + ", resourceType=" + resourceType + + ", resourceId='" + resourceId + '\'' + + '}'; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java new file mode 100644 index 0000000000..c43f1997da --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2021 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.transport.resource; + +public enum ResourceType { + LWM2M_MODEL, JKS, PKCS_12 +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java index 3bc4d494f2..638f9f6fb2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java @@ -21,6 +21,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public @interface TbLwM2mTransportComponent { } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index d5f440d808..42f6267d42 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -201,6 +201,25 @@ message LwM2MResponseMsg { LwM2MRegistrationResponseMsg registrationMsg = 1; } +message ResourceMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + string resourceType = 3; + string resourceId = 4; + string value = 5; +} + +message GetResourcesRequestMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + string resourceType = 3; + string resourceId = 4; +} + +message GetResourcesResponseMsg { + repeated ResourceMsg resources = 1; +} + message ValidateDeviceLwM2MCredentialsRequestMsg { string credentialsId = 1; } @@ -242,6 +261,20 @@ message EntityDeleteMsg { int64 entityIdLSB = 3; } +message ResourceUpdateMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + string resourceType = 3; + string resourceId = 4; +} + +message ResourceDeleteMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + string resourceType = 3; + string resourceId = 4; +} + message SessionCloseNotificationProto { string message = 1; } @@ -525,6 +558,7 @@ message TransportApiRequestMsg { ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6; ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; + GetResourcesRequestMsg resourcesRequestMsg = 9; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -534,6 +568,7 @@ message TransportApiResponseMsg { GetEntityProfileResponseMsg entityProfileResponseMsg = 3; ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; LwM2MResponseMsg lwM2MResponseMsg = 6; + GetResourcesResponseMsg resourcesResponseMsg = 7; } /* Messages that are handled by ThingsBoard Core Service */ @@ -578,6 +613,8 @@ message ToTransportMsg { EntityDeleteMsg entityDeleteMsg = 9; ProvisionDeviceResponseMsg provisionResponse = 10; ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11; + ResourceUpdateMsg resourceUpdateMsg = 12; + ResourceDeleteMsg resourceDeleteMsg = 13; } message UsageStatsKVProto{ diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index e642e7f7b2..ab728af00d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -25,6 +25,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestM import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetResourcesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetResourcesResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.LwM2MRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.LwM2MResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; @@ -51,6 +53,8 @@ public interface TransportService { GetEntityProfileResponseMsg getEntityProfile(GetEntityProfileRequestMsg msg); + GetResourcesResponseMsg getResources(GetResourcesRequestMsg msg); + void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 473eeea42c..b273ee48cf 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -254,6 +254,18 @@ public class DefaultTransportService implements TransportService { } } + @Override + public TransportProtos.GetResourcesResponseMsg getResources(TransportProtos.GetResourcesRequestMsg msg) { + TbProtoQueueMsg protoMsg = + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setResourcesRequestMsg(msg).build()); + try { + TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); + return response.getValue().getResourcesResponseMsg(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + @Override public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { @@ -688,6 +700,10 @@ public class DefaultTransportService implements TransportService { } else if (EntityType.DEVICE.equals(entityType)) { rateLimitService.remove(new DeviceId(entityUuid)); } + } else if (toSessionMsg.hasResourceUpdateMsg()) { + //TODO: update resource cache + } else if (toSessionMsg.hasResourceDeleteMsg()) { + //TODO: remove resource from cache } else { //TODO: should we notify the device actor about missed session? log.debug("[{}] Missing session.", sessionId); @@ -695,6 +711,7 @@ public class DefaultTransportService implements TransportService { } } + public void onProfileUpdate(DeviceProfile deviceProfile) { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index fc6a2bed25..3f11a4ed00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -453,6 +453,15 @@ public class ModelConstants { public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec"; public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec"; + /** + * Resource constants. + */ + public static final String RESOURCE_TABLE_NAME = "resource"; + public static final String RESOURCE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; + public static final String RESOURCE_TYPE_COLUMN = "resource_type"; + public static final String RESOURCE_ID_COLUMN = "resource_id"; + public static final String RESOURCE_VALUE_COLUMN = "resource_value"; + /** * Cassandra attributes and timeseries constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceCompositeKey.java new file mode 100644 index 0000000000..828f1f4341 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceCompositeKey.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 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.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.transport.resource.Resource; + +import javax.persistence.Transient; +import java.io.Serializable; +import java.util.UUID; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class ResourceCompositeKey implements Serializable { + + @Transient + private static final long serialVersionUID = -3789469030818742769L; + + private UUID tenantId; + private String resourceType; + private String resourceId; + + public ResourceCompositeKey(Resource resource) { + this.tenantId = resource.getTenantId().getId(); + this.resourceType = resource.getResourceType().name(); + this.resourceId = resource.getResourceId(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceEntity.java new file mode 100644 index 0000000000..a877afa027 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ResourceEntity.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2021 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 org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; +import org.thingsboard.server.dao.model.ToData; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_VALUE_COLUMN; + +@Data +@Entity +@Table(name = RESOURCE_TABLE_NAME) +@IdClass(ResourceCompositeKey.class) +public class ResourceEntity implements ToData { + + @Id + @Column(name = RESOURCE_TENANT_ID_COLUMN, columnDefinition = "uuid") + private UUID tenantId; + + @Id + @Column(name = RESOURCE_TYPE_COLUMN) + private String resourceType; + + @Id + @Column(name = RESOURCE_ID_COLUMN) + private String resourceId; + + @Column(name = RESOURCE_VALUE_COLUMN) + private String value; + + public ResourceEntity() { + } + + public ResourceEntity(Resource resource) { + this.tenantId = resource.getTenantId().getId(); + this.resourceType = resource.getResourceType().name(); + this.resourceId = resource.getResourceId(); + this.value = resource.getValue(); + } + + @Override + public Resource toData() { + Resource resource = new Resource(); + resource.setTenantId(new TenantId(tenantId)); + resource.setResourceType(ResourceType.valueOf(resourceType)); + resource.setResourceId(resourceId); + resource.setValue(value); + return resource; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java new file mode 100644 index 0000000000..f5f23836b6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -0,0 +1,96 @@ +/** + * Copyright © 2016-2021 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.resource; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +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.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; +import org.thingsboard.server.dao.exception.DataValidationException; + +import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID; +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Service +@Slf4j +public class BaseResourceService implements ResourceService { + + private final ResourceDao resourceDao; + + public BaseResourceService(ResourceDao resourceDao) { + this.resourceDao = resourceDao; + } + + @Override + public Resource saveResource(Resource resource) { + log.trace("Executing saveResource [{}]", resource); + validate(resource); + return resourceDao.saveResource(resource); + } + + @Override + public Resource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) { + log.trace("Executing getResource [{}] [{}] [{}]", tenantId, resourceType, resourceId); + validate(tenantId, resourceType, resourceId); + return resourceDao.getResource(tenantId, resourceType, resourceId); + } + + @Override + public void deleteResource(TenantId tenantId, ResourceType resourceType, String resourceId) { + log.trace("Executing deleteResource [{}] [{}] [{}]", tenantId, resourceType, resourceId); + validate(tenantId, resourceType, resourceId); + resourceDao.deleteResource(tenantId, resourceType, resourceId); + } + + @Override + public PageData findResourcesByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return resourceDao.findAllByTenantId(tenantId, pageLink); + } + + @Override + public void deleteResourcesByTenantId(TenantId tenantId) { + log.trace("Executing deleteDevicesByTenantId, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + resourceDao.removeAllByTenantId(tenantId); + } + + protected void validate(Resource resource) { + if (resource == null) { + throw new DataValidationException("Resource should be specified!"); + } + + if (resource.getValue() == null) { + throw new DataValidationException("Resource value should be specified!"); + } + validate(resource.getTenantId(), resource.getResourceType(), resource.getResourceId()); + } + + protected void validate(TenantId tenantId, ResourceType resourceType, String resourceId) { + if (resourceType == null) { + throw new DataValidationException("Resource type should be specified!"); + } + if (resourceId == null) { + throw new DataValidationException("Resource id should be specified!"); + } + validateId(tenantId, "Incorrect tenantId "); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/ResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/ResourceDao.java new file mode 100644 index 0000000000..f8fae8b19d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/ResourceDao.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.resource; + +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.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; + +public interface ResourceDao { + + Resource saveResource(Resource resource); + + Resource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); + + void deleteResource(TenantId tenantId, ResourceType resourceType, String resourceId); + + PageData findAllByTenantId(TenantId tenantId, PageLink pageLink); + + void removeAllByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceDaoImpl.java new file mode 100644 index 0000000000..0f97d51f8a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceDaoImpl.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2021 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.resource; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +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.transport.resource.Resource; +import org.thingsboard.server.common.data.transport.resource.ResourceType; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.ResourceCompositeKey; +import org.thingsboard.server.dao.model.sql.ResourceEntity; +import org.thingsboard.server.dao.resource.ResourceDao; + +@Slf4j +@Component +public class ResourceDaoImpl implements ResourceDao { + + private final ResourceRepository resourceRepository; + + public ResourceDaoImpl(ResourceRepository resourceRepository) { + this.resourceRepository = resourceRepository; + } + + @Override + @Transactional + public Resource saveResource(Resource resource) { + return DaoUtil.getData(resourceRepository.save(new ResourceEntity(resource))); + } + + @Override + public Resource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) { + ResourceCompositeKey key = new ResourceCompositeKey(); + key.setTenantId(tenantId.getId()); + key.setResourceType(resourceType.name()); + key.setResourceId(resourceId); + + return DaoUtil.getData(resourceRepository.findById(key)); + } + + @Override + @Transactional + public void deleteResource(TenantId tenantId, ResourceType resourceType, String resourceId) { + ResourceCompositeKey key = new ResourceCompositeKey(); + key.setTenantId(tenantId.getId()); + key.setResourceType(resourceType.name()); + key.setResourceId(resourceId); + + resourceRepository.deleteById(key); + } + + @Override + public PageData findAllByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(resourceRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); + } + + @Override + public void removeAllByTenantId(TenantId tenantId) { + resourceRepository.removeAllByTenantId(tenantId.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceRepository.java new file mode 100644 index 0000000000..ba7472d7ec --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/ResourceRepository.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2021 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.resource; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.ResourceCompositeKey; +import org.thingsboard.server.dao.model.sql.ResourceEntity; + +import java.util.UUID; + +public interface ResourceRepository extends CrudRepository { + + Page findAllByTenantId(UUID tenantId, Pageable pageable); + + void removeAllByTenantId(UUID tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 12fdd3a57a..3016474f27 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -35,6 +35,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -88,6 +89,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private RuleChainService ruleChainService; + @Autowired + private ResourceService resourceService; + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); @@ -140,6 +144,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe userService.deleteTenantAdmins(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId); apiUsageStateService.deleteApiUsageStateByTenantId(tenantId); + resourceService.deleteResourcesByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index ee658f8da4..787f457c6a 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -420,3 +420,11 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( sms_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); + +CREATE TABLE IF NOT EXISTS resource ( + tenant_id uuid NOT NULL, + resource_type varchar(32) NOT NULL, + resource_id varchar(255) NOT NULL, + resource_value varchar, + CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id) +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 72e00ae85a..3bf5ce29c8 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -447,6 +447,14 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); +CREATE TABLE IF NOT EXISTS resource ( + tenant_id uuid NOT NULL, + resource_type varchar(32) NOT NULL, + resource_id varchar(255) NOT NULL, + resource_value varchar, + CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id) +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index b5629b0560..a548ceec30 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -28,4 +28,5 @@ DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS api_usage_state; +DROP TABLE IF EXISTS resource; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index 34985883d7..333ce03fba 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -28,4 +28,5 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS api_usage_state; \ No newline at end of file +DROP TABLE IF EXISTS api_usage_state; +DROP TABLE IF EXISTS resource;