Browse Source

Image resources

pull/9542/head
ViacheslavKlimov 3 years ago
parent
commit
70eb9030c2
  1. 6
      application/src/main/data/upgrade/3.6.1/schema_update.sql
  2. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 44
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  5. 109
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  6. 18
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  7. 7
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  8. 100
      application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java
  9. 53
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  10. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  11. 19
      common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java
  12. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java
  13. 6
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  14. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  15. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java
  16. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java
  17. 35
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  18. 2
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java
  19. 6
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java
  20. 44
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
  21. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java
  22. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java
  23. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java
  24. 2
      dao/src/main/resources/sql/schema-entities.sql
  25. 2
      dao/src/test/java/org/thingsboard/server/dao/service/OtaPackageServiceTest.java
  26. 4
      ui-ngx/src/app/core/http/resource.service.ts
  27. 17
      ui-ngx/src/app/shared/models/resource.models.ts
  28. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
application/src/main/data/upgrade/3.6.1/schema_update.sql

@ -14,6 +14,8 @@
-- limitations under the License.
--
-- RESOURCES UPDATE START
DO
$$
BEGIN
@ -25,3 +27,7 @@ $$
END IF;
END;
$$;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS media_type varchar(255);
ALTER TABLE resource ADD COLUMN IF NOT EXISTS link varchar(255);
-- RESOURCES UPDATE END

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

