Remove tenantId from image link; remove link from resource
This commit is contained in:
		
							parent
							
								
									d06cfa9ae0
								
							
						
					
					
						commit
						7489b87fa5
					
				@ -28,6 +28,5 @@ $$
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,6 @@ import org.springframework.web.bind.annotation.RequestHeader;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.thingsboard.server.common.data.ResourceType;
 | 
			
		||||
import org.thingsboard.server.common.data.StringUtils;
 | 
			
		||||
@ -93,7 +92,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource/{resourceId}/download")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public ResponseEntity<ByteArrayResource> downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                              @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RESOURCE_ID, strResourceId);
 | 
			
		||||
@ -111,17 +109,10 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @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,
 | 
			
		||||
    @GetMapping(value = "/images/{resourceKey}", produces = "image/*")
 | 
			
		||||
    public ResponseEntity<ByteArrayResource> downloadImageIfChanged(@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));
 | 
			
		||||
        }
 | 
			
		||||
        TenantId tenantId = getTenantId();
 | 
			
		||||
        return downloadResourceIfChanged(ResourceType.IMAGE, etag,
 | 
			
		||||
                () -> resourceService.findResourceInfoByTenantIdAndKey(tenantId, ResourceType.IMAGE, resourceKey),
 | 
			
		||||
                () -> resourceService.findResourceByTenantIdAndKey(tenantId, ResourceType.IMAGE, resourceKey));
 | 
			
		||||
@ -130,7 +121,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
    @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")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public ResponseEntity<ByteArrayResource> downloadLwm2mResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                                            @PathVariable(RESOURCE_ID) String strResourceId,
 | 
			
		||||
                                                                            @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
 | 
			
		||||
@ -140,7 +130,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Download PKCS_12 Resource (downloadPkcs12ResourceIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/resource/pkcs12/{resourceId}/download", method = RequestMethod.GET, produces = "application/x-pkcs12")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public ResponseEntity<ByteArrayResource> downloadPkcs12ResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                                             @PathVariable(RESOURCE_ID) String strResourceId,
 | 
			
		||||
                                                                             @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
 | 
			
		||||
@ -151,7 +140,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource/jks/{resourceId}/download", produces = "application/x-java-keystore")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public ResponseEntity<ByteArrayResource> downloadJksResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                                          @PathVariable(RESOURCE_ID) String strResourceId,
 | 
			
		||||
                                                                          @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
 | 
			
		||||
@ -161,7 +149,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Download JS Resource (downloadJsResourceIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		||||
    @GetMapping(value = "/resource/js/{resourceId}/download", produces = "application/javascript")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public ResponseEntity<ByteArrayResource> downloadJsResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                                         @PathVariable(RESOURCE_ID) String strResourceId,
 | 
			
		||||
                                                                         @RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
 | 
			
		||||
@ -174,7 +161,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            produces = "application/json")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource/info/{resourceId}")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public TbResourceInfo getResourceInfoById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                              @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RESOURCE_ID, strResourceId);
 | 
			
		||||
@ -189,7 +175,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
    @Deprecated  // resource's data should be fetched with a download request
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource/{resourceId}")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public TbResource getResourceById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                      @PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RESOURCE_ID, strResourceId);
 | 
			
		||||
@ -211,7 +196,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            consumes = "application/json")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @PostMapping(value = "/resource")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public TbResourceInfo saveResource(@ApiParam(value = "A JSON value representing the Resource.")
 | 
			
		||||
                                       @RequestBody TbResource resource) throws Exception {
 | 
			
		||||
        resource.setTenantId(getTenantId());
 | 
			
		||||
@ -225,7 +209,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            produces = "application/json")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public PageData<TbResourceInfo> getResources(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
 | 
			
		||||
                                                 @RequestParam int pageSize,
 | 
			
		||||
                                                 @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
 | 
			
		||||
@ -257,7 +240,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            produces = "application/json")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource/lwm2m/page")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public List<LwM2mObject> getLwm2mListObjectsPage(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
 | 
			
		||||
                                                     @RequestParam int pageSize,
 | 
			
		||||
                                                     @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
 | 
			
		||||
@ -278,7 +260,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            produces = "application/json")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/resource/lwm2m")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public List<LwM2mObject> getLwm2mListObjects(@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES, required = true)
 | 
			
		||||
                                                 @RequestParam String sortOrder,
 | 
			
		||||
                                                 @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES, required = true)
 | 
			
		||||
