Version control support for resources

This commit is contained in:
ViacheslavKlimov 2023-11-16 15:13:00 +02:00
parent 6eadef0d68
commit 72ae6eb708
25 changed files with 259 additions and 34 deletions

View File

@ -30,5 +30,6 @@ $$;
ALTER TABLE resource ALTER TABLE resource
ADD COLUMN IF NOT EXISTS descriptor varchar, ADD COLUMN IF NOT EXISTS descriptor varchar,
ADD COLUMN IF NOT EXISTS preview bytea; ADD COLUMN IF NOT EXISTS preview bytea;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS external_id uuid
-- RESOURCES UPDATE END -- RESOURCES UPDATE END

View File

@ -35,7 +35,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ImageDescriptor; import org.thingsboard.server.common.data.ImageDescriptor;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
@ -88,7 +87,7 @@ public class ImageController extends BaseController {
image.setResourceType(ResourceType.IMAGE); image.setResourceType(ResourceType.IMAGE);
ImageDescriptor descriptor = new ImageDescriptor(); ImageDescriptor descriptor = new ImageDescriptor();
descriptor.setMediaType(file.getContentType()); descriptor.setMediaType(file.getContentType());
image.setDescriptor(JacksonUtil.valueToTree(descriptor)); image.setDescriptorValue(descriptor);
image.setData(file.getBytes()); image.setData(file.getBytes());
return tbImageService.save(image, user); return tbImageService.save(image, user);
} }
@ -99,11 +98,12 @@ public class ImageController extends BaseController {
@PathVariable String key, @PathVariable String key,
@RequestPart MultipartFile file) throws Exception { @RequestPart MultipartFile file) throws Exception {
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.WRITE); TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.WRITE);
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
TbResource image = new TbResource(imageInfo); TbResource image = new TbResource(imageInfo);
image.setData(file.getBytes()); image.setData(file.getBytes());
descriptor.setMediaType(file.getContentType()); image.updateDescriptor(ImageDescriptor.class, descriptor -> {
descriptor.setMediaType(file.getContentType());
return descriptor;
});
return tbImageService.save(image, getCurrentUser()); return tbImageService.save(image, getCurrentUser());
} }

View File

@ -53,6 +53,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -226,6 +227,30 @@ public class TbResourceController extends BaseController {
} }
} }
@ApiOperation(value = "Get All Resource Infos (getAllResources)",
notes = "Returns a page of Resource Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/tenant")
public PageData<TbResourceInfo> getTenantResources(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = RESOURCE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TbResourceInfoFilter filter = TbResourceInfoFilter.builder()
.tenantId(getTenantId())
.resourceTypes(EnumSet.allOf(ResourceType.class))
.build();
return checkNotNull(resourceService.findTenantResourcesByTenantId(filter, pageLink));
}
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjectsPage)", @ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjectsPage)",
notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " + notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " +
PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,

View File

@ -118,6 +118,16 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
} }
} }
@Override
public <I extends EntityId> PageData<I> findEntitiesIdsByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) {
ExportableEntityDao<I, ?> dao = getExportableEntityDao(entityType);
if (dao != null) {
return dao.findIdsByTenantId(tenantId.getId(), pageLink);
} else {
return new PageData<>();
}
}
@Override @Override
public <I extends EntityId> I getExternalIdByInternal(I internalId) { public <I extends EntityId> I getExternalIdByInternal(I internalId) {
ExportableEntityDao<I, ?> dao = getExportableEntityDao(internalId.getEntityType()); ExportableEntityDao<I, ?> dao = getExportableEntityDao(internalId.getEntityType());

View File

@ -35,6 +35,8 @@ public interface ExportableEntitiesService {
<E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink); <E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink);
<I extends EntityId> PageData<I> findEntitiesIdsByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink);
<I extends EntityId> I getExternalIdByInternal(I internalId); <I extends EntityId> I getExternalIdByInternal(I internalId);
<I extends EntityId> void removeById(TenantId tenantId, I id); <I extends EntityId> void removeById(TenantId tenantId, I id);

View File