@ -124,6 +124,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator;
@ -145,7 +146,6 @@ import org.thingsboard.server.service.entitiy.user.TbUserSettingsService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
@ -272,7 +272,7 @@ public abstract class BaseController {
protected PartitionService partitionService;
@Autowired
protected TbResourceService resourceService;
protected ResourceService resourceService;
@Autowired
protected OtaPackageService otaPackageService;
@ -587,7 +587,7 @@ public abstract class BaseController {
checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation);
return;
case TB_RESOURCE:
checkResourceId(new TbResourceId(entityId.getId()), operation);
checkResourceInfoId(new TbResourceId(entityId.getId()), operation);
return;
case OTA_PACKAGE:
checkOtaPackageId(new OtaPackageId(entityId.getId()), operation);

44
application/src/main/java/org/thingsboard/server/controller/TbResourceController.java

@ -44,6 +44,7 @@ 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.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -55,6 +56,7 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.util.Base64;
import java.util.List;
import java.util.function.Supplier;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.LWM2M_OBJECT_DESCRIPTION;
@ -107,6 +109,24 @@ public class TbResourceController extends BaseController {
.body(resource);
}
@ApiOperation(value = "Download Image (downloadImageIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/images/{tenantId}/{resourceKey}", produces = "image/*")
@ResponseBody
public ResponseEntity<ByteArrayResource> downloadImageIfChanged(@PathVariable("tenantId") String tenantIdStr,
@PathVariable("resourceKey") String resourceKey,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
TenantId tenantId;
if (tenantIdStr.equals("system")) {
tenantId = TenantId.SYS_TENANT_ID;
} else {
tenantId = TenantId.fromUUID(toUUID(tenantIdStr));
}
return downloadResourceIfChanged(ResourceType.IMAGE, etag,
() -> resourceService.findResourceInfoByTenantIdAndKey(tenantId, ResourceType.IMAGE, resourceKey),
() -> resourceService.findResourceByTenantIdAndKey(tenantId, ResourceType.IMAGE, resourceKey));
}
@ApiOperation(value = "Download LWM2M Resource (downloadLwm2mResourceIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource/lwm2m/{resourceId}/download", produces = "application/xml")
@ -249,7 +269,7 @@ public class TbResourceController extends BaseController {
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
PageLink pageLink = new PageLink(pageSize, page, textSearch);
return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink));
return checkNotNull(tbResourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink));
}
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)",
@ -265,7 +285,7 @@ public class TbResourceController extends BaseController {
@RequestParam String sortProperty,
@ApiParam(value = "LwM2M Object ids.", required = true)
@RequestParam(required = false) String[] objectIds) throws ThingsboardException {
return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds));
return checkNotNull(tbResourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds));
}
@ApiOperation(value = "Delete Resource (deleteResource)",
@ -281,12 +301,21 @@ public class TbResourceController extends BaseController {
tbResourceService.delete(tbResource, getCurrentUser());
}
private ResponseEntity<ByteArrayResource> downloadResourceIfChanged(ResourceType type, String strResourceId, String etag) throws ThingsboardException {
private ResponseEntity<ByteArrayResource> downloadResourceIfChanged(ResourceType resourceType, String strResourceId, String etag) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
TenantId tenantId = getTenantId();
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
return downloadResourceIfChanged(resourceType, etag,
() -> resourceService.findResourceInfoById(tenantId, resourceId),
() -> resourceService.findResourceById(tenantId, resourceId));
}
private ResponseEntity<ByteArrayResource> downloadResourceIfChanged(ResourceType resourceType, String etag,
Supplier<TbResourceInfo> resourceInfoSupplier,
Supplier<TbResource> resourceSupplier) throws ThingsboardException {
if (etag != null) {
TbResourceInfo tbResourceInfo = checkResourceInfoId(resourceId, Operation.READ);
TbResourceInfo tbResourceInfo = resourceInfoSupplier.get();
checkEntity(getCurrentUser(), tbResourceInfo, Operation.READ);
if (etag.equals(tbResourceInfo.getEtag())) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
.eTag(tbResourceInfo.getEtag())
@ -294,14 +323,15 @@ public class TbResourceController extends BaseController {
}
}
TbResource tbResource = checkResourceId(resourceId, Operation.READ);
ByteArrayResource resource = new ByteArrayResource(tbResource.getData());
TbResource tbResource = resourceSupplier.get();
checkEntity(getCurrentUser(), tbResource, Operation.READ);
ByteArrayResource resource = new ByteArrayResource(tbResource.getData());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName())
.header("x-filename", tbResource.getFileName())
.contentLength(resource.contentLength())
.header("Content-Type", type.getMediaType())
.header("Content-Type", tbResource.getMediaType() != null ? tbResource.getMediaType() : resourceType.getDefaultMediaType())
.cacheControl(CacheControl.noCache())
.eTag(tbResource.getEtag())
.body(resource);

3
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -333,8 +333,7 @@ public class InstallScripts {
throw new DataValidationException("Resource data should be specified!");
}
toLwm2mResource(resource);
TbResource foundResource =
resourceService.getResource(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey());
TbResource foundResource = resourceService.findResourceByTenantIdAndKey(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey());
if (foundResource == null) {
resourceService.saveResource(resource);
}

109
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -15,13 +15,12 @@
*/
package org.thingsboard.server.service.resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
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.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -31,7 +30,6 @@ import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -48,39 +46,45 @@ import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource
@Slf4j
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService {
private final ResourceService resourceService;
private final WidgetTypeService widgetTypeService;
public DefaultTbResourceService(ResourceService resourceService, WidgetTypeService widgetTypeService) {
this.resourceService = resourceService;
this.widgetTypeService = widgetTypeService;
}
@Override
public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) {
return resourceService.getResource(tenantId, resourceType, resourceId);
}
@Override
public TbResource findResourceById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceById(tenantId, resourceId);
}
@Override
public TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceInfoById(tenantId, resourceId);
}
@Override
public PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) {
return resourceService.findAllTenantResourcesByTenantId(filter, pageLink);
public TbResource save(TbResource resource, User user) throws ThingsboardException {
ActionType actionType = resource.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = resource.getTenantId();
try {
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
toLwm2mResource(resource);
} else if (resource.getResourceKey() == null) {
resource.setResourceKey(resource.getFileName());
}
TbResource savedResource = resourceService.saveResource(resource);
tbClusterService.onResourceChange(savedResource, null);
notificationEntityService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user);
return savedResource;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
resource, actionType, user, e);
throw e;
}
}
@Override
public PageData<TbResourceInfo> findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) {
return resourceService.findTenantResourcesByTenantId(filter, pageLink);
public void delete(TbResource tbResource, User user) {
TbResourceId resourceId = tbResource.getId();
TenantId tenantId = tbResource.getTenantId();
try {
resourceService.deleteResource(tenantId, resourceId);
tbClusterService.onResourceDeleted(tbResource, null);
notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString());
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
ActionType.DELETED, user, e, resourceId.toString());
throw e;
}
}
@Override
@ -106,16 +110,6 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
.collect(Collectors.toList());
}
@Override
public void deleteResourcesByTenantId(TenantId tenantId) {
resourceService.deleteResourcesByTenantId(tenantId);
}
@Override
public long sumDataSizeByTenantId(TenantId tenantId) {
return resourceService.sumDataSizeByTenantId(tenantId);
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {
@ -126,43 +120,4 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
return "DESC".equals(sortOrder) ? comparator.reversed() : comparator;
}
@Override
public TbResource save(TbResource tbResource, User user) throws ThingsboardException {
ActionType actionType = tbResource.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = tbResource.getTenantId();
try {
TbResource savedResource = checkNotNull(doSave(tbResource));
tbClusterService.onResourceChange(savedResource, null);
notificationEntityService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user);
return savedResource;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
tbResource, actionType, user, e);
throw e;
}
}
@Override
public void delete(TbResource tbResource, User user) {
TbResourceId resourceId = tbResource.getId();
TenantId tenantId = tbResource.getTenantId();
try {
resourceService.deleteResource(tenantId, resourceId);
tbClusterService.onResourceDeleted(tbResource, null);
notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString());
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
ActionType.DELETED, user, e, resourceId.toString());
throw e;
}
}
private TbResource doSave(TbResource resource) throws ThingsboardException {
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
toLwm2mResource(resource);
} else {
resource.setResourceKey(resource.getFileName());
}
return resourceService.saveResource(resource);
}
}