@ -292,7 +273,6 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            notes = "Deletes the Resource. Referencing non-existing Resource Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
 | 
			
		||||
    @DeleteMapping(value = "/resource/{resourceId}")
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    public void deleteResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                               @PathVariable("resourceId") String strResourceId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RESOURCE_ID, strResourceId);
 | 
			
		||||
@ -323,6 +303,7 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: rate limits
 | 
			
		||||
        TbResource tbResource = resourceSupplier.get();
 | 
			
		||||
        checkEntity(getCurrentUser(), tbResource, Operation.READ);
 | 
			
		||||
 | 
			
		||||
@ -336,4 +317,5 @@ public class TbResourceController extends BaseController {
 | 
			
		||||
                .eTag(tbResource.getEtag())
 | 
			
		||||
                .body(resource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -30,7 +30,7 @@ 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.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.util.MediaTypeUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.util.ImageUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
 | 
			
		||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
 | 
			
		||||
import org.thingsboard.server.dao.resource.ResourceService;
 | 
			
		||||
@ -65,7 +65,7 @@ public class ImagesUpdater {
 | 
			
		||||
            throw new IllegalArgumentException("Image name is missing for " + imageKey + ". Please add it to names.json file");
 | 
			
		||||
        }
 | 
			
		||||
        byte[] imageData = Files.readAllBytes(imageFile);
 | 
			
		||||
        String mediaType = MediaTypeUtils.fileExtensionToMediaType("image", StringUtils.substringAfterLast(imageKey, "."));
 | 
			
		||||
        String mediaType = ImageUtils.fileExtensionToMediaType("image", StringUtils.substringAfterLast(imageKey, "."));
 | 
			
		||||
        try {
 | 
			
		||||
            saveImage(TenantId.SYS_TENANT_ID, imageName, imageKey, imageData, mediaType, null);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
@ -191,7 +191,7 @@ public class ImagesUpdater {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String imageMediaType = StringUtils.substringBetween(data, "data:", ";base64");
 | 
			
		||||
        String extension = MediaTypeUtils.mediaTypeToFileExtension(imageMediaType);
 | 
			
		||||
        String extension = ImageUtils.mediaTypeToFileExtension(imageMediaType);
 | 
			
		||||
        key += "." + extension;
 | 
			
		||||
 | 
			
		||||
        byte[] imageData = Base64.getDecoder().decode(base64Data);
 | 
			
		||||
@ -203,14 +203,16 @@ public class ImagesUpdater {
 | 
			
		||||
                             String existingImageQuery) {
 | 
			
		||||
        TbResourceInfo resourceInfo = resourceService.findResourceInfoByTenantIdAndKey(tenantId, ResourceType.IMAGE, key);
 | 
			
		||||
        if (resourceInfo == null && !tenantId.isSysTenantId() && existingImageQuery != null) {
 | 
			
		||||
            // TODO: need to search among tenant images too (custom widgets)
 | 
			
		||||
            List<TbResourceInfo> existingSystemImages = resourceService.findByTenantIdAndDataAndKeyStartingWith(TenantId.SYS_TENANT_ID, imageData, existingImageQuery);
 | 
			
		||||
            if (!existingSystemImages.isEmpty()) {
 | 
			
		||||
                resourceInfo = existingSystemImages.get(0);
 | 
			
		||||
                if (existingSystemImages.size() > 1) {
 | 
			
		||||
                    log.warn("Found more than one system image resources for key {}", existingImageQuery);
 | 
			
		||||
                }
 | 
			
		||||
                log.info("Using system image {} for {}", resourceInfo.getLink(), key);
 | 
			
		||||
                return resourceInfo.getLink();
 | 
			
		||||
                String link = resourceService.getResourceLink(resourceInfo);
 | 
			
		||||
                log.info("Using system image {} for {}", link, key);
 | 
			
		||||
                return link;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        TbResource resource;
 | 
			
		||||
@ -222,7 +224,7 @@ public class ImagesUpdater {
 | 
			
		||||
        } else if (tenantId.isSysTenantId()) {
 | 
			
		||||
            resource = new TbResource(resourceInfo);
 | 
			
		||||
        } else {
 | 
			
		||||
            return resourceInfo.getLink();
 | 
			
		||||
            return resourceService.getResourceLink(resourceInfo);
 | 
			
		||||
        }
 | 
			
		||||
        resource.setTitle(name);
 | 
			
		||||
        resource.setFileName(key);
 | 
			
		||||
@ -231,7 +233,7 @@ public class ImagesUpdater {
 | 
			
		||||
        resource = resourceService.saveResource(resource);
 | 
			
		||||
        log.info("[{}] {} image '{}' ({})", tenantId, resourceInfo == null ? "Created" : "Updated",
 | 
			
		||||
                resource.getTitle(), resource.getResourceKey());
 | 
			
		||||
        return resource.getLink();
 | 
			
		||||
        return resourceService.getResourceLink(resourceInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getText(JsonNode jsonNode, String field) {
 | 
			
		||||
 | 
			
		||||
@ -676,10 +676,9 @@ public class TbResourceControllerTest extends AbstractControllerTest {
 | 
			
		||||
        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())
 | 
			
		||||
        MockHttpServletResponse imageResponse = doGet(getImageLink(savedResource)).andExpect(status().isOk())
 | 
			
		||||
                .andReturn().getResponse();
 | 
			
		||||
        assertThat(imageResponse.getContentAsByteArray())
 | 
			
		||||
                .isEqualTo(download(savedResource.getId()))
 | 
			
		||||
@ -701,9 +700,9 @@ public class TbResourceControllerTest extends AbstractControllerTest {
 | 
			
		||||
        resource.setBase64Data(TEST_DATA);
 | 
			
		||||
 | 
			
		||||
        TbResource savedResource = save(resource);
 | 
			
		||||
        assertThat(savedResource.getLink()).isEqualTo("/api/images/" + tenantId + "/image.jpg");
 | 
			
		||||
        String imageLink = getImageLink(savedResource);
 | 
			
		||||
 | 
			
		||||
        MockHttpServletResponse imageResponse = doGet(savedResource.getLink()).andExpect(status().isOk())
 | 
			
		||||
        MockHttpServletResponse imageResponse = doGet(imageLink).andExpect(status().isOk())
 | 
			
		||||
                .andReturn().getResponse();
 | 
			
		||||
        assertThat(imageResponse.getContentAsByteArray())
 | 
			
		||||
                .isEqualTo(download(savedResource.getId()))
 | 
			
		||||
@ -711,7 +710,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
 | 
			
		||||
        assertThat(imageResponse.getContentType()).isEqualTo("image/jpeg");
 | 
			
		||||
 | 
			
		||||
        loginDifferentTenant();
 | 
			
		||||
        doGet(savedResource.getLink()).andExpect(status().isForbidden());
 | 
			
		||||
        doGet(imageLink).andExpect(status().isNotFound());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private TbResource save(TbResource tbResource) throws Exception {
 | 
			
		||||
@ -724,4 +723,8 @@ public class TbResourceControllerTest extends AbstractControllerTest {
 | 
			
		||||
                .andExpect(status().isOk())
 | 
			
		||||
                .andReturn().getResponse().getContentAsByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getImageLink(TbResourceInfo resourceInfo) {
 | 
			
		||||
        return "/api/images/" + (resourceInfo.getTenantId().isSysTenantId() ? "system/" : "") + resourceInfo.getResourceKey();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -62,4 +62,6 @@ public interface ResourceService extends EntityDaoService {
 | 
			
		||||
 | 
			
		||||
    List<TbResourceInfo> findByTenantIdAndDataAndKeyStartingWith(TenantId tenantId, byte[] data, String query);
 | 
			
		||||
 | 
			
		||||
    String getResourceLink(TbResourceInfo resourceInfo);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
package org.thingsboard.server.common.data;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import io.swagger.annotations.ApiModel;
 | 
			
		||||
import io.swagger.annotations.ApiModelProperty;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
@ -56,8 +57,7 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
 | 
			
		||||
    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;
 | 
			
		||||
    private ObjectNode descriptor;
 | 
			
		||||
 | 
			
		||||
    public TbResourceInfo() {
 | 
			
		||||
        super();
 | 
			
		||||
@ -77,7 +77,7 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
 | 
			
		||||
        this.etag = resourceInfo.etag;
 | 
			
		||||
        this.fileName = resourceInfo.fileName;
 | 
			
		||||
        this.mediaType = resourceInfo.mediaType;
 | 
			
		||||
        this.link = resourceInfo.link;
 | 
			
		||||
        this.descriptor = resourceInfo.descriptor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " +
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,96 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2023 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.common.data.util;
 | 
			
		||||
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.springframework.util.MimeType;
 | 
			
		||||
import org.springframework.util.MimeTypeUtils;
 | 
			
		||||
 | 
			
		||||
import javax.imageio.ImageIO;
 | 
			
		||||
import java.awt.image.BufferedImage;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class ImageUtils {
 | 
			
		||||
 | 
			
		||||
    private static final Map<String, String> mediaTypeMappings = Map.of(
 | 
			
		||||
            "jpeg", "jpg",
 | 
			
		||||
            "svg+xml", "svg"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    public static String mediaTypeToFileExtension(String mimeType) {
 | 
			
		||||
        String subtype = MimeTypeUtils.parseMimeType(mimeType).getSubtype();
 | 
			
		||||
        return mediaTypeMappings.getOrDefault(subtype, subtype);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String fileExtensionToMediaType(String type, String extension) {
 | 
			
		||||
        String subtype = mediaTypeMappings.entrySet().stream()
 | 
			
		||||
                .filter(mapping -> mapping.getValue().equals(extension))
 | 
			
		||||
                .map(Map.Entry::getKey).findFirst().orElse(extension);
 | 
			
		||||
        return new MimeType(type, subtype).toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ImageInfo processImage(byte[] imageData) throws IOException {
 | 
			
		||||
        BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
 | 
			
		||||
        ImageThumbnail thumbnail = getImageThumbnail(image, 250);
 | 
			
		||||
        return new ImageInfo(image.getWidth(), image.getHeight(), thumbnail);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ImageThumbnail getImageThumbnail(BufferedImage originalImage, int maxDimension) throws IOException {
 | 
			
		||||
        int originalWidth = originalImage.getWidth();
 | 
			
		||||
        int originalHeight = originalImage.getHeight();
 | 
			
		||||
        int thumbnailWidth;
 | 
			
		||||
        int thumbnailHeight;
 | 
			
		||||
        if (originalWidth <= maxDimension && originalHeight <= maxDimension) {
 | 
			
		||||
            thumbnailWidth = originalWidth;
 | 
			
		||||
            thumbnailHeight = originalHeight;
 | 
			
		||||
        } else {
 | 
			
		||||
            double aspectRatio = (double) originalWidth / originalHeight;
 | 
			
		||||
            if (originalWidth > originalHeight) {
 | 
			
		||||
                thumbnailWidth = maxDimension;
 | 
			
		||||
                thumbnailHeight = (int) (maxDimension / aspectRatio);
 | 
			
		||||
            } else {
 | 
			
		||||
                thumbnailWidth = (int) (maxDimension * aspectRatio);
 | 
			
		||||
                thumbnailHeight = maxDimension;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, BufferedImage.TYPE_INT_RGB);
 | 
			
		||||
        thumbnail.getGraphics().drawImage(originalImage, 0, 0, thumbnailWidth, thumbnailHeight, null);
 | 
			
		||||
        ByteArrayOutputStream os = new ByteArrayOutputStream();
 | 
			
		||||
        ImageIO.write(thumbnail, "ignored", os);
 | 
			
		||||
        return new ImageThumbnail(thumbnail.getWidth(), thumbnail.getHeight(), os.toByteArray());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Data
 | 
			
		||||
    public static class ImageInfo {
 | 
			
		||||
        private final int width;
 | 
			
		||||
        private final int height;
 | 
			
		||||
        private final ImageThumbnail thumbnail;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Data
 | 
			
		||||
    public static class ImageThumbnail {
 | 
			
		||||
        private final int width;
 | 
			
		||||
        private final int height;
 | 
			
		||||
        private final byte[] data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,45 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2023 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.common.data.util;
 | 
			
		||||
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.springframework.util.MimeType;
 | 
			
		||||
import org.springframework.util.MimeTypeUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class MediaTypeUtils {
 | 
			
		||||
 | 
			
		||||
    private static final Map<String, String> mappings = Map.of(
 | 
			
		||||
            "jpeg", "jpg",
 | 
			
		||||
            "svg+xml", "svg"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    public static String mediaTypeToFileExtension(String mimeType) {
 | 
			
		||||
        String subtype = MimeTypeUtils.parseMimeType(mimeType).getSubtype();
 | 
			
		||||
        return mappings.getOrDefault(subtype, subtype);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String fileExtensionToMediaType(String type, String extension) {
 | 
			
		||||
        String subtype = mappings.entrySet().stream()
 | 
			
		||||
                .filter(mapping -> mapping.getValue().equals(extension))
 | 
			
		||||
                .map(Map.Entry::getKey).findFirst().orElse(extension);
 | 
			
		||||
        return new MimeType(type, subtype).toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -500,7 +500,6 @@ public class ModelConstants {
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,6 @@ 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;
 | 
			
		||||
@ -74,9 +73,6 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> implements BaseE
 | 
			
		||||
    @Column(name = RESOURCE_MEDIA_TYPE_COLUMN)
 | 
			
		||||
    private String mediaType;
 | 
			
		||||
 | 
			
		||||
    @Column(name = RESOURCE_LINK_COLUMN)
 | 
			
		||||
    private String link;
 | 
			
		||||
 | 
			
		||||
    public TbResourceEntity() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,7 +92,6 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> implements BaseE
 | 
			
		||||
        this.data = resource.getData();
 | 
			
		||||
        this.etag = resource.getEtag();
 | 
			
		||||
        this.mediaType = resource.getMediaType();
 | 
			
		||||
        this.link = resource.getLink();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@ -112,7 +107,6 @@ public class TbResourceEntity extends BaseSqlEntity<TbResource> implements BaseE
 | 
			
		||||
        resource.setData(data);
 | 
			
		||||
        resource.setEtag(etag);
 | 
			
		||||
        resource.setMediaType(mediaType);
 | 
			
		||||
        resource.setLink(link);
 | 
			
		||||
        return resource;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ 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;
 | 
			
		||||
@ -70,9 +69,6 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
 | 
			
		||||
    @Column(name = RESOURCE_MEDIA_TYPE_COLUMN)
 | 
			
		||||
    private String mediaType;
 | 
			
		||||
 | 
			
		||||
    @Column(name = RESOURCE_LINK_COLUMN)
 | 
			
		||||
    private String link;
 | 
			
		||||
 | 
			
		||||
    public TbResourceInfoEntity() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -89,7 +85,6 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
 | 
			
		||||
        this.hashCode = resource.getEtag();
 | 
			
		||||
        this.fileName = resource.getFileName();
 | 
			
		||||
        this.mediaType = resource.getMediaType();
 | 
			
		||||
        this.link = resource.getLink();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@ -104,7 +99,6 @@ public class TbResourceInfoEntity extends BaseSqlEntity<TbResourceInfo> implemen
 | 
			
		||||
        resource.setEtag(hashCode);
 | 
			
		||||
        resource.setFileName(fileName);
 | 
			
		||||
        resource.setMediaType(mediaType);
 | 
			
		||||
        resource.setLink(link);
 | 
			
		||||
        return resource;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -77,11 +77,10 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
 | 
			
		||||
        }
 | 
			
		||||
        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()));
 | 
			
		||||
            if (resource.getResourceType() == ResourceType.IMAGE) {
 | 
			
		||||
                // FIXME: skip SVG (?)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            TbResource saved;
 | 
			
		||||
@ -226,6 +225,16 @@ public class BaseResourceService extends AbstractCachedEntityService<ResourceInf
 | 
			
		||||
        return resourceInfoDao.findByTenantIdAndEtagAndKeyStartingWith(tenantId, etag, query);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getResourceLink(TbResourceInfo resourceInfo) {
 | 
			
		||||
        String link = "/api/images/";
 | 
			
		||||
        if (resourceInfo.getTenantId().isSysTenantId()) {
 | 
			
		||||
            link += "system/";
 | 
			
		||||
        }
 | 
			
		||||
        link += resourceInfo.getResourceKey();
 | 
			
		||||
        return link;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String calculateEtag(byte[] data) {
 | 
			
		||||
        return Hashing.sha256().hashBytes(data).toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -717,7 +717,6 @@ CREATE TABLE IF NOT EXISTS resource (
 | 
			
		||||
    data bytea,
 | 
			
		||||
    etag varchar,
 | 
			
		||||
    media_type varchar(255),
 | 
			
		||||
    link varchar(255),
 | 
			
		||||
    CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user