@ -0,0 +1,36 @@
/**
* 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.service.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Set;
@Service
@TbCoreComponent
public class ResourceExportService extends BaseEntityExportService<TbResourceId, TbResource, EntityExportData<TbResource>> {
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.TB_RESOURCE);
}
}

View File

@ -0,0 +1,83 @@
/**
* 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.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.User;
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.sync.ie.EntityExportData;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class ResourceImportService extends BaseEntityImportService<TbResourceId, TbResource, EntityExportData<TbResource>> {
private final ResourceService resourceService;
@Override
protected void setOwner(TenantId tenantId, TbResource resource, IdProvider idProvider) {
resource.setTenantId(tenantId);
}
@Override
protected TbResource prepare(EntitiesImportCtx ctx, TbResource resource, TbResource oldResource, EntityExportData<TbResource> exportData, IdProvider idProvider) {
return resource;
}
@Override
protected TbResource findExistingEntity(EntitiesImportCtx ctx, TbResource resource, IdProvider idProvider) {
TbResource existingResource = super.findExistingEntity(ctx, resource, idProvider);
if (existingResource == null && ctx.isFindExistingByName()) {
existingResource = resourceService.findResourceByTenantIdAndKey(ctx.getTenantId(), resource.getResourceType(), resource.getResourceKey());
}
return existingResource;
}
@Override
protected boolean compare(EntitiesImportCtx ctx, EntityExportData<TbResource> exportData, TbResource prepared, TbResource existing) {
return true;
}
@Override
protected TbResource deepCopy(TbResource resource) {
return new TbResource(resource);
}
@Override
protected TbResource saveOrUpdate(EntitiesImportCtx ctx, TbResource resource, EntityExportData<TbResource> exportData, IdProvider idProvider) {
return resourceService.saveResource(resource);
}
@Override
protected void onEntitySaved(User user, TbResource savedResource, TbResource oldResource) throws ThingsboardException {
super.onEntitySaved(user, savedResource, oldResource);
clusterService.onResourceChange(savedResource, null);
}
@Override
public EntityType getEntityType() {
return EntityType.TB_RESOURCE;
}
}

View File

@ -20,13 +20,9 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.sync.ie.WidgetTypeExportData; import org.thingsboard.server.common.data.sync.ie.WidgetTypeExportData;
import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;

View File

@ -208,10 +208,10 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
} }
if (config.isAllEntities()) { if (config.isAllEntities()) {
DaoUtil.processInBatches(pageLink -> exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink) DaoUtil.processInBatches(pageLink -> exportableEntitiesService.findEntitiesIdsByTenantId(ctx.getTenantId(), entityType, pageLink),
, 100, entity -> { 100, entityId -> {
try { try {
ctx.add(saveEntityData(ctx, entity.getId())); ctx.add(saveEntityData(ctx, entityId));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -34,7 +34,7 @@ public interface ResourceService extends EntityDaoService {
TbResource saveResource(TbResource resource, boolean doValidate); TbResource saveResource(TbResource resource, boolean doValidate);
TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceId); TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId); TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);

View File

@ -16,7 +16,6 @@
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ -34,10 +33,8 @@ public class TbResource extends TbResourceInfo {
private static final long serialVersionUID = 7379609705527272306L; private static final long serialVersionUID = 7379609705527272306L;
@JsonIgnore
private byte[] data; private byte[] data;
@JsonIgnore
private byte[] preview; private byte[] preview;
public TbResource() { public TbResource() {

View File

@ -19,7 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ -30,11 +29,13 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import java.util.function.UnaryOperator;
@ApiModel @ApiModel
@Slf4j @Slf4j
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, HasTenantId { public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, HasTenantId, ExportableEntity<TbResourceId> {
private static final long serialVersionUID = 7282664529021651736L; private static final long serialVersionUID = 7282664529021651736L;
@ -52,15 +53,17 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
private String resourceKey; private String resourceKey;
@ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String searchText; private String searchText;
@ApiModelProperty(position = 8, value = "Resource etag.", example = "33a64df551425fcc55e4d42a148795d9f25f89d4", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 8, value = "Resource etag.", example = "33a64df551425fcc55e4d42a148795d9f25f89d4", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String etag; private String etag;
@NoXss @NoXss
@Length(fieldName = "file name") @Length(fieldName = "file name")
@ApiModelProperty(position = 9, value = "Resource file name.", example = "19.xml", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 9, value = "Resource file name.", example = "19.xml", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String fileName; private String fileName;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private JsonNode descriptor; private JsonNode descriptor;
private TbResourceId externalId;
public TbResourceInfo() { public TbResourceInfo() {
super(); super();
} }
@ -78,13 +81,14 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
this.searchText = resourceInfo.searchText; this.searchText = resourceInfo.searchText;
this.etag = resourceInfo.etag; this.etag = resourceInfo.etag;
this.fileName = resourceInfo.fileName; this.fileName = resourceInfo.fileName;
this.descriptor = resourceInfo.descriptor; this.descriptor = resourceInfo.descriptor != null ? resourceInfo.descriptor.deepCopy() : null;
this.externalId = resourceInfo.externalId;
} }
@ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " + @ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " +
"Specify this field to update the Resource. " + "Specify this field to update the Resource. " +
"Referencing non-existing Resource Id will cause error. " + "Referencing non-existing Resource Id will cause error. " +
"Omit this field to create new Resource." ) "Omit this field to create new Resource.")
@Override @Override
public TbResourceId getId() { public TbResourceId getId() {
return super.getId(); return super.getId();
@ -97,7 +101,7 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
} }
@Override @Override
@JsonIgnore @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {
return title; return title;
} }
@ -107,9 +111,19 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
return searchText != null ? searchText : title; return searchText != null ? searchText : title;
} }
@JsonIgnore
public <T> T getDescriptor(Class<T> type) throws JsonProcessingException { public <T> T getDescriptor(Class<T> type) throws JsonProcessingException {
return descriptor != null ? mapper.treeToValue(descriptor, type) : null; return descriptor != null ? mapper.treeToValue(descriptor, type) : null;
} }
public <T> void updateDescriptor(Class<T> type, UnaryOperator<T> updater) throws JsonProcessingException {
T descriptor = getDescriptor(type);
descriptor = updater.apply(descriptor);
setDescriptorValue(descriptor);
}
@JsonIgnore
public void setDescriptorValue(Object value) {
this.descriptor = value != null ? mapper.valueToTree(value) : null;
}
} }

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
@ -56,7 +57,8 @@ import java.lang.annotation.Target;
@Type(name = "WIDGET_TYPE", value = WidgetTypeDetails.class), @Type(name = "WIDGET_TYPE", value = WidgetTypeDetails.class),
@Type(name = "NOTIFICATION_TEMPLATE", value = NotificationTemplate.class), @Type(name = "NOTIFICATION_TEMPLATE", value = NotificationTemplate.class),
@Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class), @Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class),
@Type(name = "NOTIFICATION_RULE", value = NotificationRule.class) @Type(name = "NOTIFICATION_RULE", value = NotificationRule.class),
@Type(name = "TB_RESOURCE", value = TbResource.class)
}) })
@JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true) @JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true)
public @interface JsonTbEntity { public @interface JsonTbEntity {

View File

@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import java.util.UUID; import java.util.UUID;
public interface ExportableEntityDao<I extends EntityId, T extends ExportableEntity<?>> extends Dao<T> { public interface ExportableEntityDao<I extends EntityId, T extends ExportableEntity<I>> extends Dao<T> {
T findByTenantIdAndExternalId(UUID tenantId, UUID externalId); T findByTenantIdAndExternalId(UUID tenantId, UUID externalId);
@ -30,6 +30,10 @@ public interface ExportableEntityDao<I extends EntityId, T extends ExportableEnt
PageData<T> findByTenantId(UUID tenantId, PageLink pageLink); PageData<T> findByTenantId(UUID tenantId, PageLink pageLink);
default PageData<I> findIdsByTenantId(UUID tenantId, PageLink pageLink) {
return findByTenantId(tenantId, pageLink).mapData(ExportableEntity::getId);
}
I getExternalIdByInternal(I internalId); I getExternalIdByInternal(I internalId);
} }

View File

@ -32,6 +32,7 @@ import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.EXTERNAL_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DATA_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DATA_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DESCRIPTOR_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DESCRIPTOR_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_ETAG_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_ETAG_COLUMN;
@ -82,6 +83,9 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> {
@Column(name = RESOURCE_PREVIEW_COLUMN) @Column(name = RESOURCE_PREVIEW_COLUMN)
private byte[] preview; private byte[] preview;
@Column(name = EXTERNAL_ID_PROPERTY)
private UUID externalId;
public TbResourceEntity() { public TbResourceEntity() {
} }
@ -102,6 +106,7 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> {
this.etag = resource.getEtag(); this.etag = resource.getEtag();
this.descriptor = resource.getDescriptor(); this.descriptor = resource.getDescriptor();
this.preview = resource.getPreview(); this.preview = resource.getPreview();
this.externalId = getUuid(resource.getExternalId());
} }
@Override @Override
@ -118,6 +123,7 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> {
resource.setEtag(etag); resource.setEtag(etag);
resource.setDescriptor(descriptor); resource.setDescriptor(descriptor);
resource.setPreview(preview); resource.setPreview(preview);
resource.setExternalId(getEntityId(externalId, TbResourceId::new));
return resource; return resource;
} }

View File

@ -33,6 +33,7 @@ import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.EXTERNAL_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DESCRIPTOR_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_DESCRIPTOR_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_ETAG_COLUMN; 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_FILE_NAME_COLUMN;
@ -75,6 +76,9 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
@Column(name = RESOURCE_DESCRIPTOR_COLUMN) @Column(name = RESOURCE_DESCRIPTOR_COLUMN)
private JsonNode descriptor; private JsonNode descriptor;
@Column(name = EXTERNAL_ID_PROPERTY)
private UUID externalId;
public TbResourceInfoEntity() { public TbResourceInfoEntity() {
} }
@ -91,6 +95,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
this.hashCode = resource.getEtag(); this.hashCode = resource.getEtag();
this.fileName = resource.getFileName(); this.fileName = resource.getFileName();
this.descriptor = resource.getDescriptor(); this.descriptor = resource.getDescriptor();
this.externalId = getUuid(resource.getExternalId());
} }
@Override @Override
@ -105,6 +110,7 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
resource.setEtag(hashCode); resource.setEtag(hashCode);
resource.setFileName(fileName); resource.setFileName(fileName);
resource.setDescriptor(descriptor); resource.setDescriptor(descriptor);
resource.setExternalId(getEntityId(externalId, TbResourceId::new));
return resource; return resource;
} }
} }

View File

@ -59,7 +59,7 @@ public class BaseImageService extends BaseResourceService implements ImageServic
Pair<ImageDescriptor, byte[]> result = processImage(image.getData(), descriptor); Pair<ImageDescriptor, byte[]> result = processImage(image.getData(), descriptor);
descriptor = result.getLeft(); descriptor = result.getLeft();
image.setEtag(descriptor.getEtag()); image.setEtag(descriptor.getEtag());
image.setDescriptor(JacksonUtil.valueToTree(descriptor)); image.setDescriptorValue(descriptor);
image.setPreview(result.getRight()); image.setPreview(result.getRight());
return new TbResourceInfo(doSaveResource(image)); return new TbResourceInfo(doSaveResource(image));

View File

@ -96,8 +96,7 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId())); publishEvictEvent(new ResourceInfoEvictEvent(tenantId, resource.getId()));
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("resource_unq_key")) { if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("resource_unq_key")) {
String field = ResourceType.LWM2M_MODEL.equals(resource.getResourceType()) ? "resourceKey" : "fileName"; throw new DataValidationException("Resource with such key already exists!");
throw new DataValidationException("Resource with such " + field + " already exists!");
} else { } else {
throw t; throw t;
} }
@ -202,6 +201,11 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
return Optional.ofNullable(findResourceInfoById(tenantId, new TbResourceId(entityId.getId()))); return Optional.ofNullable(findResourceInfoById(tenantId, new TbResourceId(entityId.getId())));
} }
@Override
public void deleteEntity(TenantId tenantId, EntityId id) {
deleteResource(tenantId, (TbResourceId) id);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.TB_RESOURCE; return EntityType.TB_RESOURCE;

View File

@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import org.thingsboard.server.dao.TenantEntityWithDataDao; import org.thingsboard.server.dao.TenantEntityWithDataDao;
import java.util.List; import java.util.List;
public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao { public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao, ExportableEntityDao<TbResourceId, TbResource> {
TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceId); TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceId);

View File

@ -109,6 +109,27 @@ public class JpaTbResourceDao extends JpaAbstractDao<TbResourceEntity, TbResourc
return resourceRepository.sumDataSizeByTenantId(tenantId.getId()); return resourceRepository.sumDataSizeByTenantId(tenantId.getId());
} }
@Override
public TbResource findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(resourceRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public PageData<TbResource> findByTenantId(UUID tenantId, PageLink pageLink) {
return findAllByTenantId(TenantId.fromUUID(tenantId), pageLink);
}
@Override
public PageData<TbResourceId> findIdsByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.pageToPageData(resourceRepository.findIdsByTenantId(tenantId, DaoUtil.toPageable(pageLink))
.map(TbResourceId::new));
}
@Override
public TbResourceId getExternalIdByInternal(TbResourceId internalId) {
return DaoUtil.toEntityId(resourceRepository.getExternalIdByInternal(internalId.getId()), TbResourceId::new);
}
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.TB_RESOURCE; return EntityType.TB_RESOURCE;

View File

@ -59,8 +59,8 @@ public interface TbResourceInfoRepository extends JpaRepository<TbResourceInfoEn
@Query(value = "SELECT r.resource_key FROM resource r WHERE r.tenant_id = :tenantId AND r.resource_type = :resourceType " + @Query(value = "SELECT r.resource_key FROM resource r WHERE r.tenant_id = :tenantId AND r.resource_type = :resourceType " +
"AND starts_with(r.resource_key, :resourceKeyStartsWith)", nativeQuery = true) "AND starts_with(r.resource_key, :resourceKeyStartsWith)", nativeQuery = true)
List<String> findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(@Param("tenantId") UUID tenantId, List<String> findKeysByTenantIdAndResourceTypeAndResourceKeyStartingWith(@Param("tenantId") UUID tenantId,
@Param("resourceType") String resourceType, @Param("resourceType") String resourceType,
@Param("resourceKeyStartsWith") String resourceKeyStartsWith); @Param("resourceKeyStartsWith") String resourceKeyStartsWith);
List<TbResourceInfoEntity> findByTenantIdAndHashCodeAndResourceKeyStartingWith(UUID tenantId, String hashCode, String query); List<TbResourceInfoEntity> findByTenantIdAndHashCodeAndResourceKeyStartingWith(UUID tenantId, String hashCode, String query);

View File

@ -20,12 +20,13 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.TbResourceEntity; import org.thingsboard.server.dao.model.sql.TbResourceEntity;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UUID> { public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UUID> , ExportableEntityRepository<TbResourceEntity> {
TbResourceEntity findByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey); TbResourceEntity findByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey);
@ -87,4 +88,10 @@ public interface TbResourceRepository extends JpaRepository<TbResourceEntity, UU
@Query(value = "SELECT COALESCE(preview, data) FROM resource WHERE id = :id", nativeQuery = true) @Query(value = "SELECT COALESCE(preview, data) FROM resource WHERE id = :id", nativeQuery = true)
byte[] getPreviewById(@Param("id") UUID id); byte[] getPreviewById(@Param("id") UUID id);
@Query("SELECT externalId FROM TbResourceInfoEntity WHERE id = :id")
UUID getExternalIdByInternal(@Param("id") UUID internalId);
@Query("SELECT id FROM TbResourceInfoEntity WHERE tenantId = :tenantId")
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
} }

View File

@ -121,7 +121,7 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
} }
@Override @Override
public PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) { public PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(deprecatedFilter); boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(deprecatedFilter);
boolean deprecatedFilterBool = DeprecatedFilter.DEPRECATED.equals(deprecatedFilter); boolean deprecatedFilterBool = DeprecatedFilter.DEPRECATED.equals(deprecatedFilter);
boolean widgetTypesEmpty = widgetTypes == null || widgetTypes.isEmpty(); boolean widgetTypesEmpty = widgetTypes == null || widgetTypes.isEmpty();
@ -204,6 +204,12 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
DaoUtil.toPageable(pageLink))); DaoUtil.toPageable(pageLink)));
} }
@Override
public PageData<WidgetTypeId> findIdsByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.pageToPageData(widgetTypeRepository.findIdsByTenantId(tenantId, DaoUtil.toPageable(pageLink))
.map(WidgetTypeId::new));
}
@Override @Override
public WidgetTypeId getExternalIdByInternal(WidgetTypeId internalId) { public WidgetTypeId getExternalIdByInternal(WidgetTypeId internalId) {
return Optional.ofNullable(widgetTypeRepository.getExternalIdById(internalId.getId())) return Optional.ofNullable(widgetTypeRepository.getExternalIdById(internalId.getId()))

View File

@ -76,7 +76,10 @@ public interface WidgetTypeRepository extends JpaRepository<WidgetTypeDetailsEnt
@Query("SELECT externalId FROM WidgetTypeDetailsEntity WHERE id = :id") @Query("SELECT externalId FROM WidgetTypeDetailsEntity WHERE id = :id")
UUID getExternalIdById(@Param("id") UUID id); UUID getExternalIdById(@Param("id") UUID id);
@Query("SELECT id FROM WidgetTypeEntity") @Query("SELECT id FROM WidgetTypeDetailsEntity")
Page<UUID> findAllIds(Pageable pageable); Page<UUID> findAllIds(Pageable pageable);
@Query("SELECT id FROM WidgetTypeDetailsEntity WHERE tenantId = :tenantId")
Page<UUID> findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable);
} }

View File

@ -718,6 +718,7 @@ CREATE TABLE IF NOT EXISTS resource (
etag varchar, etag varchar,
descriptor varchar, descriptor varchar,
preview bytea, preview bytea,
external_id uuid,
CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
); );