18
application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -15,14 +15,9 @@
*/
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;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
@ -30,16 +25,6 @@ import java.util.List;
public interface TbResourceService extends SimpleTbEntityService<TbResource> {
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
PageData<TbResourceInfo> findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
List<LwM2mObject> findLwM2mObject(TenantId tenantId,
String sortOrder,
String sortProperty,
@ -50,7 +35,4 @@ public interface TbResourceService extends SimpleTbEntityService<TbResource> {
String sortOrder,
PageLink pageLink);
void deleteResourcesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

7
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -77,6 +77,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -139,7 +140,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService;
private final TbResourceService resourceService;
private final ResourceService resourceService;
private final OtaPackageService otaPackageService;
private final OtaPackageDataCache otaPackageDataCache;
private final QueueService queueService;
@ -489,10 +490,10 @@ public class DefaultTransportApiService implements TransportApiService {
ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
String resourceKey = requestMsg.getResourceKey();
TransportProtos.GetResourceResponseMsg.Builder builder = TransportProtos.GetResourceResponseMsg.newBuilder();
TbResource resource = resourceService.getResource(tenantId, resourceType, resourceKey);
TbResource resource = resourceService.findResourceByTenantIdAndKey(tenantId, resourceType, resourceKey);
if (resource == null && !tenantId.equals(TenantId.SYS_TENANT_ID)) {
resource = resourceService.getResource(TenantId.SYS_TENANT_ID, resourceType, resourceKey);
resource = resourceService.findResourceByTenantIdAndKey(TenantId.SYS_TENANT_ID, resourceType, resourceKey);
}
if (resource != null) {

100
application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java

@ -47,17 +47,13 @@ import java.util.Base64;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class TbResourceControllerTest extends AbstractControllerTest {
/*
* TODO:
* test maxResourceSize limit in tenant profile
* */
private IdComparator<TbResourceInfo> idComparator = new IdComparator<>();
private static final String DEFAULT_FILE_NAME = "test.jks";
@ -621,6 +617,100 @@ public class TbResourceControllerTest extends AbstractControllerTest {
.andExpect(status().isForbidden());
}
@Test
public void testUpdateResourceData_nonUpdatableResourceType() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.IMAGE);
resource.setTitle("My resource");
resource.setFileName("image.png");
resource.setMediaType("image/png");
resource.setBase64Data(TEST_DATA);
TbResource savedResource = save(resource);
resource.setEtag(savedResource.getEtag());
savedResource.setBase64Data(TEST_DATA);
doPost("/api/resource", savedResource)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("can't be updated")));
savedResource.setBase64Data(null);
savedResource.setTitle("Updated resource");
savedResource = doPost("/api/resource", savedResource, TbResource.class);
assertThat(savedResource.getTitle()).isEqualTo("Updated resource");
assertThat(savedResource.getFileName()).isEqualTo(resource.getFileName());
assertThat(savedResource.getMediaType()).isEqualTo(resource.getMediaType());
assertThat(savedResource.getEtag()).isEqualTo(resource.getEtag());
assertThat(download(savedResource.getId())).asBase64Encoded().isEqualTo(TEST_DATA);
}
@Test
public void testUpdateResourceData_updatableResourceType() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JS_MODULE);
resource.setTitle("My resource");
resource.setFileName("module.js");
resource.setBase64Data(TEST_DATA);
TbResource savedResource = save(resource);
resource.setEtag(savedResource.getEtag());
String newData = Base64.getEncoder().encodeToString(new byte[]{1, 2, 3});
savedResource.setBase64Data(newData);
savedResource.setFileName("new-module.js");
savedResource.setTitle("Updated title");
savedResource = save(savedResource);
assertThat(savedResource.getTitle()).isEqualTo("Updated title");
assertThat(savedResource.getFileName()).isEqualTo("new-module.js");
assertThat(savedResource.getEtag()).isNotEqualTo(resource.getEtag());
assertThat(download(savedResource.getId())).asBase64Encoded().isEqualTo(newData);
}
@Test
public void testSaveImage_systemLevel() throws Exception {
loginSysAdmin();
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.IMAGE);
resource.setTitle("System image");
resource.setFileName("image.png");
resource.setMediaType("image/png");
resource.setBase64Data(TEST_DATA);
TbResource savedResource = save(resource);
assertThat(savedResource.getLink()).isEqualTo("/api/images/system/image.png");
loginTenantAdmin();
MockHttpServletResponse imageResponse = doGet(savedResource.getLink()).andExpect(status().isOk())
.andReturn().getResponse();
assertThat(imageResponse.getContentAsByteArray())
.isEqualTo(download(savedResource.getId()))
.isEqualTo(Base64.getDecoder().decode(TEST_DATA));
assertThat(imageResponse.getContentType()).isEqualTo("image/png");
}
@Test
public void testSaveImage_tenantLevel() throws Exception {
loginTenantAdmin();
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.IMAGE);
resource.setTitle("My image");
resource.setFileName("image.jpg");
resource.setMediaType("image/jpeg");
resource.setBase64Data(TEST_DATA);
TbResource savedResource = save(resource);
assertThat(savedResource.getLink()).isEqualTo("/api/images/" + tenantId + "/image.jpg");
MockHttpServletResponse imageResponse = doGet(savedResource.getLink()).andExpect(status().isOk())
.andReturn().getResponse();
assertThat(imageResponse.getContentAsByteArray())
.isEqualTo(download(savedResource.getId()))
.isEqualTo(Base64.getDecoder().decode(TEST_DATA));
assertThat(imageResponse.getContentType()).isEqualTo("image/jpeg");
loginDifferentTenant();
doGet(savedResource.getLink()).andExpect(status().isForbidden());
}
private TbResource save(TbResource tbResource) throws Exception {
return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>(){});
}

