From b083ab98e043321fa598de7f9188600c15e1cb80 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 17 May 2023 15:10:57 +0300 Subject: [PATCH 01/18] updated GET /resources api to filter resources by type --- .../controller/ControllerConstants.java | 3 + .../controller/TbResourceController.java | 18 ++- .../resource/DefaultTbResourceService.java | 10 ++ .../service/resource/TbResourceService.java | 4 + .../controller/TbResourceControllerTest.java | 119 ++++++++++++++++++ .../server/dao/resource/ResourceService.java | 4 + .../server/common/data/ResourceType.java | 2 +- .../dao/resource/BaseResourceService.java | 14 +++ .../dao/resource/TbResourceInfoDao.java | 4 + .../sql/resource/JpaTbResourceInfoDao.java | 21 ++++ .../resource/TbResourceInfoRepository.java | 25 ++++ 11 files changed, 221 insertions(+), 3 deletions(-) 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 f425bde86c..1cb794c2ec 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -140,6 +140,9 @@ public class ControllerConstants { protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title."; protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId"; + protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "LWM2M_MODEL, JKS, PKCS_12, JS_MODULE"; + protected static final String RESOURCE_TYPE = "A string value representing the resource type."; + protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. "; protected static final String LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name"; diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index d94dc31fa1..316599a092 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -31,6 +31,8 @@ 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.ResourceType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -57,6 +59,8 @@ import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_ID_ import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_INFO_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TEXT_SEARCH_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TYPE; +import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES; 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; @@ -153,6 +157,8 @@ public class TbResourceController extends BaseController { @RequestParam int pageSize, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, + @ApiParam(value = RESOURCE_TYPE, allowableValues = RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES) + @RequestParam(required = false) String resourceType, @ApiParam(value = RESOURCE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES) @@ -161,9 +167,17 @@ public class TbResourceController extends BaseController { @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink)); + if (StringUtils.isNotEmpty(resourceType)){ + return checkNotNull(resourceService.findTenantResourcesByType(getTenantId(), ResourceType.valueOf(resourceType), pageLink)); + } else { + return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink)); + } } else { - return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink)); + if (StringUtils.isNotEmpty(resourceType)){ + return checkNotNull(resourceService.findAllTenantResourcesByType(getTenantId(), ResourceType.valueOf(resourceType), pageLink)); + } else { + return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink)); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 5cf051367a..ece58397ab 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -85,6 +85,16 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements return resourceService.findTenantResourcesByTenantId(tenantId, pageLink); } + @Override + public PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + return resourceService.findAllTenantResourcesByType(tenantId, resourceType, pageLink); + } + + @Override + public PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + return resourceService.findTenantResourcesByType(tenantId, resourceType, pageLink); + } + @Override public List findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, String[] objectIds) { log.trace("Executing findByTenantId [{}]", tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 024d391482..c3b6506abe 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -39,6 +39,10 @@ public interface TbResourceService extends SimpleTbEntityService { PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + + PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + List findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, diff --git a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java index de8f717c0d..dbe0c591e9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java @@ -47,6 +47,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { private IdComparator idComparator = new IdComparator<>(); private static final String DEFAULT_FILE_NAME = "test.jks"; + private static final String DEFAULT_FILE_NAME_2 = "test2.jks"; private Tenant savedTenant; private User tenantAdmin; @@ -242,6 +243,54 @@ public class TbResourceControllerTest extends AbstractControllerTest { Assert.assertEquals(resources, loadedResources); } + @Test + public void testFindTenantTbResourcesByType() throws Exception { + Mockito.reset(tbClusterService, auditLogService); + + List resources = new ArrayList<>(); + int jksCntEntity = 17; + for (int i = 0; i < jksCntEntity; i++) { + TbResource resource = new TbResource(); + resource.setTitle("JKS Resource" + i); + resource.setResourceType(ResourceType.JKS); + resource.setFileName(i + DEFAULT_FILE_NAME); + resource.setData("Test Data"); + resources.add(new TbResourceInfo(save(resource))); + } + + int lwm2mCntEntity = 19; + for (int i = 0; i < lwm2mCntEntity; i++) { + TbResource resource = new TbResource(); + resource.setTitle("LWM2M Resource" + i); + resource.setResourceType(ResourceType.PKCS_12); + resource.setFileName(i + DEFAULT_FILE_NAME_2); + resource.setData("Test Data"); + save(resource); + } + + List loadedResources = new ArrayList<>(); + PageLink pageLink = new PageLink(5); + PageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/resource?resourceType=" + ResourceType.JKS.name() + "&", + new TypeReference<>() { + }, pageLink); + loadedResources.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new TbResource(), new TbResource(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, jksCntEntity + lwm2mCntEntity); + + Collections.sort(resources, idComparator); + Collections.sort(loadedResources, idComparator); + + Assert.assertEquals(resources, loadedResources); + } + @Test public void testFindSystemTbResources() throws Exception { loginSysAdmin(); @@ -300,6 +349,76 @@ public class TbResourceControllerTest extends AbstractControllerTest { Assert.assertTrue(loadedResources.isEmpty()); } + @Test + public void testFindSystemTbResourcesByType() throws Exception { + loginSysAdmin(); + + List resources = new ArrayList<>(); + int jksCntEntity = 17; + for (int i = 0; i < jksCntEntity; i++) { + TbResource resource = new TbResource(); + resource.setTitle("JKS Resource" + i); + resource.setResourceType(ResourceType.JKS); + resource.setFileName(i + DEFAULT_FILE_NAME); + resource.setData("Test Data"); + resources.add(new TbResourceInfo(save(resource))); + } + + int lwm2mCntEntity = 19; + for (int i = 0; i < lwm2mCntEntity; i++) { + TbResource resource = new TbResource(); + resource.setTitle("LWM2M Resource" + i); + resource.setResourceType(ResourceType.PKCS_12); + resource.setFileName(i + DEFAULT_FILE_NAME_2); + resource.setData("Test Data"); + save(resource); + } + + List loadedResources = new ArrayList<>(); + PageLink pageLink = new PageLink(30); + PageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/resource?resourceType=" + ResourceType.JKS + "&", + new TypeReference<>() { + }, pageLink); + loadedResources.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(resources, idComparator); + Collections.sort(loadedResources, idComparator); + + Assert.assertEquals(resources, loadedResources); + + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = resources.size(); + for (TbResourceInfo resource : resources) { + doDelete("/api/resource/" + resource.getId().getId().toString()) + .andExpect(status().isOk()); + } + + testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new TbResource(), new TbResource(), + resources.get(0).getTenantId(), null, null, SYS_ADMIN_EMAIL, + ActionType.DELETED, cntEntity, 1); + + pageLink = new PageLink(27); + loadedResources.clear(); + do { + pageData = doGetTypedWithPageLink("/api/resource?resourceType=" + ResourceType.JKS + "&", + new TypeReference<>() { + }, pageLink); + loadedResources.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Assert.assertTrue(loadedResources.isEmpty()); + } + @Test public void testFindSystemAndTenantTbResources() throws Exception { List systemResources = new ArrayList<>(); 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 index 5e05cdddc6..779fbaf275 100644 --- 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 @@ -43,6 +43,10 @@ public interface ResourceService extends EntityDaoService { PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + + PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds); PageData findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java index 31c87ecfde..a9c34a3903 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.data; public enum ResourceType { - LWM2M_MODEL, JKS, PKCS_12 + LWM2M_MODEL, JKS, PKCS_12, JS_MODULE } 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 index f5ba4abf2c..3bc02f0a5a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -116,6 +116,20 @@ public class BaseResourceService implements ResourceService { return resourceInfoDao.findTenantResourcesByTenantId(tenantId.getId(), pageLink); } + @Override + public PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + log.trace("Executing findAllTenantResourcesByType [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return resourceInfoDao.findAllTenantResourcesByType(tenantId.getId(), resourceType.name(), pageLink); + } + + @Override + public PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + log.trace("Executing findTenantResourcesByType [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return resourceInfoDao.findTenantResourcesByType(tenantId.getId(), resourceType.name(), pageLink); + } + @Override public List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType resourceType, String[] objectIds) { log.trace("Executing findTenantResourcesByResourceTypeAndObjectIds [{}][{}][{}]", tenantId, resourceType, objectIds); diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java index 6a726cd6ec..4b383556b7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java @@ -28,4 +28,8 @@ public interface TbResourceInfoDao extends Dao { PageData findTenantResourcesByTenantId(UUID tenantId, PageLink pageLink); + PageData findAllTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink); + + PageData findTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java index 837ab63fb3..041f774ab9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java @@ -68,4 +68,25 @@ public class JpaTbResourceInfoDao extends JpaAbstractSearchTextDao findAllTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink) { + return DaoUtil.toPageData(resourceInfoRepository + .findAllTenantResourcesByType( + tenantId, + TenantId.NULL_UUID, + resourceType, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } + + @Override + public PageData findTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink) { + return DaoUtil.toPageData(resourceInfoRepository + .findTenantResourcesByType( + tenantId, + resourceType, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java index dde66910df..b6a37f5578 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java @@ -46,4 +46,29 @@ public interface TbResourceInfoRepository extends JpaRepository findTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); + + @Query("SELECT tr FROM TbResourceInfoEntity tr WHERE " + + "LOWER(tr.title) LIKE LOWER(CONCAT('%', :searchText, '%'))" + + "AND (tr.tenantId = :tenantId " + + "OR (tr.tenantId = :systemAdminId " + + "AND NOT EXISTS " + + "(SELECT sr FROM TbResourceEntity sr " + + "WHERE sr.tenantId = :tenantId " + + "AND tr.resourceType = sr.resourceType " + + "AND tr.resourceKey = sr.resourceKey)))" + + "AND tr.resourceType = :resourceType") + Page findAllTenantResourcesByType(@Param("tenantId") UUID tenantId, + @Param("systemAdminId") UUID sysadminId, + @Param("resourceType") String resourceType, + @Param("searchText") String searchText, + Pageable pageable); + + @Query("SELECT ri FROM TbResourceInfoEntity ri WHERE " + + "ri.tenantId = :tenantId " + + "AND ri.resourceType = :resourceType " + + "AND LOWER(ri.title) LIKE LOWER(CONCAT('%', :searchText, '%'))") + Page findTenantResourcesByType(@Param("tenantId") UUID tenantId, + @Param("resourceType") String resourceType, + @Param("searchText") String searchText, + Pageable pageable); } From d9663676238703443f51f996c37422c745ee0299 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 17 May 2023 17:07:32 +0300 Subject: [PATCH 02/18] fixed tests --- .../controller/TbResourceControllerTest.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java index dbe0c591e9..fa4beab094 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java @@ -353,7 +353,8 @@ public class TbResourceControllerTest extends AbstractControllerTest { public void testFindSystemTbResourcesByType() throws Exception { loginSysAdmin(); - List resources = new ArrayList<>(); + List jksResources = new ArrayList<>(); + List lwm2mesources = new ArrayList<>(); int jksCntEntity = 17; for (int i = 0; i < jksCntEntity; i++) { TbResource resource = new TbResource(); @@ -361,7 +362,8 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); resource.setData("Test Data"); - resources.add(new TbResourceInfo(save(resource))); + TbResourceInfo saved = new TbResourceInfo(save(resource)); + jksResources.add(saved); } int lwm2mCntEntity = 19; @@ -371,7 +373,8 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.PKCS_12); resource.setFileName(i + DEFAULT_FILE_NAME_2); resource.setData("Test Data"); - save(resource); + TbResource saved = save(resource); + lwm2mesources.add(saved); } List loadedResources = new ArrayList<>(); @@ -387,21 +390,21 @@ public class TbResourceControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(resources, idComparator); + Collections.sort(jksResources, idComparator); Collections.sort(loadedResources, idComparator); - Assert.assertEquals(resources, loadedResources); + Assert.assertEquals(jksResources, loadedResources); Mockito.reset(tbClusterService, auditLogService); - int cntEntity = resources.size(); - for (TbResourceInfo resource : resources) { + int cntEntity = jksResources.size(); + for (TbResourceInfo resource : jksResources) { doDelete("/api/resource/" + resource.getId().getId().toString()) .andExpect(status().isOk()); } testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new TbResource(), new TbResource(), - resources.get(0).getTenantId(), null, null, SYS_ADMIN_EMAIL, + jksResources.get(0).getTenantId(), null, null, SYS_ADMIN_EMAIL, ActionType.DELETED, cntEntity, 1); pageLink = new PageLink(27); @@ -417,6 +420,13 @@ public class TbResourceControllerTest extends AbstractControllerTest { } while (pageData.hasNext()); Assert.assertTrue(loadedResources.isEmpty()); + + loginSysAdmin(); + + for (TbResourceInfo resource : lwm2mesources) { + doDelete("/api/resource/" + resource.getId().getId().toString()) + .andExpect(status().isOk()); + } } @Test From 49c230c81568cc8d19a412b74a06803d929bb2f4 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 18 May 2023 17:58:45 +0300 Subject: [PATCH 03/18] refactoring --- .../controller/TbResourceController.java | 12 ++----- .../resource/DefaultTbResourceService.java | 18 +++-------- .../service/resource/TbResourceService.java | 8 ++--- .../sql/BaseTbResourceServiceTest.java | 10 +++--- .../server/dao/resource/ResourceService.java | 8 ++--- .../dao/resource/BaseResourceService.java | 22 +++---------- .../dao/resource/TbResourceInfoDao.java | 8 ++--- .../sql/resource/JpaTbResourceInfoDao.java | 24 ++------------ .../resource/TbResourceInfoRepository.java | 32 ++++--------------- .../server/dao/service/TenantServiceTest.java | 2 +- 10 files changed, 31 insertions(+), 113 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 316599a092..6118794095 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -167,17 +167,9 @@ public class TbResourceController extends BaseController { @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - if (StringUtils.isNotEmpty(resourceType)){ - return checkNotNull(resourceService.findTenantResourcesByType(getTenantId(), ResourceType.valueOf(resourceType), pageLink)); - } else { - return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink)); - } + return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null, pageLink)); } else { - if (StringUtils.isNotEmpty(resourceType)){ - return checkNotNull(resourceService.findAllTenantResourcesByType(getTenantId(), ResourceType.valueOf(resourceType), pageLink)); - } else { - return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink)); - } + return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null, pageLink)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index ece58397ab..de01c9bf54 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -76,23 +76,13 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } @Override - public PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) { - return resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); + public PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + return resourceService.findAllTenantResourcesByTenantId(tenantId, resourceType, pageLink); } @Override - public PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) { - return resourceService.findTenantResourcesByTenantId(tenantId, pageLink); - } - - @Override - public PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { - return resourceService.findAllTenantResourcesByType(tenantId, resourceType, pageLink); - } - - @Override - public PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { - return resourceService.findTenantResourcesByType(tenantId, resourceType, pageLink); + public PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + return resourceService.findTenantResourcesByTenantId(tenantId, resourceType, pageLink); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index c3b6506abe..6e26d9ffe8 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -35,13 +35,9 @@ public interface TbResourceService extends SimpleTbEntityService { TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); - PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); - PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); - - PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); - - PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); List findLwM2mObject(TenantId tenantId, String sortOrder, diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index e2c5985553..364850e2fd 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -356,7 +356,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { PageLink pageLink = new PageLink(16); PageData pageData; do { - pageData = resourceService.findTenantResourcesByTenantId(tenantId, pageLink); + pageData = resourceService.findTenantResourcesByTenantId(tenantId, null, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -371,7 +371,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resourceService.deleteResourcesByTenantId(tenantId); pageLink = new PageLink(31); - pageData = resourceService.findTenantResourcesByTenantId(tenantId, pageLink); + pageData = resourceService.findTenantResourcesByTenantId(tenantId, null, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -417,7 +417,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { PageLink pageLink = new PageLink(10); PageData pageData; do { - pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); + pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, null, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -432,14 +432,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resourceService.deleteResourcesByTenantId(tenantId); pageLink = new PageLink(100); - pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); + pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, null, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(pageData.getData().size(), 100); resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID); pageLink = new PageLink(100); - pageData = resourceService.findAllTenantResourcesByTenantId(TenantId.SYS_TENANT_ID, pageLink); + pageData = resourceService.findAllTenantResourcesByTenantId(TenantId.SYS_TENANT_ID, null, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); 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 index 779fbaf275..2933c1a3c2 100644 --- 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 @@ -39,13 +39,9 @@ public interface ResourceService extends EntityDaoService { ListenableFuture findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId); - PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); - PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); - - PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); - - PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds); 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 index 3bc02f0a5a..1e14e11f74 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -103,31 +103,17 @@ public class BaseResourceService implements ResourceService { } @Override - public PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { log.trace("Executing findAllTenantResourcesByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findAllTenantResourcesByTenantId(tenantId.getId(), pageLink); + return resourceInfoDao.findAllTenantResourcesByTenantId(tenantId.getId(), resourceType == null ? null : resourceType.name(), pageLink); } @Override - public PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { log.trace("Executing findTenantResourcesByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findTenantResourcesByTenantId(tenantId.getId(), pageLink); - } - - @Override - public PageData findAllTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { - log.trace("Executing findAllTenantResourcesByType [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findAllTenantResourcesByType(tenantId.getId(), resourceType.name(), pageLink); - } - - @Override - public PageData findTenantResourcesByType(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { - log.trace("Executing findTenantResourcesByType [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findTenantResourcesByType(tenantId.getId(), resourceType.name(), pageLink); + return resourceInfoDao.findTenantResourcesByTenantId(tenantId.getId(), resourceType == null ? null : resourceType.name(), pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java index 4b383556b7..e916ff6b71 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java @@ -24,12 +24,8 @@ import java.util.UUID; public interface TbResourceInfoDao extends Dao { - PageData findAllTenantResourcesByTenantId(UUID tenantId, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink); - PageData findTenantResourcesByTenantId(UUID tenantId, PageLink pageLink); - - PageData findAllTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink); - - PageData findTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink); + PageData findTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java index 041f774ab9..16154976c2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java @@ -51,42 +51,24 @@ public class JpaTbResourceInfoDao extends JpaAbstractSearchTextDao findAllTenantResourcesByTenantId(UUID tenantId, PageLink pageLink) { + public PageData findAllTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink) { return DaoUtil.toPageData(resourceInfoRepository .findAllTenantResourcesByTenantId( tenantId, TenantId.NULL_UUID, + resourceType, Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } @Override - public PageData findTenantResourcesByTenantId(UUID tenantId, PageLink pageLink) { + public PageData findTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink) { return DaoUtil.toPageData(resourceInfoRepository .findTenantResourcesByTenantId( tenantId, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); - } - - @Override - public PageData findAllTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink) { - return DaoUtil.toPageData(resourceInfoRepository - .findAllTenantResourcesByType( - tenantId, - TenantId.NULL_UUID, resourceType, Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } - @Override - public PageData findTenantResourcesByType(UUID tenantId, String resourceType, PageLink pageLink) { - return DaoUtil.toPageData(resourceInfoRepository - .findTenantResourcesByType( - tenantId, - resourceType, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java index b6a37f5578..57adba882d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java @@ -34,41 +34,21 @@ public interface TbResourceInfoRepository extends JpaRepository findAllTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("systemAdminId") UUID sysadminId, + @Param("resourceType") String resourceType, @Param("searchText") String searchText, Pageable pageable); @Query("SELECT ri FROM TbResourceInfoEntity ri WHERE " + "ri.tenantId = :tenantId " + - "AND LOWER(ri.title) LIKE LOWER(CONCAT('%', :searchText, '%'))") + "AND LOWER(ri.title) LIKE LOWER(CONCAT('%', :searchText, '%'))" + + "AND (:resourceType is null or ri.resourceType = :resourceType)") Page findTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, + @Param("resourceType") String resourceType, @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT tr FROM TbResourceInfoEntity tr WHERE " + - "LOWER(tr.title) LIKE LOWER(CONCAT('%', :searchText, '%'))" + - "AND (tr.tenantId = :tenantId " + - "OR (tr.tenantId = :systemAdminId " + - "AND NOT EXISTS " + - "(SELECT sr FROM TbResourceEntity sr " + - "WHERE sr.tenantId = :tenantId " + - "AND tr.resourceType = sr.resourceType " + - "AND tr.resourceKey = sr.resourceKey)))" + - "AND tr.resourceType = :resourceType") - Page findAllTenantResourcesByType(@Param("tenantId") UUID tenantId, - @Param("systemAdminId") UUID sysadminId, - @Param("resourceType") String resourceType, - @Param("searchText") String searchText, - Pageable pageable); - - @Query("SELECT ri FROM TbResourceInfoEntity ri WHERE " + - "ri.tenantId = :tenantId " + - "AND ri.resourceType = :resourceType " + - "AND LOWER(ri.title) LIKE LOWER(CONCAT('%', :searchText, '%'))") - Page findTenantResourcesByType(@Param("tenantId") UUID tenantId, - @Param("resourceType") String resourceType, - @Param("searchText") String searchText, - Pageable pageable); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java index 2c7674c81b..74af3c0850 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java @@ -506,7 +506,7 @@ public class TenantServiceTest extends AbstractServiceTest { .as("resource").isNull(); PageLink pageLinkResources = new PageLink(1); PageData tenantResources = - resourceService.findAllTenantResourcesByTenantId(tenant.getId(), pageLinkResources); + resourceService.findAllTenantResourcesByTenantId(tenant.getId(), null, pageLinkResources); Assert.assertEquals(0, tenantResources.getTotalElements()); } From 6d78fe484c41fbedcf40037b57af49deba3684c9 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 18 May 2023 18:01:09 +0300 Subject: [PATCH 04/18] minor refactoring --- .../thingsboard/server/controller/TbResourceController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 6118794095..ab48214981 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -166,10 +166,11 @@ public class TbResourceController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + ResourceType resourceTypeValue = StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null; if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null, pageLink)); + return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), resourceTypeValue, pageLink)); } else { - return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null, pageLink)); + return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), resourceTypeValue, pageLink)); } } From 45e1a17be34355b8155335e45c7e1c91f34f931d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 18 May 2023 18:30:50 +0300 Subject: [PATCH 05/18] refactoring --- .../server/dao/sql/resource/TbResourceInfoRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java index 57adba882d..720b78a5f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java @@ -35,7 +35,7 @@ public interface TbResourceInfoRepository extends JpaRepository findAllTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("systemAdminId") UUID sysadminId, @Param("resourceType") String resourceType, @@ -44,8 +44,8 @@ public interface TbResourceInfoRepository extends JpaRepository findTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("resourceType") String resourceType, @Param("searchText") String searchText, From 7e7b5b17e71131dfd72d7857f8ad20ba637af186 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 19 May 2023 17:32:17 +0300 Subject: [PATCH 06/18] refactoring --- .../controller/TbResourceController.java | 10 +++++-- .../resource/DefaultTbResourceService.java | 9 +++--- .../service/resource/TbResourceService.java | 5 ++-- .../sql/BaseTbResourceServiceTest.java | 26 +++++++++++++---- .../server/dao/resource/ResourceService.java | 5 ++-- .../common/data/TbResourceInfoFilter.java | 29 +++++++++++++++++++ .../dao/resource/BaseResourceService.java | 11 ++++--- .../dao/resource/TbResourceInfoDao.java | 5 ++-- .../sql/resource/JpaTbResourceInfoDao.java | 16 ++++++---- .../server/dao/service/TenantServiceTest.java | 6 +++- 10 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index ab48214981..4196599557 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; @@ -166,11 +167,14 @@ public class TbResourceController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - ResourceType resourceTypeValue = StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null; + TbResourceInfoFilter.TbResourceInfoFilterBuilder filter = TbResourceInfoFilter.builder(); + filter.tenantId(getTenantId()); + filter.resourceType(StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null); + if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), resourceTypeValue, pageLink)); + return checkNotNull(resourceService.findTenantResourcesByTenantId(filter.build(), pageLink)); } else { - return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), resourceTypeValue, pageLink)); + return checkNotNull(resourceService.findAllTenantResourcesByTenantId(filter.build(), pageLink)); } } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index de01c9bf54..41ed04ac8f 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -76,13 +77,13 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } @Override - public PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { - return resourceService.findAllTenantResourcesByTenantId(tenantId, resourceType, pageLink); + public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { + return resourceService.findAllTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); } @Override - public PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { - return resourceService.findTenantResourcesByTenantId(tenantId, resourceType, pageLink); + public PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { + return resourceService.findTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 6e26d9ffe8..d80af9b513 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.resource; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; @@ -35,9 +36,9 @@ public interface TbResourceService extends SimpleTbEntityService { TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); - PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); - PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); List findLwM2mObject(TenantId tenantId, String sortOrder, diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index 364850e2fd..300ef0e4c5 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; @@ -356,7 +357,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { PageLink pageLink = new PageLink(16); PageData pageData; do { - pageData = resourceService.findTenantResourcesByTenantId(tenantId, null, pageLink); + TbResourceInfoFilter filter = TbResourceInfoFilter.builder() + .tenantId(tenantId) + .build(); + pageData = resourceService.findTenantResourcesByTenantId(filter, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -371,7 +375,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resourceService.deleteResourcesByTenantId(tenantId); pageLink = new PageLink(31); - pageData = resourceService.findTenantResourcesByTenantId(tenantId, null, pageLink); + TbResourceInfoFilter filter = TbResourceInfoFilter.builder() + .tenantId(tenantId) + .build(); + pageData = resourceService.findTenantResourcesByTenantId(filter, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -417,7 +424,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { PageLink pageLink = new PageLink(10); PageData pageData; do { - pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, null, pageLink); + TbResourceInfoFilter filter = TbResourceInfoFilter.builder() + .tenantId(tenantId) + .build(); + pageData = resourceService.findAllTenantResourcesByTenantId(filter, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -432,14 +442,20 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resourceService.deleteResourcesByTenantId(tenantId); pageLink = new PageLink(100); - pageData = resourceService.findAllTenantResourcesByTenantId(tenantId, null, pageLink); + TbResourceInfoFilter filter = TbResourceInfoFilter.builder() + .tenantId(tenantId) + .build(); + pageData = resourceService.findAllTenantResourcesByTenantId(filter, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(pageData.getData().size(), 100); resourceService.deleteResourcesByTenantId(TenantId.SYS_TENANT_ID); pageLink = new PageLink(100); - pageData = resourceService.findAllTenantResourcesByTenantId(TenantId.SYS_TENANT_ID, null, pageLink); + filter = TbResourceInfoFilter.builder() + .tenantId(TenantId.SYS_TENANT_ID) + .build(); + pageData = resourceService.findAllTenantResourcesByTenantId(filter, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); 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 index 2933c1a3c2..f561662a5e 100644 --- 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 @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -39,9 +40,9 @@ public interface ResourceService extends EntityDaoService { ListenableFuture findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId); - PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); - PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink); + PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java new file mode 100644 index 0000000000..9057fc144b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2023 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; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@Builder +public class TbResourceInfoFilter { + + private TenantId tenantId; + private ResourceType resourceType; + +} 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 index 1e14e11f74..2bab06da6f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TbResourceId; @@ -103,17 +104,19 @@ public class BaseResourceService implements ResourceService { } @Override - public PageData findAllTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { + TenantId tenantId = tbResourceInfoFilter.getTenantId(); log.trace("Executing findAllTenantResourcesByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findAllTenantResourcesByTenantId(tenantId.getId(), resourceType == null ? null : resourceType.name(), pageLink); + return resourceInfoDao.findAllTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); } @Override - public PageData findTenantResourcesByTenantId(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + public PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { + TenantId tenantId = tbResourceInfoFilter.getTenantId(); log.trace("Executing findTenantResourcesByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findTenantResourcesByTenantId(tenantId.getId(), resourceType == null ? null : resourceType.name(), pageLink); + return resourceInfoDao.findTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java index e916ff6b71..6f6163b9c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.resource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; @@ -24,8 +25,8 @@ import java.util.UUID; public interface TbResourceInfoDao extends Dao { - PageData findAllTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); - PageData findTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink); + PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java index 16154976c2..988b00f043 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java @@ -19,7 +19,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -51,22 +53,24 @@ public class JpaTbResourceInfoDao extends JpaAbstractSearchTextDao findAllTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink) { + public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) { + ResourceType resourceType = filter.getResourceType(); return DaoUtil.toPageData(resourceInfoRepository .findAllTenantResourcesByTenantId( - tenantId, + filter.getTenantId().getId(), TenantId.NULL_UUID, - resourceType, + resourceType == null ? null : resourceType.name(), Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } @Override - public PageData findTenantResourcesByTenantId(UUID tenantId, String resourceType, PageLink pageLink) { + public PageData findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) { + ResourceType resourceType = filter.getResourceType(); return DaoUtil.toPageData(resourceInfoRepository .findTenantResourcesByTenantId( - tenantId, - resourceType, + filter.getTenantId().getId(), + resourceType == null ? null : resourceType.name(), Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java index 74af3c0850..5872a5846b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceTest.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.TbResourceInfoFilter; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; @@ -505,8 +506,11 @@ public class TenantServiceTest extends AbstractServiceTest { assertThat(resourceService.findResourceById(tenant.getId(), resource.getId())) .as("resource").isNull(); PageLink pageLinkResources = new PageLink(1); + TbResourceInfoFilter filter = TbResourceInfoFilter.builder() + .tenantId(tenantId) + .build(); PageData tenantResources = - resourceService.findAllTenantResourcesByTenantId(tenant.getId(), null, pageLinkResources); + resourceService.findAllTenantResourcesByTenantId(filter, pageLinkResources); Assert.assertEquals(0, tenantResources.getTotalElements()); } From b41fa03d24fcdfc5e807a7d048d4f738f4510375 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 19 May 2023 19:23:21 +0300 Subject: [PATCH 07/18] UI: Added new resource type JS module --- .../resources-library-table-config.resolve.ts | 8 ++-- .../resource/resources-library.component.html | 6 +-- .../resource/resources-library.component.ts | 43 +++++++++++-------- .../src/app/shared/models/resource.models.ts | 16 ++++--- .../assets/locale/locale.constant-en_US.json | 8 +++- 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index a488bd3807..b3126c781b 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -61,11 +61,9 @@ export class ResourcesLibraryTableConfigResolver implements Resolve('createdTime', 'common.created-time', this.datePipe, '150px'), new EntityTableColumn('title', 'resource.title', '60%'), new EntityTableColumn('resourceType', 'resource.resource-type', '40%', - entity => this.resourceTypesTranslationMap.get(entity.resourceType)), + entity => this.translate.instant(this.resourceTypesTranslationMap.get(entity.resourceType))), new EntityTableColumn('tenantId', 'resource.system', '60px', - entity => { - return checkBoxCell(entity.tenantId.id === NULL_UUID); - }), + entity => checkBoxCell(entity.tenantId.id === NULL_UUID)), ); this.config.cellActionDescriptors.push( @@ -124,7 +122,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve {}); } downloadResource($event: Event, resource: ResourceInfo) { diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html index c4208d1fcd..f955d26f0d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html @@ -52,7 +52,7 @@ resource.resource-type - {{ resourceTypesTranslationMap.get(resourceType) }} + {{ resourceTypesTranslationMap.get(resourceType) | translate }} @@ -66,7 +66,7 @@ {{ 'resource.title-max-length' | translate }} - -
+
resource.file-name diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts index 078f87b5ef..90f7847e10 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts @@ -20,7 +20,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { EntityComponent } from '@home/components/entity/entity.component'; import { Resource, @@ -29,8 +29,9 @@ import { ResourceTypeMIMETypes, ResourceTypeTranslationMap } from '@shared/models/resource.models'; -import {filter, pairwise, startWith, takeUntil} from 'rxjs/operators'; +import { filter, startWith, takeUntil } from 'rxjs/operators'; import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-resources-library', @@ -39,7 +40,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions' export class ResourcesLibraryComponent extends EntityComponent implements OnInit, OnDestroy { readonly resourceType = ResourceType; - readonly resourceTypes = Object.values(this.resourceType); + readonly resourceTypes: ResourceType[] = Object.values(this.resourceType); readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; private destroy$ = new Subject(); @@ -48,7 +49,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme protected translate: TranslateService, @Inject('entity') protected entityValue: Resource, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: UntypedFormBuilder, + public fb: FormBuilder, protected cd: ChangeDetectorRef) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } @@ -87,33 +88,39 @@ export class ResourcesLibraryComponent extends EntityComponent impleme } } - buildForm(entity: Resource): UntypedFormGroup { - const form = this.fb.group( - { - title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], - resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, [Validators.required]], - fileName: [entity ? entity.fileName : null, [Validators.required]], - } - ); - if (this.isAdd) { - form.addControl('data', this.fb.control(null, Validators.required)); - } - return form; + buildForm(entity: Resource): FormGroup { + return this.fb.group({ + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], + resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, Validators.required], + fileName: [entity ? entity.fileName : null, Validators.required], + data: [entity ? entity.data : null, Validators.required] + }); } updateForm(entity: Resource) { this.entity.name = entity.title; if (this.isEdit) { this.entityForm.get('resourceType').disable({emitEvent: false}); - this.entityForm.get('fileName').disable({emitEvent: false}); + if (entity.resourceType !== ResourceType.JS_MODULE) { + this.entityForm.get('fileName').disable({emitEvent: false}); + this.entityForm.get('data').disable({emitEvent: false}); + } } this.entityForm.patchValue({ resourceType: entity.resourceType, fileName: entity.fileName, - title: entity.title + title: entity.title, + data: entity.data }); } + prepareFormValue(formValue: Resource): Resource { + if (this.isEdit && !isDefinedAndNotNull(formValue.data)) { + delete formValue.data; + } + return super.prepareFormValue(formValue); + } + getAllowedExtensions() { try { return ResourceTypeExtension.get(this.entityForm.get('resourceType').value); diff --git a/ui-ngx/src/app/shared/models/resource.models.ts b/ui-ngx/src/app/shared/models/resource.models.ts index 871bf147ea..8d7121c5e5 100644 --- a/ui-ngx/src/app/shared/models/resource.models.ts +++ b/ui-ngx/src/app/shared/models/resource.models.ts @@ -21,14 +21,16 @@ import { TbResourceId } from '@shared/models/id/tb-resource-id'; export enum ResourceType { LWM2M_MODEL = 'LWM2M_MODEL', PKCS_12 = 'PKCS_12', - JKS = 'JKS' + JKS = 'JKS', + JS_MODULE = 'JS_MODULE' } export const ResourceTypeMIMETypes = new Map( [ [ResourceType.LWM2M_MODEL, 'application/xml,text/xml'], [ResourceType.PKCS_12, 'application/x-pkcs12'], - [ResourceType.JKS, 'application/x-java-keystore'] + [ResourceType.JKS, 'application/x-java-keystore'], + [ResourceType.JS_MODULE, 'text/javascript,application/javascript'] ] ); @@ -36,15 +38,17 @@ export const ResourceTypeExtension = new Map( [ [ResourceType.LWM2M_MODEL, 'xml'], [ResourceType.PKCS_12, 'p12,pfx'], - [ResourceType.JKS, 'jks'] + [ResourceType.JKS, 'jks'], + [ResourceType.JS_MODULE, 'js'] ] ); export const ResourceTypeTranslationMap = new Map( [ - [ResourceType.LWM2M_MODEL, 'LWM2M model'], - [ResourceType.PKCS_12, 'PKCS #12'], - [ResourceType.JKS, 'JKS'] + [ResourceType.LWM2M_MODEL, 'resource.type.lwm2m-model'], + [ResourceType.PKCS_12, 'resource.type.pkcs-12'], + [ResourceType.JKS, 'resource.type.jks'], + [ResourceType.JS_MODULE, 'resource.type.js-module'] ] ); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ee081d9727..533afa474b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3259,7 +3259,13 @@ "system": "System", "title": "Title", "title-required": "Title is required.", - "title-max-length": "Title should be less than 256" + "title-max-length": "Title should be less than 256", + "type": { + "jks": "JKS", + "js-module": "JS module", + "lwm2m-model": "LWM2M model", + "pkcs-12": "PKCS #12" + } }, "rulechain": { "rulechain": "Rule chain", From f489e934b154e49fdd28dbf3ca95b4662eb22ac8 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 23 May 2023 15:02:33 +0300 Subject: [PATCH 08/18] renamed variables --- .../server/controller/TbResourceController.java | 5 +++-- .../service/resource/DefaultTbResourceService.java | 8 ++++---- .../server/service/resource/TbResourceService.java | 4 ++-- .../server/dao/resource/ResourceService.java | 4 ++-- .../server/dao/resource/BaseResourceService.java | 12 ++++++------ .../server/dao/resource/TbResourceInfoDao.java | 4 ++-- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 4196599557..ea151bd0c5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -169,8 +169,9 @@ public class TbResourceController extends BaseController { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); TbResourceInfoFilter.TbResourceInfoFilterBuilder filter = TbResourceInfoFilter.builder(); filter.tenantId(getTenantId()); - filter.resourceType(StringUtils.isNotEmpty(resourceType) ? ResourceType.valueOf(resourceType) : null); - + if (StringUtils.isNotEmpty(resourceType)){ + filter.resourceType(ResourceType.valueOf(resourceType)); + } if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { return checkNotNull(resourceService.findTenantResourcesByTenantId(filter.build(), pageLink)); } else { diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 41ed04ac8f..9c41a427ab 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -77,13 +77,13 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } @Override - public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { - return resourceService.findAllTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); + public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) { + return resourceService.findAllTenantResourcesByTenantId(filter, pageLink); } @Override - public PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { - return resourceService.findTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); + public PageData findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) { + return resourceService.findTenantResourcesByTenantId(filter, pageLink); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index d80af9b513..40fe7dda9a 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -36,9 +36,9 @@ public interface TbResourceService extends SimpleTbEntityService { TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); - PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink); - PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); + PageData findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink); List findLwM2mObject(TenantId tenantId, String sortOrder, 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 index f561662a5e..6f0e362209 100644 --- 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 @@ -40,9 +40,9 @@ public interface ResourceService extends EntityDaoService { ListenableFuture findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId); - PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink); - PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); + PageData findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink); List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds); 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 index 2bab06da6f..974e3665f1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -104,19 +104,19 @@ public class BaseResourceService implements ResourceService { } @Override - public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { - TenantId tenantId = tbResourceInfoFilter.getTenantId(); + public PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) { + TenantId tenantId = filter.getTenantId(); log.trace("Executing findAllTenantResourcesByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findAllTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); + return resourceInfoDao.findAllTenantResourcesByTenantId(filter, pageLink); } @Override - public PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink) { - TenantId tenantId = tbResourceInfoFilter.getTenantId(); + public PageData findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) { + TenantId tenantId = filter.getTenantId(); log.trace("Executing findTenantResourcesByTenantId [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return resourceInfoDao.findTenantResourcesByTenantId(tbResourceInfoFilter, pageLink); + return resourceInfoDao.findTenantResourcesByTenantId(filter, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java index 6f6163b9c0..f0f0b6fdad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java @@ -25,8 +25,8 @@ import java.util.UUID; public interface TbResourceInfoDao extends Dao { - PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); + PageData findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink); - PageData findTenantResourcesByTenantId(TbResourceInfoFilter tbResourceInfoFilter, PageLink pageLink); + PageData findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink); } From 051c7c384d338f57106038dcf892e40d4fc50c53 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 6 Jun 2023 11:52:08 +0300 Subject: [PATCH 09/18] added etag header for resources/{id}/download endpoint --- .../controller/TbResourceController.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index ea151bd0c5..d9c846e4bd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.controller; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -47,6 +50,7 @@ import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import javax.servlet.http.HttpServletRequest; import java.util.Base64; import java.util.List; @@ -85,17 +89,42 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { + @PathVariable(RESOURCE_ID) String strResourceId, HttpServletRequest request) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); TbResource tbResource = checkResourceId(resourceId, Operation.READ); ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); + + HashCode hashCode = Hashing.sha256().hashBytes(resource.getByteArray()); + String ifNoneMatch = request.getHeader("If-None-Match"); + if (ifNoneMatch != null) { + if (ifNoneMatch.equals(hashCode.toString())) { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED) + .eTag(hashCode.toString()).build(); + } + } + + String mediaType; + switch (tbResource.getResourceType()) { + case LWM2M_MODEL: + mediaType = "application/xml"; + break; + case JKS: + mediaType = "application/x-java-keystore"; + break; + case PKCS_12: + mediaType = "application/x-pkcs12"; + break; + default: mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE; + } + return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) .header("x-filename", tbResource.getFileName()) .contentLength(resource.contentLength()) - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header("Content-Type", mediaType) + .eTag(hashCode.toString()) .body(resource); } From f356a94b817027c09a93ae7696aaf13bddf5dba5 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Jun 2023 00:06:55 +0300 Subject: [PATCH 10/18] added new hash_code column to resource table --- .../main/data/upgrade/3.5.1/schema_update.sql | 7 ++ .../controller/TbResourceController.java | 87 +++++++++++++------ .../resource/DefaultTbResourceService.java | 5 ++ .../server/common/data/ResourceType.java | 13 ++- .../server/common/data/TbResource.java | 2 + .../server/common/data/TbResourceInfo.java | 5 ++ .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/TbResourceEntity.java | 6 ++ 8 files changed, 97 insertions(+), 29 deletions(-) diff --git a/application/src/main/data/upgrade/3.5.1/schema_update.sql b/application/src/main/data/upgrade/3.5.1/schema_update.sql index 3bc2c99168..9000acc69e 100644 --- a/application/src/main/data/upgrade/3.5.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.5.1/schema_update.sql @@ -52,3 +52,10 @@ $$ $$; -- NOTIFICATION CONFIGS VERSION CONTROL END + +ALTER TABLE resource + ADD COLUMN IF NOT EXISTS hash_code varchar; + +UPDATE resource + SET hash_code = encode(sha256(decode(resource.data, 'base64')),'hex') WHERE resource.data is not null; + diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index d9c846e4bd..59d07c4781 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -22,6 +22,7 @@ import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -29,6 +30,7 @@ import org.springframework.http.ResponseEntity; 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.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -47,10 +49,10 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.resource.TbResourceService; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import javax.servlet.http.HttpServletRequest; import java.util.Base64; import java.util.List; @@ -89,45 +91,47 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, HttpServletRequest request) throws ThingsboardException { + @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); TbResource tbResource = checkResourceId(resourceId, Operation.READ); ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); - - HashCode hashCode = Hashing.sha256().hashBytes(resource.getByteArray()); - String ifNoneMatch = request.getHeader("If-None-Match"); - if (ifNoneMatch != null) { - if (ifNoneMatch.equals(hashCode.toString())) { - return ResponseEntity.status(HttpStatus.NOT_MODIFIED) - .eTag(hashCode.toString()).build(); - } - } - - String mediaType; - switch (tbResource.getResourceType()) { - case LWM2M_MODEL: - mediaType = "application/xml"; - break; - case JKS: - mediaType = "application/x-java-keystore"; - break; - case PKCS_12: - mediaType = "application/x-pkcs12"; - break; - default: mediaType = MediaType.APPLICATION_OCTET_STREAM_VALUE; - } - return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) .header("x-filename", tbResource.getFileName()) .contentLength(resource.contentLength()) - .header("Content-Type", mediaType) - .eTag(hashCode.toString()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(resource); } + @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource/lwm2m/{resourceId}/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadLwm2mResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.LWM2M_MODEL, strResourceId, headers); + } + + @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource/pkcs12/{resourceId}/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadPkcs12ResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + @PathVariable(RESOURCE_ID) String strResourceId, HttpHeaders headers) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.PKCS_12, strResourceId, headers); + } + + @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource/js/{resourceId}/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadJsResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + @PathVariable(RESOURCE_ID) String strResourceId, HttpHeaders headers) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.JS_MODULE, strResourceId, headers); + } + @ApiOperation(value = "Get Resource Info (getResourceInfoById)", notes = "Fetch the Resource Info object based on the provided Resource Id. " + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, @@ -257,4 +261,31 @@ public class TbResourceController extends BaseController { TbResource tbResource = checkResourceId(resourceId, Operation.DELETE); tbResourceService.delete(tbResource, getCurrentUser()); } + + private ResponseEntity downloadResourceIfChanged(ResourceType type, String strResourceId, HttpHeaders headers) throws ThingsboardException { + checkParameter(RESOURCE_ID, strResourceId); + TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); + TbResourceInfo tbResourceInfo = checkResourceInfoId(resourceId, Operation.READ); + + List ifNoneMatchHeaders = headers.getIfNoneMatch(); + if (!ifNoneMatchHeaders.isEmpty()) { + if (ifNoneMatchHeaders.contains(tbResourceInfo.getHashCode())) { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED) + .eTag(tbResourceInfo.getHashCode()).build(); + } + } + + SecurityUser currentUser = getCurrentUser(); + TbResource tbResource = resourceService.findResourceById(currentUser.getTenantId(), resourceId); + ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) + .header("x-filename", tbResource.getFileName()) + .contentLength(resource.contentLength()) + .header("Content-Type", type.mediaType) + .cacheControl(CacheControl.noCache()) + .eTag(tbResource.getHashCode()) + .body(resource); + } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index e8b90c02d7..97eac85c48 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.resource; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; @@ -36,6 +38,7 @@ import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import java.util.Base64; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -166,6 +169,8 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } else { resource.setResourceKey(resource.getFileName()); } + HashCode hashCode = Hashing.sha256().hashBytes(Base64.getDecoder().decode(resource.getData().getBytes())); + resource.setHashCode(hashCode.toString()); return resourceService.saveResource(resource); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java index a9c34a3903..bef4681c05 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java @@ -16,5 +16,16 @@ package org.thingsboard.server.common.data; public enum ResourceType { - LWM2M_MODEL, JKS, PKCS_12, JS_MODULE + LWM2M_MODEL("lwm2m", "application/xml"), + JKS("jks", "application/x-java-keystore"), + PKCS_12("pkcs12", "application/x-pkcs12"), + JS_MODULE("js", "application/javascript"); + + public String type; + public String mediaType; + + ResourceType(String type, String mediaType) { + this.type = type; + this.mediaType = mediaType; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java index 91bb7ba383..1d6f7ca21d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java @@ -74,6 +74,8 @@ public class TbResource extends TbResourceInfo { builder.append(fileName); builder.append(", data="); builder.append(data); + builder.append(", hashCode="); + builder.append(getHashCode()); builder.append("]"); return builder.toString(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index 331958ac58..671cd65924 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -48,6 +48,8 @@ public class TbResourceInfo extends BaseData implements HasName, H private String resourceKey; @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String searchText; + @ApiModelProperty(position = 8, value = "Resource hash code.", example = "33a64df551425fcc55e4d42a148795d9f25f89d4", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private String hashCode; public TbResourceInfo() { super(); @@ -64,6 +66,7 @@ public class TbResourceInfo extends BaseData implements HasName, H this.resourceType = resourceInfo.getResourceType(); this.resourceKey = resourceInfo.getResourceKey(); this.searchText = resourceInfo.getSearchText(); + this.hashCode = resourceInfo.getHashCode(); } @ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " + @@ -107,6 +110,8 @@ public class TbResourceInfo extends BaseData implements HasName, H builder.append(resourceType); builder.append(", resourceKey="); builder.append(resourceKey); + builder.append(", hashCode="); + builder.append(hashCode); builder.append("]"); return builder.toString(); } 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 17606e1a1f..68123b22c4 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 @@ -480,6 +480,7 @@ public class ModelConstants { public static final String RESOURCE_TITLE_COLUMN = TITLE_PROPERTY; public static final String RESOURCE_FILE_NAME_COLUMN = "file_name"; public static final String RESOURCE_DATA_COLUMN = "data"; + public static final String RESOURCE_HASH_CODE_COLUMN = "hash_code"; /** * Ota Package constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java index 1540abfe6b..bfd26a6290 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java @@ -31,6 +31,7 @@ import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DATA_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_FILE_NAME_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_HASH_CODE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN; @@ -65,6 +66,9 @@ public class TbResourceEntity extends BaseSqlEntity implements BaseE @Column(name = RESOURCE_DATA_COLUMN) private String data; + @Column(name = RESOURCE_HASH_CODE_COLUMN) + private String hashCode; + public TbResourceEntity() { } @@ -82,6 +86,7 @@ public class TbResourceEntity extends BaseSqlEntity implements BaseE this.searchText = resource.getSearchText(); this.fileName = resource.getFileName(); this.data = resource.getData(); + this.hashCode = resource.getHashCode(); } @Override @@ -95,6 +100,7 @@ public class TbResourceEntity extends BaseSqlEntity implements BaseE resource.setSearchText(searchText); resource.setFileName(fileName); resource.setData(data); + resource.setHashCode(hashCode); return resource; } From 29384170d79b988dcefef38938f4fc954a28590c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Jun 2023 13:16:55 +0300 Subject: [PATCH 11/18] added tests --- .../controller/ControllerConstants.java | 2 +- .../controller/TbResourceController.java | 8 +- .../server/controller/AbstractWebTest.java | 7 ++ .../controller/TbResourceControllerTest.java | 112 ++++++++++++++++-- .../server/common/data/ResourceType.java | 21 +++- .../dao/model/sql/TbResourceInfoEntity.java | 6 + .../main/resources/sql/schema-entities.sql | 1 + 7 files changed, 137 insertions(+), 20 deletions(-) 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 1cb794c2ec..a8cc2c1043 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -140,7 +140,7 @@ public class ControllerConstants { protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title."; protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId"; - protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "LWM2M_MODEL, JKS, PKCS_12, JS_MODULE"; + protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "lwm2m, jks, pkcs12, js"; protected static final String RESOURCE_TYPE = "A string value representing the resource type."; protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. "; diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 59d07c4781..369dc2e2bd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -119,7 +119,7 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/pkcs12/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadPkcs12ResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, HttpHeaders headers) throws ThingsboardException { + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { return downloadResourceIfChanged(ResourceType.PKCS_12, strResourceId, headers); } @@ -128,7 +128,7 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/js/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadJsResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, HttpHeaders headers) throws ThingsboardException { + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { return downloadResourceIfChanged(ResourceType.JS_MODULE, strResourceId, headers); } @@ -203,7 +203,7 @@ public class TbResourceController extends BaseController { TbResourceInfoFilter.TbResourceInfoFilterBuilder filter = TbResourceInfoFilter.builder(); filter.tenantId(getTenantId()); if (StringUtils.isNotEmpty(resourceType)){ - filter.resourceType(ResourceType.valueOf(resourceType)); + filter.resourceType(ResourceType.getResourceByType(resourceType)); } if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { return checkNotNull(resourceService.findTenantResourcesByTenantId(filter.build(), pageLink)); @@ -283,7 +283,7 @@ public class TbResourceController extends BaseController { .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) .header("x-filename", tbResource.getFileName()) .contentLength(resource.contentLength()) - .header("Content-Type", type.mediaType) + .header("Content-Type", type.getMediaType()) .cacheControl(CacheControl.noCache()) .eTag(tbResource.getHashCode()) .body(resource); 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 724e975792..67598b0e83 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -601,6 +601,13 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return mockMvc.perform(getRequest); } + protected ResultActions doGet(String urlTemplate, HttpHeaders httpHeaders, Object... urlVariables) throws Exception { + MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); + getRequest.headers(httpHeaders); + setJwtToken(getRequest); + return mockMvc.perform(getRequest); + } + protected T doGet(String urlTemplate, Class responseClass, Object... urlVariables) throws Exception { return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java index fa4beab094..111a08d571 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java @@ -16,11 +16,18 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.ResultActions; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; @@ -35,6 +42,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.List; @@ -48,6 +56,9 @@ public class TbResourceControllerTest extends AbstractControllerTest { private static final String DEFAULT_FILE_NAME = "test.jks"; private static final String DEFAULT_FILE_NAME_2 = "test2.jks"; + private static final String JS_TEST_FILE_NAME = "test.js"; + private static final String TEST_DATA = "77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQpGSUxFIElORk9STUFUSU9OCgpPTUEgUGVybWFuZW50IERvY3VtZW50CiAgIEZpbGU6IE9NQS1TVVAtTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci1WMV8wXzEtMjAxOTAyMjEtQQogICBUeXBlOiB4bWwKClB1YmxpYyBSZWFjaGFibGUgSW5mb3JtYXRpb24KICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMKICAgTmFtZTogTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci12MV8wXzEueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogIE9NQS1UUy1MV00yTV9CaW5hcnlBcHBEYXRhQ29udGFpbmVyLVYxXzBfMQoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvCgogIFNlbmQgY29tbWVudHMgdG8gaHR0cHM6Ly9naXRodWIuY29tL09wZW5Nb2JpbGVBbGxpYW5jZS9PTUFfTHdNMk1fZm9yX0RldmVsb3BlcnMvaXNzdWVzCgpDSEFOR0UgSElTVE9SWQoKMTUwNjIwMTggU3RhdHVzIGNoYW5nZWQgdG8gQXBwcm92ZWQgYnkgRE0sIERvYyBSZWYgIyBPTUEtRE0mU0UtMjAxOC0wMDYxLUlOUF9MV00yTV9BUFBEQVRBX1YxXzBfRVJQX2Zvcl9maW5hbF9BcHByb3ZhbAoyMTAyMjAxOSBTdGF0dXMgY2hhbmdlZCB0byBBcHByb3ZlZCBieSBJUFNPLCBEb2MgUmVmICMgT01BLUlQU08tMjAxOS0wMDI1LUlOUF9Md00yTV9PYmplY3RfQXBwX0RhdGFfQ29udGFpbmVyXzFfMF8xX2Zvcl9GaW5hbF9BcHByb3ZhbAoKTEVHQUwgRElTQ0xBSU1FUgoKQ29weXJpZ2h0IDIwMTkgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zCmFyZSBtZXQ6CgoxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodApub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCjIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUKZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUwpGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULApJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLApCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIKQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUCkxJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCkFOWSBXQVkgT1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRQpQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KClRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiBQbGVhc2UKcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKaHR0cHM6Ly93d3cub21hc3BlY3dvcmtzLm9yZy9hYm91dC9pbnRlbGxlY3R1YWwtcHJvcGVydHktcmlnaHRzLwoKLS0+CjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL29wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS54c2QiPgoJPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgoJCTxOYW1lPkJpbmFyeUFwcERhdGFDb250YWluZXI8L05hbWU+CgkJPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdHMgcHJvdmlkZXMgdGhlIGFwcGxpY2F0aW9uIHNlcnZpY2UgZGF0YSByZWxhdGVkIHRvIGEgTHdNMk0gU2VydmVyLCBlZy4gV2F0ZXIgbWV0ZXIgZGF0YS4gClRoZXJlIGFyZSBzZXZlcmFsIG1ldGhvZHMgdG8gY3JlYXRlIGluc3RhbmNlIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlIGRpcmVjdGlvbiBiYXNlZCBvbiB0aGUgbmVnb3RpYXRpb24gYmV0d2VlbiBBcHBsaWNhdGlvbiBhbmQgTHdNMk0uIFRoZSBDbGllbnQgYW5kIFNlcnZlciBzaG91bGQgbmVnb3RpYXRlIHRoZSBpbnN0YW5jZShzKSB1c2VkIHRvIGV4Y2hhbmdlIHRoZSBkYXRhLiBGb3IgZXhhbXBsZToKIC0gVXNpbmcgYSBzaW5nbGUgaW5zdGFuY2UgZm9yIGJvdGggZGlyZWN0aW9ucyBjb21tdW5pY2F0aW9uLCBmcm9tIENsaWVudCB0byBTZXJ2ZXIgYW5kIGZyb20gU2VydmVyIHRvIENsaWVudC4KIC0gVXNpbmcgYW4gaW5zdGFuY2UgZm9yIGNvbW11bmljYXRpb24gZnJvbSBDbGllbnQgdG8gU2VydmVyIGFuZCBhbm90aGVyIG9uZSBmb3IgY29tbXVuaWNhdGlvbiBmcm9tIFNlcnZlciB0byBDbGllbnQKIC0gVXNpbmcgc2V2ZXJhbCBpbnN0YW5jZXMKXV0+PC9EZXNjcmlwdGlvbjE+CgkJPE9iamVjdElEPjE5PC9PYmplY3RJRD4KCQk8T2JqZWN0VVJOPnVybjpvbWE6bHdtMm06b21hOjE5PC9PYmplY3RVUk4+CgkJPExXTTJNVmVyc2lvbj4xLjA8L0xXTTJNVmVyc2lvbj4KCQk8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CgkJPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJPFJlc291cmNlcz4KCQkJPEl0ZW0gSUQ9IjAiPjxOYW1lPkRhdGE8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+T3BhcXVlPC9UeXBlPgoJCQkJPFJhbmdlRW51bWVyYXRpb24gLz4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgYXBwbGljYXRpb24gZGF0YSBjb250ZW50Ll1dPjwvRGVzY3JpcHRpb24+CgkJCTwvSXRlbT4KCQkJPEl0ZW0gSUQ9IjEiPjxOYW1lPkRhdGEgUHJpb3JpdHk8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgoJCQkJPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+SW50ZWdlcjwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjEgYnl0ZXM8L1JhbmdlRW51bWVyYXRpb24+CgkJCQk8VW5pdHMgLz4KCQkJCTxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgdGhlIEFwcGxpY2F0aW9uIGRhdGEgcHJpb3JpdHk6CjA6SW1tZWRpYXRlCjE6QmVzdEVmZm9ydAoyOkxhdGVzdAozLTEwMDogUmVzZXJ2ZWQgZm9yIGZ1dHVyZSB1c2UuCjEwMS0yNTQ6IFByb3ByaWV0YXJ5IG1vZGUuXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iMiI+PE5hbWU+RGF0YSBDcmVhdGlvbiBUaW1lPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlRpbWU8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbiAvPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBEYXRhIGluc3RhbmNlIGNyZWF0aW9uIHRpbWVzdGFtcC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSIzIj48TmFtZT5EYXRhIERlc2NyaXB0aW9uPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlN0cmluZzwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjMyIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkYXRhIGRlc2NyaXB0aW9uLgplLmcuICJtZXRlciByZWFkaW5nIi5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSI0Ij48TmFtZT5EYXRhIEZvcm1hdDwvTmFtZT4KCQkJCTxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgoJCQkJPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJCQk8VHlwZT5TdHJpbmc8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4zMiBieXRlczwvUmFuZ2VFbnVtZXJhdGlvbj4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgZm9ybWF0IG9mIHRoZSBBcHBsaWNhdGlvbiBEYXRhLgplLmcuIFlHLU1ldGVyLVdhdGVyLVJlYWRpbmcKVVRGOC1zdHJpbmcKXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iNSI+PE5hbWU+QXBwIElEPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPkludGVnZXI8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4yIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkZXN0aW5hdGlvbiBBcHBsaWNhdGlvbiBJRC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+PC9SZXNvdXJjZXM+CgkJPERlc2NyaXB0aW9uMj48IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMj4KCTwvT2JqZWN0Pgo8L0xXTTJNPgo="; + private Tenant savedTenant; private User tenantAdmin; @@ -88,7 +99,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My first resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = save(resource); @@ -123,7 +134,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle(StringUtils.randomAlphabetic(300)); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); Mockito.reset(tbClusterService, auditLogService); @@ -142,7 +153,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My first resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = save(resource); @@ -171,7 +182,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My first resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = save(resource); @@ -186,7 +197,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My first resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = save(resource); @@ -217,7 +228,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); resources.add(new TbResourceInfo(save(resource))); } List loadedResources = new ArrayList<>(); @@ -254,7 +265,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("JKS Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); resources.add(new TbResourceInfo(save(resource))); } @@ -264,7 +275,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("LWM2M Resource" + i); resource.setResourceType(ResourceType.PKCS_12); resource.setFileName(i + DEFAULT_FILE_NAME_2); - resource.setData("Test Data"); + resource.setData(TEST_DATA); save(resource); } @@ -301,7 +312,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); resources.add(new TbResourceInfo(save(resource))); } List loadedResources = new ArrayList<>(); @@ -361,7 +372,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("JKS Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResourceInfo saved = new TbResourceInfo(save(resource)); jksResources.add(saved); } @@ -372,7 +383,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("LWM2M Resource" + i); resource.setResourceType(ResourceType.PKCS_12); resource.setFileName(i + DEFAULT_FILE_NAME_2); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource saved = save(resource); lwm2mesources.add(saved); } @@ -438,7 +449,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); expectedResources.add(new TbResourceInfo(save(resource))); } @@ -449,7 +460,7 @@ public class TbResourceControllerTest extends AbstractControllerTest { resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResourceInfo savedResource = new TbResourceInfo(save(resource)); systemResources.add(savedResource); if (i >= 73) { @@ -485,6 +496,81 @@ public class TbResourceControllerTest extends AbstractControllerTest { } } + @Test + public void testDownloadTbResourceIfChanged() throws Exception { + Mockito.reset(tbClusterService, auditLogService); + + TbResource resource = new TbResource(); + resource.setResourceType(ResourceType.JS_MODULE); + resource.setTitle("Js resource"); + resource.setFileName(JS_TEST_FILE_NAME); + resource.setData(TEST_DATA); + + TbResource savedResource = save(resource); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedResource, savedResource.getId(), savedResource.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + + ResultActions resultActions = doGet("/api/resource/js/" + savedResource.getId().getId().toString() + "/download") + .andExpect(status().isOk()); + MockHttpServletResponse response = resultActions.andReturn().getResponse(); + String eTag = response.getHeader("ETag"); + Assert.assertNotNull(eTag); + Assert.assertEquals(Base64.getEncoder().encodeToString(response.getContentAsByteArray()), TEST_DATA); + + //download with if-none-match header + HttpHeaders headers = new HttpHeaders(); + headers.setIfNoneMatch(eTag); + doGet("/api/resource/js/" + savedResource.getId().getId().toString() + "/download", headers) + .andExpect(status().isNotModified()); + } + + @Ignore + @Test + public void testDownloadTbResourceIfChangedAsPublicCustomer() throws Exception { + loginTenantAdmin(); + Mockito.reset(tbClusterService, auditLogService); + + TbResource resource = new TbResource(); + resource.setResourceType(ResourceType.JS_MODULE); + resource.setTitle("Js resource"); + resource.setFileName(JS_TEST_FILE_NAME); + resource.setData(TEST_DATA); + + TbResource savedResource = save(resource); + + //download as public customer + Device device = new Device(); + device.setName("Test Public Device"); + device.setLabel("Label"); + device.setCustomerId(customerId); + device = doPost("/api/device", device, Device.class); + device = doPost("/api/customer/public/device/" + device.getUuidId(), Device.class); + + String publicId = device.getCustomerId().toString(); + + Mockito.reset(tbClusterService, auditLogService); + resetTokens(); + + JsonNode publicLoginRequest = JacksonUtil.toJsonNode("{\"publicId\": \"" + publicId + "\"}"); + JsonNode tokens = doPost("/api/auth/login/public", publicLoginRequest, JsonNode.class); + this.token = tokens.get("token").asText(); + + ResultActions resultActions = doGet("/api/resource/js/" + savedResource.getId().getId().toString() + "/download") + .andExpect(status().isOk()); + MockHttpServletResponse response = resultActions.andReturn().getResponse(); + String eTag = response.getHeader("ETag"); + Assert.assertNotNull(eTag); + Assert.assertEquals(Base64.getEncoder().encodeToString(response.getContentAsByteArray()), TEST_DATA); + + //download with if-none-match header + HttpHeaders headers = new HttpHeaders(); + headers.setIfNoneMatch(eTag); + doGet("/api/resource/js/" + savedResource.getId().getId().toString() + "/download", headers) + .andExpect(status().isNotModified()); + } + private TbResource save(TbResource tbResource) throws Exception { return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>(){}); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java index bef4681c05..b67cd49aff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java @@ -21,11 +21,28 @@ public enum ResourceType { PKCS_12("pkcs12", "application/x-pkcs12"), JS_MODULE("js", "application/javascript"); - public String type; - public String mediaType; + private final String type; + private final String mediaType; ResourceType(String type, String mediaType) { this.type = type; this.mediaType = mediaType; } + + public static ResourceType getResourceByType(String type) { + for(ResourceType resourceType : values()) { + if (resourceType.getType().equalsIgnoreCase(type)) { + return resourceType; + } + } + throw new IllegalArgumentException(); + } + + public String getMediaType() { + return mediaType; + } + + public String getType() { + return type; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java index 54dca66657..cb2dcf3133 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java @@ -29,6 +29,7 @@ import javax.persistence.Entity; import javax.persistence.Table; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_HASH_CODE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN; @@ -57,6 +58,9 @@ public class TbResourceInfoEntity extends BaseSqlEntity implemen @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; + @Column(name = RESOURCE_HASH_CODE_COLUMN) + private String hashCode; + public TbResourceInfoEntity() { } @@ -70,6 +74,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity implemen this.resourceType = resource.getResourceType().name(); this.resourceKey = resource.getResourceKey(); this.searchText = resource.getSearchText(); + this.hashCode = resource.getHashCode(); } @Override @@ -81,6 +86,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity implemen resource.setResourceType(ResourceType.valueOf(resourceType)); resource.setResourceKey(resourceKey); resource.setSearchText(searchText); + resource.setHashCode(hashCode); return resource; } } diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6f0969056c..62231e6995 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -697,6 +697,7 @@ CREATE TABLE IF NOT EXISTS resource ( search_text varchar(255), file_name varchar(255) NOT NULL, data varchar, + hash_code varchar, CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) ); From 06dd2044659e214bdf0fc209330922aacbc37d15 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Jun 2023 13:38:47 +0300 Subject: [PATCH 12/18] refactoring --- .../server/controller/TbResourceController.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 369dc2e2bd..fad26f72f7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -105,7 +105,7 @@ public class TbResourceController extends BaseController { .body(resource); } - @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Download LWM2M Resource (downloadLwm2mResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/lwm2m/{resourceId}/download", method = RequestMethod.GET) @ResponseBody @@ -114,7 +114,7 @@ public class TbResourceController extends BaseController { return downloadResourceIfChanged(ResourceType.LWM2M_MODEL, strResourceId, headers); } - @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Download PKCS_12 Resource (downloadPkcs12ResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/pkcs12/{resourceId}/download", method = RequestMethod.GET) @ResponseBody @@ -123,7 +123,16 @@ public class TbResourceController extends BaseController { return downloadResourceIfChanged(ResourceType.PKCS_12, strResourceId, headers); } - @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Download JKS Resource (downloadJksResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/resource/jks/{resourceId}/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadJksResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.JKS, strResourceId, headers); + } + + @ApiOperation(value = "Download JS Resource (downloadJsResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/js/{resourceId}/download", method = RequestMethod.GET) @ResponseBody From 407effbc2629bc67006300e655c1118da5e61ea7 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Jun 2023 14:08:55 +0300 Subject: [PATCH 13/18] refactoring --- .../controller/TbResourceController.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index fad26f72f7..6e8977a886 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -110,8 +110,8 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/lwm2m/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadLwm2mResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { - return downloadResourceIfChanged(ResourceType.LWM2M_MODEL, strResourceId, headers); + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.LWM2M_MODEL, strResourceId, etag); } @ApiOperation(value = "Download PKCS_12 Resource (downloadPkcs12ResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @@ -119,8 +119,8 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/pkcs12/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadPkcs12ResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { - return downloadResourceIfChanged(ResourceType.PKCS_12, strResourceId, headers); + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.PKCS_12, strResourceId, etag); } @ApiOperation(value = "Download JKS Resource (downloadJksResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @@ -128,17 +128,17 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/jks/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadJksResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { - return downloadResourceIfChanged(ResourceType.JKS, strResourceId, headers); + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.JKS, strResourceId, etag); } @ApiOperation(value = "Download JS Resource (downloadJsResourceIfChanged)", notes = "Download Resource based on the provided Resource Id or return 304 status code if resource was not changed." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/resource/js/{resourceId}/download", method = RequestMethod.GET) @ResponseBody public ResponseEntity downloadJsResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION) - @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader HttpHeaders headers) throws ThingsboardException { - return downloadResourceIfChanged(ResourceType.JS_MODULE, strResourceId, headers); + @PathVariable(RESOURCE_ID) String strResourceId, @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException { + return downloadResourceIfChanged(ResourceType.JS_MODULE, strResourceId, etag); } @ApiOperation(value = "Get Resource Info (getResourceInfoById)", @@ -271,21 +271,20 @@ public class TbResourceController extends BaseController { tbResourceService.delete(tbResource, getCurrentUser()); } - private ResponseEntity downloadResourceIfChanged(ResourceType type, String strResourceId, HttpHeaders headers) throws ThingsboardException { + private ResponseEntity downloadResourceIfChanged(ResourceType type, String strResourceId, String etag) throws ThingsboardException { checkParameter(RESOURCE_ID, strResourceId); TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); - TbResourceInfo tbResourceInfo = checkResourceInfoId(resourceId, Operation.READ); - List ifNoneMatchHeaders = headers.getIfNoneMatch(); - if (!ifNoneMatchHeaders.isEmpty()) { - if (ifNoneMatchHeaders.contains(tbResourceInfo.getHashCode())) { + if (etag != null) { + TbResourceInfo tbResourceInfo = checkResourceInfoId(resourceId, Operation.READ); + if (etag.equals(tbResourceInfo.getHashCode())) { return ResponseEntity.status(HttpStatus.NOT_MODIFIED) - .eTag(tbResourceInfo.getHashCode()).build(); + .eTag(tbResourceInfo.getHashCode()) + .build(); } } - SecurityUser currentUser = getCurrentUser(); - TbResource tbResource = resourceService.findResourceById(currentUser.getTenantId(), resourceId); + TbResource tbResource = checkResourceId(resourceId, Operation.READ); ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); return ResponseEntity.ok() From 87786ef72d8a08299083c9d05257b5d37a33fbc3 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 7 Jun 2023 15:27:44 +0300 Subject: [PATCH 14/18] refactoring --- .../controller/ControllerConstants.java | 2 +- .../controller/TbResourceController.java | 2 +- .../sql/BaseTbResourceServiceTest.java | 42 +++++++++---------- .../server/common/data/ResourceType.java | 25 +++-------- 4 files changed, 27 insertions(+), 44 deletions(-) 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 a8cc2c1043..1cb794c2ec 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -140,7 +140,7 @@ public class ControllerConstants { protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title."; protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId"; - protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "lwm2m, jks, pkcs12, js"; + protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "LWM2M_MODEL, JKS, PKCS_12, JS_MODULE"; protected static final String RESOURCE_TYPE = "A string value representing the resource type."; protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. "; diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 6e8977a886..7bd1000443 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -212,7 +212,7 @@ public class TbResourceController extends BaseController { TbResourceInfoFilter.TbResourceInfoFilterBuilder filter = TbResourceInfoFilter.builder(); filter.tenantId(getTenantId()); if (StringUtils.isNotEmpty(resourceType)){ - filter.resourceType(ResourceType.getResourceByType(resourceType)); + filter.resourceType(ResourceType.valueOf(resourceType)); } if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { return checkNotNull(resourceService.findTenantResourcesByTenantId(filter.build(), pageLink)); diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index e5183ba8b3..6f08e47412 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -105,6 +105,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { ""; private static final String DEFAULT_FILE_NAME = "test.jks"; + private static final String TEST_DATA = "77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQpGSUxFIElORk9STUFUSU9OCgpPTUEgUGVybWFuZW50IERvY3VtZW50CiAgIEZpbGU6IE9NQS1TVVAtTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci1WMV8wXzEtMjAxOTAyMjEtQQogICBUeXBlOiB4bWwKClB1YmxpYyBSZWFjaGFibGUgSW5mb3JtYXRpb24KICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMKICAgTmFtZTogTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci12MV8wXzEueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogIE9NQS1UUy1MV00yTV9CaW5hcnlBcHBEYXRhQ29udGFpbmVyLVYxXzBfMQoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvCgogIFNlbmQgY29tbWVudHMgdG8gaHR0cHM6Ly9naXRodWIuY29tL09wZW5Nb2JpbGVBbGxpYW5jZS9PTUFfTHdNMk1fZm9yX0RldmVsb3BlcnMvaXNzdWVzCgpDSEFOR0UgSElTVE9SWQoKMTUwNjIwMTggU3RhdHVzIGNoYW5nZWQgdG8gQXBwcm92ZWQgYnkgRE0sIERvYyBSZWYgIyBPTUEtRE0mU0UtMjAxOC0wMDYxLUlOUF9MV00yTV9BUFBEQVRBX1YxXzBfRVJQX2Zvcl9maW5hbF9BcHByb3ZhbAoyMTAyMjAxOSBTdGF0dXMgY2hhbmdlZCB0byBBcHByb3ZlZCBieSBJUFNPLCBEb2MgUmVmICMgT01BLUlQU08tMjAxOS0wMDI1LUlOUF9Md00yTV9PYmplY3RfQXBwX0RhdGFfQ29udGFpbmVyXzFfMF8xX2Zvcl9GaW5hbF9BcHByb3ZhbAoKTEVHQUwgRElTQ0xBSU1FUgoKQ29weXJpZ2h0IDIwMTkgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zCmFyZSBtZXQ6CgoxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodApub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCjIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUKZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUwpGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULApJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLApCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIKQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUCkxJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCkFOWSBXQVkgT1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRQpQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KClRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiBQbGVhc2UKcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKaHR0cHM6Ly93d3cub21hc3BlY3dvcmtzLm9yZy9hYm91dC9pbnRlbGxlY3R1YWwtcHJvcGVydHktcmlnaHRzLwoKLS0+CjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL29wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS54c2QiPgoJPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgoJCTxOYW1lPkJpbmFyeUFwcERhdGFDb250YWluZXI8L05hbWU+CgkJPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdHMgcHJvdmlkZXMgdGhlIGFwcGxpY2F0aW9uIHNlcnZpY2UgZGF0YSByZWxhdGVkIHRvIGEgTHdNMk0gU2VydmVyLCBlZy4gV2F0ZXIgbWV0ZXIgZGF0YS4gClRoZXJlIGFyZSBzZXZlcmFsIG1ldGhvZHMgdG8gY3JlYXRlIGluc3RhbmNlIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlIGRpcmVjdGlvbiBiYXNlZCBvbiB0aGUgbmVnb3RpYXRpb24gYmV0d2VlbiBBcHBsaWNhdGlvbiBhbmQgTHdNMk0uIFRoZSBDbGllbnQgYW5kIFNlcnZlciBzaG91bGQgbmVnb3RpYXRlIHRoZSBpbnN0YW5jZShzKSB1c2VkIHRvIGV4Y2hhbmdlIHRoZSBkYXRhLiBGb3IgZXhhbXBsZToKIC0gVXNpbmcgYSBzaW5nbGUgaW5zdGFuY2UgZm9yIGJvdGggZGlyZWN0aW9ucyBjb21tdW5pY2F0aW9uLCBmcm9tIENsaWVudCB0byBTZXJ2ZXIgYW5kIGZyb20gU2VydmVyIHRvIENsaWVudC4KIC0gVXNpbmcgYW4gaW5zdGFuY2UgZm9yIGNvbW11bmljYXRpb24gZnJvbSBDbGllbnQgdG8gU2VydmVyIGFuZCBhbm90aGVyIG9uZSBmb3IgY29tbXVuaWNhdGlvbiBmcm9tIFNlcnZlciB0byBDbGllbnQKIC0gVXNpbmcgc2V2ZXJhbCBpbnN0YW5jZXMKXV0+PC9EZXNjcmlwdGlvbjE+CgkJPE9iamVjdElEPjE5PC9PYmplY3RJRD4KCQk8T2JqZWN0VVJOPnVybjpvbWE6bHdtMm06b21hOjE5PC9PYmplY3RVUk4+CgkJPExXTTJNVmVyc2lvbj4xLjA8L0xXTTJNVmVyc2lvbj4KCQk8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CgkJPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJPFJlc291cmNlcz4KCQkJPEl0ZW0gSUQ9IjAiPjxOYW1lPkRhdGE8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+T3BhcXVlPC9UeXBlPgoJCQkJPFJhbmdlRW51bWVyYXRpb24gLz4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgYXBwbGljYXRpb24gZGF0YSBjb250ZW50Ll1dPjwvRGVzY3JpcHRpb24+CgkJCTwvSXRlbT4KCQkJPEl0ZW0gSUQ9IjEiPjxOYW1lPkRhdGEgUHJpb3JpdHk8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgoJCQkJPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+SW50ZWdlcjwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjEgYnl0ZXM8L1JhbmdlRW51bWVyYXRpb24+CgkJCQk8VW5pdHMgLz4KCQkJCTxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgdGhlIEFwcGxpY2F0aW9uIGRhdGEgcHJpb3JpdHk6CjA6SW1tZWRpYXRlCjE6QmVzdEVmZm9ydAoyOkxhdGVzdAozLTEwMDogUmVzZXJ2ZWQgZm9yIGZ1dHVyZSB1c2UuCjEwMS0yNTQ6IFByb3ByaWV0YXJ5IG1vZGUuXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iMiI+PE5hbWU+RGF0YSBDcmVhdGlvbiBUaW1lPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlRpbWU8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbiAvPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBEYXRhIGluc3RhbmNlIGNyZWF0aW9uIHRpbWVzdGFtcC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSIzIj48TmFtZT5EYXRhIERlc2NyaXB0aW9uPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlN0cmluZzwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjMyIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkYXRhIGRlc2NyaXB0aW9uLgplLmcuICJtZXRlciByZWFkaW5nIi5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSI0Ij48TmFtZT5EYXRhIEZvcm1hdDwvTmFtZT4KCQkJCTxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgoJCQkJPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJCQk8VHlwZT5TdHJpbmc8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4zMiBieXRlczwvUmFuZ2VFbnVtZXJhdGlvbj4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgZm9ybWF0IG9mIHRoZSBBcHBsaWNhdGlvbiBEYXRhLgplLmcuIFlHLU1ldGVyLVdhdGVyLVJlYWRpbmcKVVRGOC1zdHJpbmcKXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iNSI+PE5hbWU+QXBwIElEPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPkludGVnZXI8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4yIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkZXN0aW5hdGlvbiBBcHBsaWNhdGlvbiBJRC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+PC9SZXNvdXJjZXM+CgkJPERlc2NyaXB0aW9uMj48IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMj4KCTwvT2JqZWN0Pgo8L0xXTTJNPgo="; private IdComparator idComparator = new IdComparator<>(); @@ -147,7 +148,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { @Test public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception { loginSysAdmin(); - long limit = 1; + long limit = 4; EntityInfo defaultTenantProfileInfo = doGet("/api/tenantProfileInfo/default", EntityInfo.class); TenantProfile defaultTenantProfile = doGet("/api/tenantProfile/" + defaultTenantProfileInfo.getId().getId().toString(), TenantProfile.class); defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(limit).build()); @@ -159,7 +160,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { createResource("test", DEFAULT_FILE_NAME); - assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(4, resourceService.sumDataSizeByTenantId(tenantId)); try { assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME)) @@ -177,16 +178,12 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); createResource("test", DEFAULT_FILE_NAME); - assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(4, resourceService.sumDataSizeByTenantId(tenantId)); - int maxSumDataSize = 8; - - for (int i = 2; i <= maxSumDataSize; i++) { + for (int i = 2; i < 4; i++) { createResource("test" + i, i + DEFAULT_FILE_NAME); - assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId)); + assertEquals(i*4, resourceService.sumDataSizeByTenantId(tenantId)); } - - assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId)); } private TbResource createResource(String title, String filename) throws Exception { @@ -195,7 +192,8 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle(title); resource.setResourceType(ResourceType.JKS); resource.setFileName(filename); - resource.setData("1"); + byte[] b = new byte[1]; + resource.setData(Base64.getEncoder().encodeToString(b)); return resourceService.save(resource); } @@ -206,7 +204,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My first resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = resourceService.save(resource); @@ -254,7 +252,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = resourceService.save(resource); assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); @@ -269,7 +267,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = resourceService.save(resource); @@ -278,7 +276,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); try { Assertions.assertThrows(DataValidationException.class, () -> { @@ -295,7 +293,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTenantId(tenantId); resource.setResourceType(ResourceType.JKS); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); Assertions.assertThrows(DataValidationException.class, () -> { resourceService.save(resource); }); @@ -308,7 +306,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); Assertions.assertThrows(DataValidationException.class, () -> { resourceService.save(resource); }); @@ -335,7 +333,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = resourceService.save(resource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); @@ -351,7 +349,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTenantId(tenantId); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = resourceService.save(resource); TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey()); @@ -366,7 +364,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResource savedResource = resourceService.save(resource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); @@ -392,7 +390,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); resources.add(new TbResourceInfo(resourceService.save(resource))); } @@ -446,7 +444,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("System Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); TbResourceInfo tbResourceInfo = new TbResourceInfo(resourceService.save(resource)); if (i >= 50) { resources.add(tbResourceInfo); @@ -459,7 +457,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("Tenant Resource" + i); resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); - resource.setData("Test Data"); + resource.setData(TEST_DATA); resources.add(new TbResourceInfo(resourceService.save(resource))); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java index b67cd49aff..81be6a1e41 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java @@ -16,33 +16,18 @@ package org.thingsboard.server.common.data; public enum ResourceType { - LWM2M_MODEL("lwm2m", "application/xml"), - JKS("jks", "application/x-java-keystore"), - PKCS_12("pkcs12", "application/x-pkcs12"), - JS_MODULE("js", "application/javascript"); + LWM2M_MODEL("application/xml"), + JKS("application/x-java-keystore"), + PKCS_12("application/x-pkcs12"), + JS_MODULE("application/javascript"); - private final String type; private final String mediaType; - ResourceType(String type, String mediaType) { - this.type = type; + ResourceType(String mediaType) { this.mediaType = mediaType; } - public static ResourceType getResourceByType(String type) { - for(ResourceType resourceType : values()) { - if (resourceType.getType().equalsIgnoreCase(type)) { - return resourceType; - } - } - throw new IllegalArgumentException(); - } - public String getMediaType() { return mediaType; } - - public String getType() { - return type; - } } From 1bde8d3dffa5ea07c6946d701427fc1b3805b193 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 7 Jun 2023 15:37:47 +0300 Subject: [PATCH 15/18] UI: Added support js module resource in widget/action --- ui-ngx/package.json | 4 +- ui-ngx/src/app/core/http/resource.service.ts | 16 +- .../app/core/services/resources.service.ts | 32 +++- ui-ngx/src/app/modules/common/modules-map.ts | 7 + ...ction-pretty-resources-tabs.component.html | 32 ++-- .../modules/home/pages/admin/admin.module.ts | 2 + .../resources-library-table-config.resolve.ts | 7 +- .../resource/resources-library.component.ts | 4 +- .../resources-table-header.component.html | 30 +++ .../resources-table-header.component.ts | 42 ++++ .../pages/widget/widget-editor.component.html | 31 +-- .../pages/widget/widget-editor.component.scss | 27 +-- .../src/app/shared/components/public-api.ts | 1 + .../resource-autocomplete.component.html | 46 +++++ .../resource-autocomplete.component.ts | 179 ++++++++++++++++++ .../src/app/shared/models/resource.models.ts | 3 +- ui-ngx/src/app/shared/shared.module.ts | 3 + .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/yarn.lock | 16 +- 19 files changed, 419 insertions(+), 64 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts create mode 100644 ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts diff --git a/ui-ngx/package.json b/ui-ngx/package.json index ed096b871a..4112e24fce 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -96,7 +96,7 @@ "schema-inspector": "^2.0.2", "screenfull": "^6.0.2", "split.js": "^1.6.5", - "systemjs": "6.11.0", + "systemjs": "6.14.1", "tinycolor2": "^1.6.0", "tinymce": "~5.10.7", "tooltipster": "^4.2.8", @@ -137,7 +137,7 @@ "@types/raphael": "^2.3.2", "@types/react": "17.0.37", "@types/react-dom": "17.0.11", - "@types/systemjs": "6.1.1", + "@types/systemjs": "6.13.1", "@types/tinycolor2": "^1.4.3", "@types/tooltipster": "^0.0.31", "@typescript-eslint/eslint-plugin": "5.57.0", diff --git a/ui-ngx/src/app/core/http/resource.service.ts b/ui-ngx/src/app/core/http/resource.service.ts index 9296f701b0..f7cb8441d1 100644 --- a/ui-ngx/src/app/core/http/resource.service.ts +++ b/ui-ngx/src/app/core/http/resource.service.ts @@ -20,8 +20,9 @@ import { PageLink } from '@shared/models/page/page-link'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { forkJoin, Observable, of } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; -import { Resource, ResourceInfo } from '@shared/models/resource.models'; +import { Resource, ResourceInfo, ResourceType } from '@shared/models/resource.models'; import { catchError, map, mergeMap } from 'rxjs/operators'; +import { isNotEmptyStr } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -33,15 +34,22 @@ export class ResourceService { } - public getResources(pageLink: PageLink, config?: RequestConfig): Observable> { - return this.http.get>(`/api/resource${pageLink.toQuery()}`, - defaultHttpOptionsFromConfig(config)); + public getResources(pageLink: PageLink, resourceType?: ResourceType, config?: RequestConfig): Observable> { + let url = `/api/resource${pageLink.toQuery()}`; + if (isNotEmptyStr(resourceType)) { + url += `&resourceType=${resourceType}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } public getResource(resourceId: string, config?: RequestConfig): Observable { return this.http.get(`/api/resource/${resourceId}`, defaultHttpOptionsFromConfig(config)); } + public getResourceInfo(resourceId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/resource/info/${resourceId}`, defaultHttpOptionsFromConfig(config)); + } + public downloadResource(resourceId: string): Observable { return this.http.get(`/api/resource/${resourceId}/download`, { responseType: 'arraybuffer', diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index b34cec5eb0..5dc20ddd46 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -27,6 +27,9 @@ import { DOCUMENT } from '@angular/common'; import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { IModulesMap } from '@modules/common/modules-map.models'; +import { TbResourceId } from '@shared/models/id/tb-resource-id'; +import { isObject } from '@core/utils'; +import { AuthService } from '@core/auth/auth.service'; declare const System; @@ -69,16 +72,18 @@ export class ResourcesService { return this.loadResourceByType(fileType, url); } - public loadFactories(url: string, modulesMap: IModulesMap): Observable { + public loadFactories(resourceId: string | TbResourceId, modulesMap: IModulesMap): Observable { + const url = this.getDownloadUrl(resourceId); if (this.loadedModulesAndFactories[url]) { return this.loadedModulesAndFactories[url].asObservable(); } modulesMap.init(); + const meta = this.getMetaInfo(resourceId); const subject = new ReplaySubject(); this.loadedModulesAndFactories[url] = subject; import('@angular/compiler').then( () => { - System.import(url).then( + System.import(url, undefined, meta).then( (module) => { const modules = this.extractNgModules(module); if (modules.length) { @@ -123,16 +128,18 @@ export class ResourcesService { return subject.asObservable(); } - public loadModules(url: string, modulesMap: IModulesMap): Observable[]> { + public loadModules(resourceId: string | TbResourceId, modulesMap: IModulesMap): Observable[]> { + const url = this.getDownloadUrl(resourceId); if (this.loadedModules[url]) { return this.loadedModules[url].asObservable(); } modulesMap.init(); + const meta = this.getMetaInfo(resourceId); const subject = new ReplaySubject[]>(); this.loadedModules[url] = subject; import('@angular/compiler').then( () => { - System.import(url).then( + System.import(url, undefined, meta).then( (module) => { try { let modules; @@ -246,4 +253,21 @@ export class ResourcesService { this.anchor.appendChild(el); return subject.asObservable(); } + + private getDownloadUrl(resourceId: string | TbResourceId): string { + if (isObject(resourceId)) { + return `/api/resource/js/${(resourceId as TbResourceId).id}/download`; + } + return resourceId as string; + } + + private getMetaInfo(resourceId: string | TbResourceId): object { + if (isObject(resourceId)) { + return { + additionalHeaders: { + 'X-Authorization': `Bearer ${AuthService.getJwtToken()}` + } + }; + } + } } diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index b3a14b0f61..961d34a1d4 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -614,6 +614,13 @@ class ModulesMap implements IModulesMap { for (const moduleId of Object.keys(this.modulesMap)) { System.set('app:' + moduleId, this.modulesMap[moduleId]); } + System.constructor.prototype.shouldFetch = (url: string) => url.endsWith('/download'); + System.constructor.prototype.fetch = (url, options: RequestInit & {meta?: any}) => { + if (options?.meta?.additionalHeaders) { + options.headers = { ...options.headers, ...options.meta.additionalHeaders }; + } + return fetch(url, options); + }; this.initialized = true; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html index 7014570235..f44ff6138c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html @@ -22,11 +22,14 @@
- - - + + {{ 'widget.resource-is-module' | translate }} @@ -40,16 +43,15 @@ close
-
- -
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 02a4bf5867..533fd0fea1 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -28,6 +28,7 @@ import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component'; +import { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component'; import { QueueComponent } from '@home/pages/admin/queue/queue.component'; import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component'; @@ -44,6 +45,7 @@ import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-aut OAuth2SettingsComponent, HomeSettingsComponent, ResourcesLibraryComponent, + ResourcesTableHeaderComponent, QueueComponent, RepositoryAdminSettingsComponent, AutoCommitAdminSettingsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index b3126c781b..733e5ef2b3 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -36,6 +36,7 @@ import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources- import { PageLink } from '@shared/models/page/page-link'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { map } from 'rxjs/operators'; +import { ResourcesTableHeaderComponent } from '@home/pages/admin/resource/resources-table-header.component'; @Injectable() export class ResourcesLibraryTableConfigResolver implements Resolve> { @@ -53,6 +54,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve resource ? resource.title : ''; @@ -81,7 +83,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve this.translate.instant('resource.delete-resources-title', {count}); this.config.deleteEntitiesContent = () => this.translate.instant('resource.delete-resources-text'); - this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink); + this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink, this.config.componentsData.resourceType); this.config.loadEntity = id => this.resourceService.getResource(id.id); this.config.saveEntity = resource => this.saveResource(resource); this.config.deleteEntity = id => this.resourceService.deleteResource(id.id); @@ -110,6 +112,9 @@ export class ResourcesLibraryTableConfigResolver implements Resolve { this.config.tableTitle = this.translate.instant('resource.resources-library'); + this.config.componentsData = { + resourceType: '' + }; const authUser = getCurrentAuthUser(this.store); this.config.deleteEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); this.config.entitySelectionEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts index 90f7847e10..99d8697dc5 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts @@ -57,7 +57,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme ngOnInit() { super.ngOnInit(); this.entityForm.get('resourceType').valueChanges.pipe( - startWith(ResourceType.LWM2M_MODEL), + startWith(ResourceType.JS_MODULE), filter(() => this.isAdd), takeUntil(this.destroy$) ).subscribe((type) => { @@ -91,7 +91,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme buildForm(entity: Resource): FormGroup { return this.fb.group({ title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], - resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, Validators.required], + resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.JS_MODULE, Validators.required], fileName: [entity ? entity.fileName : null, Validators.required], data: [entity ? entity.data : null, Validators.required] }); diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.html b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.html new file mode 100644 index 0000000000..ad8cf063e0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.html @@ -0,0 +1,30 @@ + + + resource.resource-type + + + {{ "resource.all-types" | translate }} + + + {{ resourceTypesTranslationMap.get(resourceType) | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts new file mode 100644 index 0000000000..dfe4bacff8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-table-header.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2023 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. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Resource, ResourceInfo, ResourceType, ResourceTypeTranslationMap } from '@shared/models/resource.models'; +import { PageLink } from '@shared/models/page/page-link'; + +@Component({ + selector: 'tb-resources-table-header', + templateUrl: './resources-table-header.component.html', + styleUrls: [] +}) +export class ResourcesTableHeaderComponent extends EntityTableHeaderComponent { + + readonly resourceTypes: ResourceType[] = Object.values(ResourceType); + readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; + + constructor(protected store: Store) { + super(store); + } + + resourceTypeChanged(resourceType: ResourceType) { + this.entitiesTableConfig.componentsData.resourceType = resourceType; + this.entitiesTableConfig.getTable().resetSortAndFilter(true); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html index 046ad5acb2..2b650cb1ce 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -122,12 +122,14 @@
- - - + + {{ 'widget.resource-is-module' | translate }} @@ -140,15 +142,14 @@ close
-
- -
+ diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss index 7d0d854d46..b8359b38db 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss @@ -31,18 +31,21 @@ tb-widget-editor { overflow-y: auto; } - mat-form-field.resource-field { - max-height: 40px; - margin: 10px 0 0; - .mat-mdc-text-field-wrapper { - padding-bottom: 0; - .mat-mdc-form-field-flex { - max-height: 40px; - .mat-mdc-form-field-infix { - border: 0; - padding-top: 7px; - padding-bottom: 7px; - min-height: 32px; + .resource-field { + mat-form-field { + .mat-mdc-text-field-wrapper { + padding-bottom: 0; + height: 40px; + + .mat-mdc-form-field-flex { + max-height: 40px; + + .mat-mdc-form-field-infix { + border: 0; + padding-top: 7px; + padding-bottom: 7px; + min-height: 32px; + } } } } diff --git a/ui-ngx/src/app/shared/components/public-api.ts b/ui-ngx/src/app/shared/components/public-api.ts index 1dd31182f1..9ec226769a 100644 --- a/ui-ngx/src/app/shared/components/public-api.ts +++ b/ui-ngx/src/app/shared/components/public-api.ts @@ -22,4 +22,5 @@ export * from './js-func.component'; export * from './script-lang.component'; export * from './slack-conversation-autocomplete.component'; export * from './notification/template-autocomplete.component'; +export * from './resource/resource-autocomplete.component'; export * from './toggle-header.component'; diff --git a/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.html b/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.html new file mode 100644 index 0000000000..bd7a2016d0 --- /dev/null +++ b/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.html @@ -0,0 +1,46 @@ + + + + + + + + + + {{ searchText }} + + + diff --git a/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts b/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts new file mode 100644 index 0000000000..6debdb49d9 --- /dev/null +++ b/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts @@ -0,0 +1,179 @@ +/// +/// Copyright © 2016-2023 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. +/// + +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { Observable, of } from 'rxjs'; +import { catchError, debounceTime, map, share, switchMap, tap } from 'rxjs/operators'; +import { isDefinedAndNotNull, isEmptyStr, isEqual, isObject } from '@core/utils'; +import { ResourceInfo, ResourceType } from '@shared/models/resource.models'; +import { TbResourceId } from '@shared/models/id/tb-resource-id'; +import { ResourceService } from '@core/http/resource.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; + +@Component({ + selector: 'tb-resource-autocomplete', + templateUrl: './resource-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ResourceAutocompleteComponent), + multi: true + }] +}) +export class ResourceAutocompleteComponent implements ControlValueAccessor, OnInit { + + @Input() + @coerceBoolean() + disabled: boolean; + + @Input() + @coerceBoolean() + required: boolean; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + placeholder: string; + + @Input() + @coerceBoolean() + hideRequiredMarker = false; + + @Input() + @coerceBoolean() + allowAutocomplete = false; + + resourceFormGroup = this.fb.group({ + resource: [null] + }); + + filteredResources$: Observable>; + + searchText = ''; + + @ViewChild('resourceInput', {static: true}) resourceInput: ElementRef; + + private modelValue: string | TbResourceId; + private dirty = false; + + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder, + private resourceService: ResourceService) { + } + + ngOnInit(): void { + if(this.required) { + this.resourceFormGroup.get('resource').setValidators(Validators.required); + this.resourceFormGroup.get('resource').updateValueAndValidity({emitEvent: false}); + } + this.filteredResources$ = this.resourceFormGroup.get('resource').valueChanges + .pipe( + debounceTime(150), + tap(value => { + let modelValue; + if (isObject(value)) { + modelValue = value.id; + } else if (isEmptyStr(value)) { + modelValue = null; + } else { + modelValue = value; + } + this.updateView(modelValue); + if (value === null) { + this.clear(); + } + }), + map(value => value ? (typeof value === 'string' ? value : value.title) : ''), + switchMap(name => this.fetchResources(name) ), + share() + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.resourceFormGroup.disable({emitEvent: false}); + } else { + this.resourceFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string | TbResourceId) { + if (isDefinedAndNotNull(value)) { + this.searchText = ''; + if (isObject(value) && typeof value !== 'string' && (value as TbResourceId).id) { + this.resourceService.getResourceInfo(value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe(resource => { + this.modelValue = resource.id; + this.resourceFormGroup.get('resource').patchValue(resource, {emitEvent: false}); + }); + } else { + this.modelValue = value; + this.resourceFormGroup.get('resource').patchValue(value, {emitEvent: false}); + } + this.dirty = true; + } + } + + displayResourceFn(resource?: ResourceInfo | string): string { + return isObject(resource) ? (resource as ResourceInfo).title : resource as string; + } + + clear() { + this.resourceFormGroup.get('resource').patchValue('', {emitEvent: true}); + setTimeout(() => { + this.resourceInput.nativeElement.blur(); + this.resourceInput.nativeElement.focus(); + }, 0); + } + + onFocus() { + if (this.dirty) { + this.resourceFormGroup.get('resource').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = false; + } + } + + private updateView(value: string | TbResourceId ) { + if (!isEqual(this.modelValue, value)) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + private fetchResources(searchText?: string): Observable> { + this.searchText = searchText; + return this.resourceService.getResources(new PageLink(50, 0, searchText), ResourceType.JS_MODULE, {ignoreLoading: true}).pipe( + catchError(() => of(null)), + map(data => data.data) + ); + } + +} diff --git a/ui-ngx/src/app/shared/models/resource.models.ts b/ui-ngx/src/app/shared/models/resource.models.ts index 8d7121c5e5..6621f10c42 100644 --- a/ui-ngx/src/app/shared/models/resource.models.ts +++ b/ui-ngx/src/app/shared/models/resource.models.ts @@ -52,7 +52,7 @@ export const ResourceTypeTranslationMap = new Map( ] ); -export interface ResourceInfo extends BaseData { +export interface ResourceInfo extends Omit, 'name' | 'label'> { tenantId?: TenantId; resourceKey?: string; title?: string; @@ -62,6 +62,7 @@ export interface ResourceInfo extends BaseData { export interface Resource extends ResourceInfo { data: string; fileName: string; + name?: string; } export interface Resources extends ResourceInfo { diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 1d755c3bb1..7806c9c15b 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -189,6 +189,7 @@ import { GtMdLgShowHideDirective } from '@shared/layout/layout.directives'; import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component'; +import { ResourceAutocompleteComponent } from '@shared/components/resource/resource-autocomplete.component'; import { ShortNumberPipe } from '@shared/pipe/short-number.pipe'; import { ToggleHeaderComponent } from '@shared/components/toggle-header.component'; @@ -360,6 +361,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) GtMdLgLayoutGapDirective, GtMdLgShowHideDirective, ColorPickerComponent, + ResourceAutocompleteComponent, ToggleHeaderComponent ], imports: [ @@ -586,6 +588,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) GtMdLgLayoutGapDirective, GtMdLgShowHideDirective, ColorPickerComponent, + ResourceAutocompleteComponent, ToggleHeaderComponent ] }) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ea9230cf34..345d31067a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3252,6 +3252,7 @@ }, "resource": { "add": "Add Resource", + "all-types": "All", "copyId": "Copy resource Id", "delete": "Delete resource", "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 8da9cb382a..2ff2c81163 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -3286,10 +3286,10 @@ dependencies: "@types/react" "*" -"@types/systemjs@6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@types/systemjs/-/systemjs-6.1.1.tgz#eae17f2a080e867d01a2dd614f524ab227cf5a41" - integrity sha512-d1M6eDKBGWx7RbYy295VEFoOF9YDJkPI959QYnmzcmeaV+SP4D0xV7dEh3sN5XF3GvO3PhGzm+17Z598nvHQuQ== +"@types/systemjs@6.13.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@types/systemjs/-/systemjs-6.13.1.tgz#fccf8049fdf328bca4cfbad3a9cc7bf088b45048" + integrity sha512-Jxo2/uif1WpkabfyvWpFmPWFPDdwKUmyL7xWzjtxNALEu2pgce+eISjbf0Vr+SsK/D9savO5kTRcf+COLK5eiQ== "@types/tinycolor2@^1.4.3": version "1.4.3" @@ -10101,10 +10101,10 @@ symbol-observable@4.0.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== -systemjs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.11.0.tgz#8df8e74fc05822e6c40170aa409b9ca64833315f" - integrity sha512-7YPIY44j+BoY+E6cGBSw0oCU8SNTTIHKZgftcBdwWkDzs/M86Fdlr21FrzAyph7Zo8r3CFGscyFe4rrBtixrBg== +systemjs@6.14.1: + version "6.14.1" + resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.14.1.tgz#95a580b91b50d0d69ff178ed4816f0ddbcea23c1" + integrity sha512-8ftwWd+XnQtZ/aGbatrN4QFNGrKJzmbtixW+ODpci7pyoTajg4sonPP8aFLESAcuVxaC1FyDESt+SpfFCH9rZQ== table-layout@^1.0.2: version "1.0.2" From cc3e5681a89a5dd134e77cc8eaf9aa33170dd847 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 7 Jun 2023 16:28:10 +0300 Subject: [PATCH 16/18] UI: Added clearing cache resources when we change user --- .../app/core/services/resources.service.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 5dc20ddd46..55cda1660b 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -30,6 +30,9 @@ import { IModulesMap } from '@modules/common/modules-map.models'; import { TbResourceId } from '@shared/models/id/tb-resource-id'; import { isObject } from '@core/utils'; import { AuthService } from '@core/auth/auth.service'; +import { select, Store } from '@ngrx/store'; +import { selectIsAuthenticated } from '@core/auth/auth.selectors'; +import { AppState } from '@core/core.state'; declare const System; @@ -50,9 +53,12 @@ export class ResourcesService { private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; constructor(@Inject(DOCUMENT) private readonly document: any, + protected store: Store, private compiler: Compiler, private http: HttpClient, - private injector: Injector) {} + private injector: Injector) { + this.store.pipe(select(selectIsAuthenticated)).subscribe(() => this.clearCache()); + } public loadResource(url: string): Observable { if (this.loadedResources[url]) { @@ -270,4 +276,20 @@ export class ResourcesService { }; } } + + private deleteFromSystemJS(keys: string[]) { + keys.forEach(item => { + if (System.has(item)) { + System.delete(item); + } + }); + } + + private clearCache() { + this.deleteFromSystemJS(Object.keys(this.loadedModules)); + this.loadedModules = {}; + + this.deleteFromSystemJS(Object.keys(this.loadedModulesAndFactories)); + this.loadedModulesAndFactories = {}; + } } From 01065ec0c586d0d42d7bc5630ec3200d263a04dd Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Jun 2023 10:32:26 +0300 Subject: [PATCH 17/18] UI: Fixed clear cache in SystemJs load resources --- .../app/core/services/resources.service.ts | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 55cda1660b..70084153e8 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -33,6 +33,7 @@ import { AuthService } from '@core/auth/auth.service'; import { select, Store } from '@ngrx/store'; import { selectIsAuthenticated } from '@core/auth/auth.selectors'; import { AppState } from '@core/core.state'; +import { tap } from 'rxjs/operators'; declare const System; @@ -57,7 +58,7 @@ export class ResourcesService { private compiler: Compiler, private http: HttpClient, private injector: Injector) { - this.store.pipe(select(selectIsAuthenticated)).subscribe(() => this.clearCache()); + this.store.pipe(select(selectIsAuthenticated)).subscribe(() => this.clearModulesCache()); } public loadResource(url: string): Observable { @@ -71,9 +72,9 @@ export class ResourcesService { fileType = match[1]; } if (!fileType) { - return throwError(new Error(`Unable to detect file type from url: ${url}`)); + return throwError(() => new Error(`Unable to detect file type from url: ${url}`)); } else if (fileType !== 'css' && fileType !== 'js') { - return throwError(new Error(`Unsupported file type: ${fileType}`)); + return throwError(() => new Error(`Unsupported file type: ${fileType}`)); } return this.loadResourceByType(fileType, url); } @@ -97,7 +98,8 @@ export class ResourcesService { for (const m of modules) { tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); } - forkJoin(tasks).subscribe((compiled) => { + forkJoin(tasks).subscribe({ + next: (compiled) => { try { const componentFactories: ComponentFactory[] = []; for (const c of compiled) { @@ -112,26 +114,32 @@ export class ResourcesService { this.loadedModulesAndFactories[url].complete(); } catch (e) { this.loadedModulesAndFactories[url].error(new Error(`Unable to init module from url: ${url}`)); - delete this.loadedModulesAndFactories[url]; } }, - (e) => { + error: (e) => { this.loadedModulesAndFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); - delete this.loadedModulesAndFactories[url]; - }); + } + }); } else { this.loadedModulesAndFactories[url].error(new Error(`Module '${url}' doesn't have default export!`)); - delete this.loadedModulesAndFactories[url]; } }, (e) => { this.loadedModulesAndFactories[url].error(new Error(`Unable to load module from url: ${url}`)); - delete this.loadedModulesAndFactories[url]; } ); } ); - return subject.asObservable(); + return subject.asObservable().pipe( + tap({ + next: () => System.delete(url), + error: () => { + delete this.loadedModulesAndFactories[url]; + System.delete(url); + }, + complete: () => System.delete(url) + }) + ); } public loadModules(resourceId: string | TbResourceId, modulesMap: IModulesMap): Observable[]> { @@ -168,31 +176,35 @@ export class ResourcesService { this.loadedModules[url].complete(); } catch (e) { this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); - delete this.loadedModules[url]; } }, (e) => { this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); - delete this.loadedModules[url]; }); } else { this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export or not NgModule!`)); - delete this.loadedModules[url]; } } catch (e) { this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`)); - delete this.loadedModules[url]; } }, (e) => { this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`)); - delete this.loadedModules[url]; console.error(`Unable to load module from url: ${url}`, e); } ); } ); - return subject.asObservable(); + return subject.asObservable().pipe( + tap({ + next: () => System.delete(url), + error: () => { + delete this.loadedModulesAndFactories[url]; + System.delete(url); + }, + complete: () => System.delete(url) + }) + ); } private extractNgModules(module: any, modules: Type[] = []): Type[] { @@ -277,19 +289,8 @@ export class ResourcesService { } } - private deleteFromSystemJS(keys: string[]) { - keys.forEach(item => { - if (System.has(item)) { - System.delete(item); - } - }); - } - - private clearCache() { - this.deleteFromSystemJS(Object.keys(this.loadedModules)); + private clearModulesCache() { this.loadedModules = {}; - - this.deleteFromSystemJS(Object.keys(this.loadedModulesAndFactories)); this.loadedModulesAndFactories = {}; } } From a48416e442f59071ea12829b3c5d830139a1c83a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 8 Jun 2023 10:43:21 +0300 Subject: [PATCH 18/18] UI: Fixed process error in resource library component --- .../resource/resource-autocomplete.component.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts b/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts index 6debdb49d9..8489fe0640 100644 --- a/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/resource/resource-autocomplete.component.ts @@ -130,9 +130,15 @@ export class ResourceAutocompleteComponent implements ControlValueAccessor, OnIn if (isDefinedAndNotNull(value)) { this.searchText = ''; if (isObject(value) && typeof value !== 'string' && (value as TbResourceId).id) { - this.resourceService.getResourceInfo(value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe(resource => { - this.modelValue = resource.id; - this.resourceFormGroup.get('resource').patchValue(resource, {emitEvent: false}); + this.resourceService.getResourceInfo(value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe({ + next: resource => { + this.modelValue = resource.id; + this.resourceFormGroup.get('resource').patchValue(resource, {emitEvent: false}); + }, + error: () => { + this.modelValue = ''; + this.resourceFormGroup.get('resource').patchValue(''); + } }); } else { this.modelValue = value;