53
application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.resource.TbResourceService;
@ -115,7 +116,9 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
private TenantId tenantId;
@Autowired
private TbResourceService resourceService;
private ResourceService resourceService;
@Autowired
private TbResourceService tbResourceService;
private Tenant savedTenant;
private User tenantAdmin;
@ -207,7 +210,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setFileName(filename);
byte[] b = new byte[]{1, 2, 3, 4};
resource.setBase64Data(Base64.getEncoder().encodeToString(b));
return resourceService.save(resource);
return tbResourceService.save(resource);
}
@Test
@ -219,7 +222,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
@ -233,12 +236,12 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
savedResource.setBase64Data(null);
savedResource.setData(null);
resourceService.save(savedResource);
tbResourceService.save(savedResource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
assertEquals(foundResource.getTitle(), savedResource.getTitle());
assertArrayEquals(foundResource.getData(), TEST_DATA);
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
}
@Test
@ -249,7 +252,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setFileName("test_model.xml");
resource.setBase64Data(Base64.getEncoder().encodeToString(LWM2M_TEST_MODEL.getBytes()));
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
Assert.assertNotNull(savedResource);
Assert.assertNotNull(savedResource.getId());
@ -259,7 +262,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
assertEquals("0_1.0", savedResource.getResourceKey());
assertArrayEquals(savedResource.getData(), LWM2M_TEST_MODEL.getBytes());
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
}
@Test
@ -269,11 +272,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setTitle("My resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId());
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
}
@Test
@ -285,7 +288,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
TbResource resource2 = new TbResource();
resource.setTenantId(tenantId);
@ -296,10 +299,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
try {
Assertions.assertThrows(DataValidationException.class, () -> {
resourceService.save(resource2);
tbResourceService.save(resource2);
});
} finally {
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
}
}
@ -311,7 +314,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
Assertions.assertThrows(DataValidationException.class, () -> {
resourceService.save(resource);
tbResourceService.save(resource);
});
}
@ -324,7 +327,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
Assertions.assertThrows(DataValidationException.class, () -> {
resourceService.save(resource);
tbResourceService.save(resource);
});
}
@ -337,7 +340,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setData(LWM2M_TEST_MODEL_WITH_XXE.getBytes());
DataValidationException thrown = assertThrows(DataValidationException.class, () -> {
resourceService.save(resource);
tbResourceService.save(resource);
});
assertEquals("Failed to parse file xxe_test_model.xml", thrown.getMessage());
}
@ -350,12 +353,12 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setTitle("My resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNotNull(foundResource);
assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
}
@Test
@ -366,12 +369,12 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setTitle("My resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey());
TbResource foundResource = resourceService.findResourceByTenantIdAndKey(tenantId, savedResource.getResourceType(), savedResource.getResourceKey());
Assert.assertNotNull(foundResource);
assertEquals(savedResource, foundResource);
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
}
@Test
@ -381,11 +384,11 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setTitle("My resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResource savedResource = resourceService.save(resource);
TbResource savedResource = tbResourceService.save(resource);
TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNotNull(foundResource);
resourceService.delete(savedResource, null);
tbResourceService.delete(savedResource, null);
foundResource = resourceService.findResourceById(tenantId, savedResource.getId());
Assert.assertNull(foundResource);
}
@ -407,7 +410,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
resources.add(new TbResourceInfo(resourceService.save(resource)));
resources.add(new TbResourceInfo(tbResourceService.save(resource)));
}
List<TbResourceInfo> loadedResources = new ArrayList<>();
@ -461,7 +464,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
TbResourceInfo tbResourceInfo = new TbResourceInfo(resourceService.save(resource));
TbResourceInfo tbResourceInfo = new TbResourceInfo(tbResourceService.save(resource));
if (i >= 50) {
resources.add(tbResourceInfo);
}
@ -474,7 +477,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setBase64Data(TEST_BASE64_DATA);
resources.add(new TbResourceInfo(resourceService.save(resource)));
resources.add(new TbResourceInfo(tbResourceService.save(resource)));
}
List<TbResourceInfo> loadedResources = new ArrayList<>();

5
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -32,12 +32,14 @@ public interface ResourceService extends EntityDaoService {
TbResource saveResource(TbResource resource);
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
ListenableFuture<TbResourceInfo> findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId);
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
@ -53,4 +55,5 @@ public interface ResourceService extends EntityDaoService {
void deleteResourcesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

19
common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java

@ -16,20 +16,21 @@
package org.thingsboard.server.common.data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum ResourceType {
LWM2M_MODEL("application/xml", false),
JKS("application/x-java-keystore", false),
PKCS_12("application/x-pkcs12", false),
JS_MODULE("application/javascript", true);
LWM2M_MODEL("application/xml", false, false),
JKS("application/x-java-keystore", false, false),
PKCS_12("application/x-pkcs12", false, false),
JS_MODULE("application/javascript", true, true),
IMAGE(null, true, false);
@Getter
private final String mediaType;
private final String defaultMediaType;
@Getter
private final boolean customerAccess;
@Getter
private final boolean updatable;
ResourceType(String mediaType, boolean customerAccess) {
this.mediaType = mediaType;
this.customerAccess = customerAccess;
}
}

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

File diff suppressed because one or more lines are too long

6
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -54,6 +54,10 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
@Length(fieldName = "file name")
@ApiModelProperty(position = 9, value = "Resource file name.", example = "19.xml", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String fileName;
@ApiModelProperty(position = 10, value = "Resource media type.", example = "image/png", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String mediaType;
@ApiModelProperty(position = 11, value = "Resource link (for IMAGE resource type).", example = "/api/images/system/my-image.png", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String link;
public TbResourceInfo() {
super();
@ -72,6 +76,8 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
this.searchText = resourceInfo.searchText;
this.etag = resourceInfo.etag;
this.fileName = resourceInfo.fileName;
this.mediaType = resourceInfo.mediaType;
this.link = resourceInfo.link;
}
@ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " +

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

@ -498,6 +498,8 @@ public class ModelConstants {
public static final String RESOURCE_FILE_NAME_COLUMN = "file_name";
public static final String RESOURCE_DATA_COLUMN = "data";
public static final String RESOURCE_ETAG_COLUMN = "etag";
public static final String RESOURCE_MEDIA_TYPE_COLUMN = "media_type";
public static final String RESOURCE_LINK_COLUMN = "link";
/**
* Ota Package constants.

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceEntity.java

@ -33,6 +33,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DATA_COLU
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_ETAG_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_FILE_NAME_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_LINK_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_MEDIA_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TITLE_COLUMN;
@ -69,6 +71,12 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> implements BaseE
@Column(name = RESOURCE_ETAG_COLUMN)
private String etag;
@Column(name = RESOURCE_MEDIA_TYPE_COLUMN)
private String mediaType;
@Column(name = RESOURCE_LINK_COLUMN)
private String link;
public TbResourceEntity() {
}
@ -87,6 +95,8 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> implements BaseE
this.fileName = resource.getFileName();
this.data = resource.getData();
this.etag = resource.getEtag();
this.mediaType = resource.getMediaType();
this.link = resource.getLink();
}
@Override
@ -101,6 +111,8 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> implements BaseE
resource.setFileName(fileName);
resource.setData(data);
resource.setEtag(etag);
resource.setMediaType(mediaType);
resource.setLink(link);
return resource;
}

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/TbResourceInfoEntity.java

@ -32,6 +32,8 @@ import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_ETAG_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_FILE_NAME_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_KEY_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_LINK_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_MEDIA_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TITLE_COLUMN;
@ -65,6 +67,12 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
@Column(name = RESOURCE_FILE_NAME_COLUMN)
private String fileName;
@Column(name = RESOURCE_MEDIA_TYPE_COLUMN)
private String mediaType;
@Column(name = RESOURCE_LINK_COLUMN)
private String link;
public TbResourceInfoEntity() {
}
@ -80,6 +88,8 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
this.searchText = resource.getSearchText();
this.hashCode = resource.getEtag();
this.fileName = resource.getFileName();
this.mediaType = resource.getMediaType();
this.link = resource.getLink();
}
@Override
@ -93,6 +103,8 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
resource.setSearchText(searchText);
resource.setEtag(hashCode);
resource.setFileName(fileName);
resource.setMediaType(mediaType);
resource.setLink(link);
return resource;
}
}

35
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -38,9 +38,9 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import java.util.List;
import java.util.Optional;
@ -57,14 +57,12 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId ";
private final TbResourceDao resourceDao;
private final TbResourceInfoDao resourceInfoDao;
private final DataValidator<TbResource> resourceValidator;
private final ResourceDataValidator resourceValidator;
@Override
public TbResource saveResource(TbResource resource) {
log.trace("Executing saveResource [{}]", resource);
if (resource.getData() != null) {
resource.setEtag(calculateEtag(resource.getData()));
}
TenantId tenantId = resource.getTenantId();
resourceValidator.validate(resource, TbResourceInfo::getTenantId);
boolean newResource = resource.getId() == null;
@ -73,18 +71,26 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
resource.setId(new TbResourceId(uuid));
resource.setCreatedTime(Uuids.unixTimestamp(uuid));
}
if (resource.getData() != null) {
resource.setEtag(calculateEtag(resource.getData()));
}
if (resource.getResourceType() == ResourceType.IMAGE) {
resource.setLink(String.format("/api/images/%s/%s",
tenantId.isSysTenantId() ? "system" : tenantId.getId(),
resource.getResourceKey()));
}
try {
TbResource saved;
if (resource.getData() != null) {
saved = resourceDao.save(resource.getTenantId(), resource);
saved = resourceDao.save(tenantId, resource);
} else {
TbResourceInfo resourceInfo = saveResourceInfo(resource);
saved = new TbResource(resourceInfo);
}
publishEvictEvent(new ResourceInfoEvictEvent(resource.getTenantId(), resource.getId()));
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));
return saved;
} catch (Exception t) {
publishEvictEvent(new ResourceInfoEvictEvent(resource.getTenantId(), resource.getId()));
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("resource_unq_key")) {
String field = ResourceType.LWM2M_MODEL.equals(resource.getResourceType()) ? "resourceKey" : "fileName";
@ -100,9 +106,9 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
}
@Override
public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey) {
log.trace("Executing getResource [{}] [{}] [{}]", tenantId, resourceType, resourceKey);
return resourceDao.getResource(tenantId, resourceType, resourceKey);
public TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey) {
log.trace("Executing findResourceByTenantIdAndKey [{}] [{}] [{}]", tenantId, resourceType, resourceKey);
return resourceDao.findResourceByTenantIdAndKey(tenantId, resourceType, resourceKey);
}
@Override
@ -121,6 +127,13 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
() -> resourceInfoDao.findById(tenantId, resourceId.getId()), true);
}
@Override
public TbResourceInfo findResourceInfoByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey) {
log.trace("Executing findResourceInfoByTenantIdAndKey [{}] [{}] [{}]", tenantId, resourceType, resourceKey);
// TODO: add caching
return resourceInfoDao.findByTenantIdAndKey(tenantId, resourceType, resourceKey);
}
@Override
public ListenableFuture<TbResourceInfo> findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId) {
log.trace("Executing findResourceInfoById [{}] [{}]", tenantId, resourceId);

2
dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java

@ -27,7 +27,7 @@ import java.util.List;
public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao {
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceId);
PageData<TbResource> findAllByTenantId(TenantId tenantId, PageLink pageLink);

6
dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceInfoDao.java

@ -15,18 +15,20 @@
*/
package org.thingsboard.server.dao.resource;
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;
import org.thingsboard.server.dao.Dao;
import java.util.UUID;
public interface TbResourceInfoDao extends Dao<TbResourceInfo> {
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
PageData<TbResourceInfo> findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
TbResourceInfo findByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
}

44
dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java

@ -17,6 +17,8 @@ package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
@ -26,7 +28,6 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
import org.thingsboard.server.common.data.widget.BaseWidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.resource.TbResourceDao;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -59,23 +60,12 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
if (resource.getData() == null || resource.getData().length == 0) {
throw new DataValidationException("Resource data should be specified");
}
if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId)) {
DefaultTenantProfileConfiguration profileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
long maxResourceSize = profileConfiguration.getMaxResourceSize();
if (maxResourceSize > 0 && resource.getData().length > maxResourceSize) {
throw new IllegalArgumentException("Resource exceeds the maximum size of " + maxResourceSize + " bytes");
}
long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length, TB_RESOURCE);
}
}
@Override
protected TbResource validateUpdate(TenantId tenantId, TbResource resource) {
if (!tenantId.isSysTenantId()) {
if (resource.getData() != null) {
throw new DataValidationException("Resource data can't be updated");
}
if (resource.getData() != null && !resource.getResourceType().isUpdatable()) {
throw new DataValidationException("This type of resource can't be updated");
}
return resource;
}
@ -86,6 +76,28 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
if (resource.getResourceType() == null) {
throw new DataValidationException("Resource type should be specified!");
}
if (tenantId != null && !tenantId.isSysTenantId() && resource.getData() != null) {
DefaultTenantProfileConfiguration profileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
long maxResourceSize = profileConfiguration.getMaxResourceSize();
if (maxResourceSize > 0 && resource.getData().length > maxResourceSize) {
throw new IllegalArgumentException("Resource exceeds the maximum size of " + maxResourceSize + " bytes");
}
long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length, TB_RESOURCE);
}
if (resource.getResourceType().getDefaultMediaType() != null) {
resource.setMediaType(resource.getResourceType().getDefaultMediaType());
} else {
if (resource.getMediaType() == null) {
throw new DataValidationException("Media type is required");
} else {
try {
MediaType.parseMediaType(resource.getMediaType());
} catch (InvalidMediaTypeException e) {
throw new DataValidationException("Invalid media type", e);
}
}
}
if (StringUtils.isEmpty(resource.getFileName())) {
throw new DataValidationException("Resource file name should be specified!");
}
@ -93,9 +105,9 @@ public class ResourceDataValidator extends DataValidator<TbResource> {
throw new DataValidationException("Resource key should be specified!");
}
if (resource.getTenantId() == null) {
resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
resource.setTenantId(TenantId.SYS_TENANT_ID);
}
if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
if (!resource.getTenantId().isSysTenantId()) {
if (!tenantService.tenantExists(resource.getTenantId())) {
throw new DataValidationException("Resource is referencing to non-existent tenant!");
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java

@ -56,8 +56,7 @@ public class JpaTbResourceDao extends JpaAbstractDao<TbResourceEntity, TbResourc
}
@Override
public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey) {
public TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey) {
return DaoUtil.getData(resourceRepository.findByTenantIdAndResourceTypeAndResourceKey(tenantId.getId(), resourceType.name(), resourceKey));
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java

@ -75,4 +75,9 @@ public class JpaTbResourceInfoDao extends JpaAbstractDao<TbResourceInfoEntity, T
DaoUtil.toPageable(pageLink)));
}
@Override
public TbResourceInfo findByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey) {
return DaoUtil.getData(resourceInfoRepository.findByTenantIdAndResourceTypeAndResourceKey(tenantId.getId(), resourceType.name(), resourceKey));
}
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java

@ -51,4 +51,6 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
@Param("searchText") String searchText,
Pageable pageable);
TbResourceInfoEntity findByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey);
}

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

@ -715,6 +715,8 @@ CREATE TABLE IF NOT EXISTS resource (
file_name varchar(255) NOT NULL,
data bytea,
etag varchar,
media_type varchar(255),
link varchar(255),
CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
);

2
dao/src/test/java/org/thingsboard/server/dao/service/OtaPackageServiceTest.java

@ -102,7 +102,7 @@ public class OtaPackageServiceTest extends AbstractServiceTest {
assertThatThrownBy(() -> createAndSaveFirmware(tenantId, "2"))
.isInstanceOf(DataValidationException.class)
.hasMessageContaining("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE);
.hasMessageContaining("Ota packages total size exceeds the maximum of %d bytes", DATA_SIZE);
}
@Test

4
ui-ngx/src/app/core/http/resource.service.ts

@ -42,6 +42,10 @@ export class ResourceService {
return this.http.get<PageData<ResourceInfo>>(url, defaultHttpOptionsFromConfig(config));
}
public getResource(resourceId: string, config?: RequestConfig): Observable<Resource> {
return this.http.get<Resource>(`/api/resource/${resourceId}`, defaultHttpOptionsFromConfig(config));
}
public getResourceInfo(resourceId: string, config?: RequestConfig): Observable<ResourceInfo> {
return this.http.get<Resource>(`/api/resource/info/${resourceId}`, defaultHttpOptionsFromConfig(config));
}

17
ui-ngx/src/app/shared/models/resource.models.ts

@ -22,7 +22,8 @@ export enum ResourceType {
LWM2M_MODEL = 'LWM2M_MODEL',
PKCS_12 = 'PKCS_12',
JKS = 'JKS',
JS_MODULE = 'JS_MODULE'
JS_MODULE = 'JS_MODULE',
IMAGE = 'IMAGE'
}
export const ResourceTypeMIMETypes = new Map<ResourceType, string>(
@ -30,7 +31,8 @@ export const ResourceTypeMIMETypes = new Map<ResourceType, string>(
[ResourceType.LWM2M_MODEL, 'application/xml,text/xml'],
[ResourceType.PKCS_12, 'application/x-pkcs12'],
[ResourceType.JKS, 'application/x-java-keystore'],
[ResourceType.JS_MODULE, 'text/javascript,application/javascript']
[ResourceType.JS_MODULE, 'text/javascript,application/javascript'],
[ResourceType.IMAGE, 'image/*']
]
);
@ -48,7 +50,8 @@ export const ResourceTypeTranslationMap = new Map<ResourceType, string>(
[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']
[ResourceType.JS_MODULE, 'resource.type.js-module'],
[ResourceType.IMAGE, 'resource.type.image']
]
);
@ -58,15 +61,11 @@ export interface ResourceInfo extends Omit<BaseData<TbResourceId>, 'name' | 'lab
title?: string;
resourceType: ResourceType;
fileName: string;
mediaType: string;
link: string;
}
export interface Resource extends ResourceInfo {
base64Data: string;
name?: string;
}
// @ts-ignore FIXME
export interface Resources extends ResourceInfo {
data: Array<string>;
fileName: Array<string>;
}

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3622,7 +3622,8 @@
"jks": "JKS",
"js-module": "JS module",
"lwm2m-model": "LWM2M model",
"pkcs-12": "PKCS #12"
"pkcs-12": "PKCS #12",
"image": "Image"
}
},
"rulechain": {

Loading…
Cancel
Save