Commit 07aefe533a57b2c7ce9b78a6b4e619a225eb52b2

Authored by Andrew Shvayka
Committed by GitHub
2 parents a207e318 75fe679b

Merge pull request #4380 from YevhenBondarenko/develop/3.3-firmware

[WIP] feature firmware
Showing 83 changed files with 3422 additions and 149 deletions

Too many changes to show.

To preserve performance only 83 of 112 files are displayed.

  1 +--
  2 +-- Copyright © 2016-2021 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE TABLE IF NOT EXISTS resource (
  18 + id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY,
  19 + created_time bigint NOT NULL,
  20 + tenant_id uuid NOT NULL,
  21 + title varchar(255) NOT NULL,
  22 + resource_type varchar(32) NOT NULL,
  23 + resource_key varchar(255) NOT NULL,
  24 + search_text varchar(255),
  25 + file_name varchar(255) NOT NULL,
  26 + data varchar,
  27 + CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
  28 +);
  29 +
  30 +CREATE TABLE IF NOT EXISTS firmware (
  31 + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
  32 + created_time bigint NOT NULL,
  33 + tenant_id uuid NOT NULL,
  34 + title varchar(255) NOT NULL,
  35 + version varchar(255) NOT NULL,
  36 + file_name varchar(255),
  37 + content_type varchar(255),
  38 + checksum_algorithm varchar(32),
  39 + checksum varchar(1020),
  40 + data bytea,
  41 + additional_info varchar,
  42 + search_text varchar(255),
  43 + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
  44 +);
  45 +
  46 +ALTER TABLE device_profile
  47 + ADD COLUMN IF NOT EXISTS firmware_id uuid;
  48 +
  49 +ALTER TABLE device
  50 + ADD COLUMN IF NOT EXISTS firmware_id uuid;
  51 +
  52 +DO $$
  53 + BEGIN
  54 + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
  55 + ALTER TABLE device_profile
  56 + ADD CONSTRAINT fk_firmware_device_profile
  57 + FOREIGN KEY (firmware_id) REFERENCES firmware(id);
  58 + END IF;
  59 +
  60 + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
  61 + ALTER TABLE device
  62 + ADD CONSTRAINT fk_firmware_device
  63 + FOREIGN KEY (firmware_id) REFERENCES firmware(id);
  64 + END IF;
  65 + END;
  66 +$$;
@@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
24 import org.apache.commons.lang3.StringUtils; 24 import org.apache.commons.lang3.StringUtils;
25 import org.springframework.beans.factory.annotation.Autowired; 25 import org.springframework.beans.factory.annotation.Autowired;
26 import org.springframework.beans.factory.annotation.Value; 26 import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.http.MediaType;
27 import org.springframework.security.core.Authentication; 28 import org.springframework.security.core.Authentication;
28 import org.springframework.security.core.context.SecurityContextHolder; 29 import org.springframework.security.core.context.SecurityContextHolder;
29 import org.springframework.web.bind.annotation.ExceptionHandler; 30 import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -37,6 +38,8 @@ import org.thingsboard.server.common.data.DeviceProfile; @@ -37,6 +38,8 @@ import org.thingsboard.server.common.data.DeviceProfile;
37 import org.thingsboard.server.common.data.EntityType; 38 import org.thingsboard.server.common.data.EntityType;
38 import org.thingsboard.server.common.data.EntityView; 39 import org.thingsboard.server.common.data.EntityView;
39 import org.thingsboard.server.common.data.EntityViewInfo; 40 import org.thingsboard.server.common.data.EntityViewInfo;
  41 +import org.thingsboard.server.common.data.Firmware;
  42 +import org.thingsboard.server.common.data.FirmwareInfo;
40 import org.thingsboard.server.common.data.HasName; 43 import org.thingsboard.server.common.data.HasName;
41 import org.thingsboard.server.common.data.HasTenantId; 44 import org.thingsboard.server.common.data.HasTenantId;
42 import org.thingsboard.server.common.data.TbResourceInfo; 45 import org.thingsboard.server.common.data.TbResourceInfo;
@@ -61,6 +64,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -61,6 +64,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
61 import org.thingsboard.server.common.data.id.EntityId; 64 import org.thingsboard.server.common.data.id.EntityId;
62 import org.thingsboard.server.common.data.id.EntityIdFactory; 65 import org.thingsboard.server.common.data.id.EntityIdFactory;
63 import org.thingsboard.server.common.data.id.EntityViewId; 66 import org.thingsboard.server.common.data.id.EntityViewId;
  67 +import org.thingsboard.server.common.data.id.FirmwareId;
64 import org.thingsboard.server.common.data.id.TbResourceId; 68 import org.thingsboard.server.common.data.id.TbResourceId;
65 import org.thingsboard.server.common.data.id.RuleChainId; 69 import org.thingsboard.server.common.data.id.RuleChainId;
66 import org.thingsboard.server.common.data.id.RuleNodeId; 70 import org.thingsboard.server.common.data.id.RuleNodeId;
@@ -97,6 +101,7 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -97,6 +101,7 @@ import org.thingsboard.server.dao.device.DeviceService;
97 import org.thingsboard.server.dao.entityview.EntityViewService; 101 import org.thingsboard.server.dao.entityview.EntityViewService;
98 import org.thingsboard.server.dao.exception.DataValidationException; 102 import org.thingsboard.server.dao.exception.DataValidationException;
99 import org.thingsboard.server.dao.exception.IncorrectParameterException; 103 import org.thingsboard.server.dao.exception.IncorrectParameterException;
  104 +import org.thingsboard.server.dao.firmware.FirmwareService;
100 import org.thingsboard.server.dao.model.ModelConstants; 105 import org.thingsboard.server.dao.model.ModelConstants;
101 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; 106 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
102 import org.thingsboard.server.dao.oauth2.OAuth2Service; 107 import org.thingsboard.server.dao.oauth2.OAuth2Service;
@@ -114,6 +119,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; @@ -114,6 +119,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
114 import org.thingsboard.server.queue.provider.TbQueueProducerProvider; 119 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
115 import org.thingsboard.server.queue.util.TbCoreComponent; 120 import org.thingsboard.server.queue.util.TbCoreComponent;
116 import org.thingsboard.server.service.component.ComponentDiscoveryService; 121 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  122 +import org.thingsboard.server.service.firmware.FirmwareStateService;
117 import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; 123 import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
118 import org.thingsboard.server.service.profile.TbDeviceProfileCache; 124 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
119 import org.thingsboard.server.service.queue.TbClusterService; 125 import org.thingsboard.server.service.queue.TbClusterService;
@@ -233,6 +239,12 @@ public abstract class BaseController { @@ -233,6 +239,12 @@ public abstract class BaseController {
233 protected TbResourceService resourceService; 239 protected TbResourceService resourceService;
234 240
235 @Autowired 241 @Autowired
  242 + protected FirmwareService firmwareService;
  243 +
  244 + @Autowired
  245 + protected FirmwareStateService firmwareStateService;
  246 +
  247 + @Autowired
236 protected TbQueueProducerProvider producerProvider; 248 protected TbQueueProducerProvider producerProvider;
237 249
238 @Autowired 250 @Autowired
@@ -470,6 +482,9 @@ public abstract class BaseController { @@ -470,6 +482,9 @@ public abstract class BaseController {
470 case TB_RESOURCE: 482 case TB_RESOURCE:
471 checkResourceId(new TbResourceId(entityId.getId()), operation); 483 checkResourceId(new TbResourceId(entityId.getId()), operation);
472 return; 484 return;
  485 + case FIRMWARE:
  486 + checkFirmwareId(new FirmwareId(entityId.getId()), operation);
  487 + return;
473 default: 488 default:
474 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); 489 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
475 } 490 }
@@ -701,6 +716,30 @@ public abstract class BaseController { @@ -701,6 +716,30 @@ public abstract class BaseController {
701 } 716 }
702 } 717 }
703 718
  719 + Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException {
  720 + try {
  721 + validateId(firmwareId, "Incorrect firmwareId " + firmwareId);
  722 + Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId);
  723 + checkNotNull(firmware);
  724 + accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware);
  725 + return firmware;
  726 + } catch (Exception e) {
  727 + throw handleException(e, false);
  728 + }
  729 + }
  730 +
  731 + FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException {
  732 + try {
  733 + validateId(firmwareId, "Incorrect firmwareId " + firmwareId);
  734 + FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId);
  735 + checkNotNull(firmwareInfo);
  736 + accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo);
  737 + return firmwareInfo;
  738 + } catch (Exception e) {
  739 + throw handleException(e, false);
  740 + }
  741 + }
  742 +
704 @SuppressWarnings("unchecked") 743 @SuppressWarnings("unchecked")
705 protected <I extends EntityId> I emptyId(EntityType entityType) { 744 protected <I extends EntityId> I emptyId(EntityType entityType) {
706 return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); 745 return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
@@ -924,4 +963,11 @@ public abstract class BaseController { @@ -924,4 +963,11 @@ public abstract class BaseController {
924 } 963 }
925 } 964 }
926 965
  966 + protected MediaType parseMediaType(String contentType) {
  967 + try {
  968 + return MediaType.parseMediaType(contentType);
  969 + } catch (Exception e) {
  970 + return MediaType.APPLICATION_OCTET_STREAM;
  971 + }
  972 + }
927 } 973 }
@@ -70,6 +70,7 @@ import javax.annotation.Nullable; @@ -70,6 +70,7 @@ import javax.annotation.Nullable;
70 import java.io.IOException; 70 import java.io.IOException;
71 import java.util.ArrayList; 71 import java.util.ArrayList;
72 import java.util.List; 72 import java.util.List;
  73 +import java.util.Objects;
73 import java.util.stream.Collectors; 74 import java.util.stream.Collectors;
74 75
75 @RestController 76 @RestController
@@ -117,13 +118,25 @@ public class DeviceController extends BaseController { @@ -117,13 +118,25 @@ public class DeviceController extends BaseController {
117 118
118 checkEntity(device.getId(), device, Resource.DEVICE); 119 checkEntity(device.getId(), device, Resource.DEVICE);
119 120
  121 + boolean created = device.getId() == null;
  122 +
  123 + boolean isFirmwareChanged = false;
  124 +
  125 + if (created) {
  126 + isFirmwareChanged = true;
  127 + } else {
  128 + Device oldDevice = deviceService.findDeviceById(getTenantId(), device.getId());
  129 + if (!Objects.equals(device.getFirmwareId(), oldDevice.getFirmwareId()) || !oldDevice.getDeviceProfileId().equals(device.getDeviceProfileId())) {
  130 + isFirmwareChanged = true;
  131 + }
  132 + }
  133 +
120 Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); 134 Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
121 135
122 tbClusterService.onDeviceChange(savedDevice, null); 136 tbClusterService.onDeviceChange(savedDevice, null);
123 tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(), 137 tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
124 savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); 138 savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
125 - tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),  
126 - device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); 139 + tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
127 140
128 logEntityAction(savedDevice.getId(), savedDevice, 141 logEntityAction(savedDevice.getId(), savedDevice,
129 savedDevice.getCustomerId(), 142 savedDevice.getCustomerId(),
@@ -134,12 +147,19 @@ public class DeviceController extends BaseController { @@ -134,12 +147,19 @@ public class DeviceController extends BaseController {
134 } else { 147 } else {
135 deviceStateService.onDeviceUpdated(savedDevice); 148 deviceStateService.onDeviceUpdated(savedDevice);
136 } 149 }
  150 +
  151 + if (isFirmwareChanged) {
  152 + firmwareStateService.update(savedDevice, created);
  153 + }
  154 +
137 return savedDevice; 155 return savedDevice;
138 - } catch (Exception e) { 156 + } catch (
  157 + Exception e) {
139 logEntityAction(emptyId(EntityType.DEVICE), device, 158 logEntityAction(emptyId(EntityType.DEVICE), device,
140 null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); 159 null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
141 throw handleException(e); 160 throw handleException(e);
142 } 161 }
  162 +
143 } 163 }
144 164
145 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 165 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@@ -43,6 +43,7 @@ import org.thingsboard.server.service.security.permission.Operation; @@ -43,6 +43,7 @@ import org.thingsboard.server.service.security.permission.Operation;
43 import org.thingsboard.server.service.security.permission.Resource; 43 import org.thingsboard.server.service.security.permission.Resource;
44 44
45 import java.util.List; 45 import java.util.List;
  46 +import java.util.Objects;
46 import java.util.UUID; 47 import java.util.UUID;
47 48
48 @RestController 49 @RestController
@@ -143,6 +144,15 @@ public class DeviceProfileController extends BaseController { @@ -143,6 +144,15 @@ public class DeviceProfileController extends BaseController {
143 144
144 checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); 145 checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE);
145 146
  147 + boolean isFirmwareChanged = false;
  148 +
  149 + if (!created) {
  150 + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId());
  151 + if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) {
  152 + isFirmwareChanged = true;
  153 + }
  154 + }
  155 +
146 DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); 156 DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
147 157
148 tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); 158 tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
@@ -153,6 +163,10 @@ public class DeviceProfileController extends BaseController { @@ -153,6 +163,10 @@ public class DeviceProfileController extends BaseController {
153 null, 163 null,
154 created ? ActionType.ADDED : ActionType.UPDATED, null); 164 created ? ActionType.ADDED : ActionType.UPDATED, null);
155 165
  166 + if (isFirmwareChanged) {
  167 + firmwareStateService.update(savedDeviceProfile);
  168 + }
  169 +
156 return savedDeviceProfile; 170 return savedDeviceProfile;
157 } catch (Exception e) { 171 } catch (Exception e) {
158 logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile, 172 logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile,
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.google.common.hash.Hashing;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.commons.lang3.StringUtils;
  21 +import org.springframework.core.io.ByteArrayResource;
  22 +import org.springframework.http.HttpHeaders;
  23 +import org.springframework.http.ResponseEntity;
  24 +import org.springframework.security.access.prepost.PreAuthorize;
  25 +import org.springframework.web.bind.annotation.PathVariable;
  26 +import org.springframework.web.bind.annotation.RequestBody;
  27 +import org.springframework.web.bind.annotation.RequestMapping;
  28 +import org.springframework.web.bind.annotation.RequestMethod;
  29 +import org.springframework.web.bind.annotation.RequestParam;
  30 +import org.springframework.web.bind.annotation.ResponseBody;
  31 +import org.springframework.web.bind.annotation.RestController;
  32 +import org.springframework.web.multipart.MultipartFile;
  33 +import org.thingsboard.server.common.data.Firmware;
  34 +import org.thingsboard.server.common.data.FirmwareInfo;
  35 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  36 +import org.thingsboard.server.common.data.id.FirmwareId;
  37 +import org.thingsboard.server.common.data.page.PageData;
  38 +import org.thingsboard.server.common.data.page.PageLink;
  39 +import org.thingsboard.server.queue.util.TbCoreComponent;
  40 +import org.thingsboard.server.service.security.permission.Operation;
  41 +import org.thingsboard.server.service.security.permission.Resource;
  42 +
  43 +import java.nio.ByteBuffer;
  44 +
  45 +@Slf4j
  46 +@RestController
  47 +@TbCoreComponent
  48 +@RequestMapping("/api")
  49 +public class FirmwareController extends BaseController {
  50 +
  51 + public static final String FIRMWARE_ID = "firmwareId";
  52 +
  53 + @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')")
  54 + @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET)
  55 + @ResponseBody
  56 + public ResponseEntity<org.springframework.core.io.Resource> downloadFirmware(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
  57 + checkParameter(FIRMWARE_ID, strFirmwareId);
  58 + try {
  59 + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
  60 + Firmware firmware = checkFirmwareId(firmwareId, Operation.READ);
  61 +
  62 + ByteArrayResource resource = new ByteArrayResource(firmware.getData().array());
  63 + return ResponseEntity.ok()
  64 + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmware.getFileName())
  65 + .header("x-filename", firmware.getFileName())
  66 + .contentLength(resource.contentLength())
  67 + .contentType(parseMediaType(firmware.getContentType()))
  68 + .body(resource);
  69 + } catch (Exception e) {
  70 + throw handleException(e);
  71 + }
  72 + }
  73 +
  74 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  75 + @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET)
  76 + @ResponseBody
  77 + public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
  78 + checkParameter(FIRMWARE_ID, strFirmwareId);
  79 + try {
  80 + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
  81 + return checkFirmwareInfoId(firmwareId, Operation.READ);
  82 + } catch (Exception e) {
  83 + throw handleException(e);
  84 + }
  85 + }
  86 +
  87 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  88 + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET)
  89 + @ResponseBody
  90 + public Firmware getFirmwareById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
  91 + checkParameter(FIRMWARE_ID, strFirmwareId);
  92 + try {
  93 + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
  94 + return checkFirmwareId(firmwareId, Operation.READ);
  95 + } catch (Exception e) {
  96 + throw handleException(e);
  97 + }
  98 + }
  99 +
  100 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  101 + @RequestMapping(value = "/firmware", method = RequestMethod.POST)
  102 + @ResponseBody
  103 + public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException {
  104 + firmwareInfo.setTenantId(getTenantId());
  105 + checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE);
  106 + try {
  107 + return firmwareService.saveFirmwareInfo(firmwareInfo);
  108 + } catch (Exception e) {
  109 + throw handleException(e);
  110 + }
  111 + }
  112 +
  113 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  114 + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST)
  115 + @ResponseBody
  116 + public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId,
  117 + @RequestParam(required = false) String checksum,
  118 + @RequestParam(required = false) String checksumAlgorithm,
  119 + @RequestBody MultipartFile file) throws ThingsboardException {
  120 + checkParameter(FIRMWARE_ID, strFirmwareId);
  121 + try {
  122 + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
  123 + FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ);
  124 +
  125 + Firmware firmware = new Firmware(firmwareId);
  126 + firmware.setCreatedTime(info.getCreatedTime());
  127 + firmware.setTenantId(getTenantId());
  128 + firmware.setTitle(info.getTitle());
  129 + firmware.setVersion(info.getVersion());
  130 + firmware.setAdditionalInfo(info.getAdditionalInfo());
  131 +
  132 + if (StringUtils.isEmpty(checksumAlgorithm)) {
  133 + checksumAlgorithm = "sha256";
  134 + checksum = Hashing.sha256().hashBytes(file.getBytes()).toString();
  135 + }
  136 +
  137 + firmware.setChecksumAlgorithm(checksumAlgorithm);
  138 + firmware.setChecksum(checksum);
  139 + firmware.setFileName(file.getOriginalFilename());
  140 + firmware.setContentType(file.getContentType());
  141 + firmware.setData(ByteBuffer.wrap(file.getBytes()));
  142 + return firmwareService.saveFirmware(firmware);
  143 + } catch (Exception e) {
  144 + throw handleException(e);
  145 + }
  146 + }
  147 +
  148 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  149 + @RequestMapping(value = "/firmwares", method = RequestMethod.GET)
  150 + @ResponseBody
  151 + public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,
  152 + @RequestParam int page,
  153 + @RequestParam(required = false) String textSearch,
  154 + @RequestParam(required = false) String sortProperty,
  155 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  156 + try {
  157 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  158 + return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink));
  159 + } catch (Exception e) {
  160 + throw handleException(e);
  161 + }
  162 + }
  163 +
  164 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  165 + @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET)
  166 + @ResponseBody
  167 + public PageData<FirmwareInfo> getFirmwares(@PathVariable("hasData") boolean hasData,
  168 + @RequestParam int pageSize,
  169 + @RequestParam int page,
  170 + @RequestParam(required = false) String textSearch,
  171 + @RequestParam(required = false) String sortProperty,
  172 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  173 + try {
  174 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  175 + return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndHasData(getTenantId(), hasData, pageLink));
  176 + } catch (Exception e) {
  177 + throw handleException(e);
  178 + }
  179 + }
  180 +
  181 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  182 + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE)
  183 + @ResponseBody
  184 + public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {
  185 + checkParameter(FIRMWARE_ID, strFirmwareId);
  186 + try {
  187 + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
  188 + checkFirmwareInfoId(firmwareId, Operation.DELETE);
  189 + firmwareService.deleteFirmware(getTenantId(), firmwareId);
  190 + } catch (Exception e) {
  191 + throw handleException(e);
  192 + }
  193 + }
  194 +
  195 +}
@@ -55,12 +55,6 @@ public class TbResourceController extends BaseController { @@ -55,12 +55,6 @@ public class TbResourceController extends BaseController {
55 55
56 public static final String RESOURCE_ID = "resourceId"; 56 public static final String RESOURCE_ID = "resourceId";
57 57
58 - private final TbResourceService resourceService;  
59 -  
60 - public TbResourceController(TbResourceService resourceService) {  
61 - this.resourceService = resourceService;  
62 - }  
63 -  
64 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") 58 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
65 @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) 59 @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
66 @ResponseBody 60 @ResponseBody
@@ -187,7 +181,7 @@ public class TbResourceController extends BaseController { @@ -187,7 +181,7 @@ public class TbResourceController extends BaseController {
187 @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE) 181 @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE)
188 @ResponseBody 182 @ResponseBody
189 public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException { 183 public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException {
190 - checkParameter("resourceId", strResourceId); 184 + checkParameter(RESOURCE_ID, strResourceId);
191 try { 185 try {
192 TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); 186 TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
193 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE); 187 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);
@@ -230,7 +230,6 @@ public class ThingsboardInstallService { @@ -230,7 +230,6 @@ public class ThingsboardInstallService {
230 systemDataLoaderService.createAdminSettings(); 230 systemDataLoaderService.createAdminSettings();
231 systemDataLoaderService.loadSystemWidgets(); 231 systemDataLoaderService.loadSystemWidgets();
232 systemDataLoaderService.createOAuth2Templates(); 232 systemDataLoaderService.createOAuth2Templates();
233 - systemDataLoaderService.loadSystemLwm2mResources();  
234 // systemDataLoaderService.loadSystemPlugins(); 233 // systemDataLoaderService.loadSystemPlugins();
235 // systemDataLoaderService.loadSystemRules(); 234 // systemDataLoaderService.loadSystemRules();
236 235
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.firmware;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.stereotype.Service;
  21 +import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
  22 +import org.thingsboard.server.common.data.DataConstants;
  23 +import org.thingsboard.server.common.data.Device;
  24 +import org.thingsboard.server.common.data.DeviceProfile;
  25 +import org.thingsboard.server.common.data.Firmware;
  26 +import org.thingsboard.server.common.data.id.DeviceId;
  27 +import org.thingsboard.server.common.data.id.FirmwareId;
  28 +import org.thingsboard.server.common.data.id.TenantId;
  29 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  30 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  31 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  32 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  33 +import org.thingsboard.server.common.data.kv.StringDataEntry;
  34 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  35 +import org.thingsboard.server.common.data.page.PageData;
  36 +import org.thingsboard.server.common.data.page.PageLink;
  37 +import org.thingsboard.server.dao.device.DeviceProfileService;
  38 +import org.thingsboard.server.dao.device.DeviceService;
  39 +import org.thingsboard.server.dao.firmware.FirmwareService;
  40 +import org.thingsboard.server.queue.util.TbCoreComponent;
  41 +
  42 +import javax.annotation.Nullable;
  43 +import java.util.ArrayList;
  44 +import java.util.Arrays;
  45 +import java.util.List;
  46 +import java.util.function.Consumer;
  47 +
  48 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM;
  49 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM_ALGORITHM;
  50 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_SIZE;
  51 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_TITLE;
  52 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_VERSION;
  53 +
  54 +@Slf4j
  55 +@Service
  56 +@TbCoreComponent
  57 +public class DefaultFirmwareStateService implements FirmwareStateService {
  58 +
  59 + private final FirmwareService firmwareService;
  60 + private final DeviceService deviceService;
  61 + private final DeviceProfileService deviceProfileService;
  62 + private final RuleEngineTelemetryService telemetryService;
  63 +
  64 + public DefaultFirmwareStateService(FirmwareService firmwareService, DeviceService deviceService, DeviceProfileService deviceProfileService, RuleEngineTelemetryService telemetryService) {
  65 + this.firmwareService = firmwareService;
  66 + this.deviceService = deviceService;
  67 + this.deviceProfileService = deviceProfileService;
  68 + this.telemetryService = telemetryService;
  69 + }
  70 +
  71 + @Override
  72 + public void update(Device device, boolean created) {
  73 + FirmwareId firmwareId = device.getFirmwareId();
  74 + if (firmwareId == null) {
  75 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
  76 + firmwareId = deviceProfile.getFirmwareId();
  77 + }
  78 +
  79 + if (firmwareId == null) {
  80 + if (!created) {
  81 + remove(device);
  82 + }
  83 + } else {
  84 + update(device, firmwareService.findFirmwareById(device.getTenantId(), firmwareId), System.currentTimeMillis());
  85 + }
  86 + }
  87 +
  88 + @Override
  89 + public void update(DeviceProfile deviceProfile) {
  90 + TenantId tenantId = deviceProfile.getTenantId();
  91 +
  92 + Consumer<Device> updateConsumer;
  93 + if (deviceProfile.getFirmwareId() != null) {
  94 + Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId());
  95 + long ts = System.currentTimeMillis();
  96 + updateConsumer = d -> update(d, firmware, ts);
  97 + } else {
  98 + updateConsumer = this::remove;
  99 + }
  100 +
  101 + PageLink pageLink = new PageLink(100);
  102 + PageData<Device> pageData;
  103 + do {
  104 + //TODO: create a query which will return devices without firmware
  105 + pageData = deviceService.findDevicesByTenantIdAndType(tenantId, deviceProfile.getName(), pageLink);
  106 +
  107 + pageData.getData().stream().filter(d -> d.getFirmwareId() == null).forEach(updateConsumer);
  108 +
  109 + if (pageData.hasNext()) {
  110 + pageLink = pageLink.nextPageLink();
  111 + }
  112 + } while (pageData.hasNext());
  113 + }
  114 +
  115 + private void update(Device device, Firmware firmware, long ts) {
  116 + TenantId tenantId = device.getTenantId();
  117 + DeviceId deviceId = device.getId();
  118 +
  119 + List<TsKvEntry> telemetry = new ArrayList<>();
  120 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle())));
  121 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion())));
  122 +
  123 + telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
  124 + @Override
  125 + public void onSuccess(@Nullable Void tmp) {
  126 + log.trace("[{}] Success save telemetry with target firmware for device!", deviceId);
  127 + }
  128 +
  129 + @Override
  130 + public void onFailure(Throwable t) {
  131 + log.error("[{}] Failed to save telemetry with target firmware for device!", deviceId, t);
  132 + }
  133 + });
  134 +
  135 + List<AttributeKvEntry> attributes = new ArrayList<>();
  136 +
  137 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle())));
  138 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion())));
  139 +
  140 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, (long) firmware.getData().array().length)));
  141 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm())));
  142 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum())));
  143 + telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
  144 + @Override
  145 + public void onSuccess(@Nullable Void tmp) {
  146 + log.trace("[{}] Success save attributes with target firmware!", deviceId);
  147 + }
  148 +
  149 + @Override
  150 + public void onFailure(Throwable t) {
  151 + log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
  152 + }
  153 + });
  154 + }
  155 +
  156 + private void remove(Device device) {
  157 + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE,
  158 + Arrays.asList(FIRMWARE_TITLE, FIRMWARE_VERSION, FIRMWARE_SIZE, FIRMWARE_CHECKSUM_ALGORITHM, FIRMWARE_CHECKSUM),
  159 + new FutureCallback<>() {
  160 + @Override
  161 + public void onSuccess(@Nullable Void tmp) {
  162 + log.trace("[{}] Success remove target firmware attributes!", device.getId());
  163 + }
  164 +
  165 + @Override
  166 + public void onFailure(Throwable t) {
  167 + log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t);
  168 + }
  169 + });
  170 + }
  171 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.firmware;
  17 +
  18 +import org.thingsboard.server.common.data.Device;
  19 +import org.thingsboard.server.common.data.DeviceProfile;
  20 +
  21 +public interface FirmwareStateService {
  22 +
  23 + void update(Device device, boolean created);
  24 +
  25 + void update(DeviceProfile deviceProfile);
  26 +
  27 +}
@@ -58,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink; @@ -58,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink;
58 import org.thingsboard.server.common.data.query.BooleanFilterPredicate; 58 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
59 import org.thingsboard.server.common.data.query.DynamicValue; 59 import org.thingsboard.server.common.data.query.DynamicValue;
60 import org.thingsboard.server.common.data.query.DynamicValueSourceType; 60 import org.thingsboard.server.common.data.query.DynamicValueSourceType;
61 -import org.thingsboard.server.common.data.query.EntityKey;  
62 -import org.thingsboard.server.common.data.query.EntityKeyType;  
63 import org.thingsboard.server.common.data.query.EntityKeyValueType; 61 import org.thingsboard.server.common.data.query.EntityKeyValueType;
64 import org.thingsboard.server.common.data.query.FilterPredicateValue; 62 import org.thingsboard.server.common.data.query.FilterPredicateValue;
65 -import org.thingsboard.server.common.data.query.KeyFilter;  
66 import org.thingsboard.server.common.data.query.NumericFilterPredicate; 63 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
67 import org.thingsboard.server.common.data.security.Authority; 64 import org.thingsboard.server.common.data.security.Authority;
68 import org.thingsboard.server.common.data.security.DeviceCredentials; 65 import org.thingsboard.server.common.data.security.DeviceCredentials;
@@ -445,11 +442,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -445,11 +442,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
445 installScripts.loadSystemWidgets(); 442 installScripts.loadSystemWidgets();
446 } 443 }
447 444
448 - @Override  
449 - public void loadSystemLwm2mResources() throws Exception {  
450 - installScripts.loadSystemLwm2mResources();  
451 - }  
452 -  
453 private User createUser(Authority authority, 445 private User createUser(Authority authority,
454 TenantId tenantId, 446 TenantId tenantId,
455 CustomerId customerId, 447 CustomerId customerId,
@@ -22,8 +22,6 @@ import org.springframework.beans.factory.annotation.Value; @@ -22,8 +22,6 @@ import org.springframework.beans.factory.annotation.Value;
22 import org.springframework.stereotype.Component; 22 import org.springframework.stereotype.Component;
23 import org.springframework.util.StringUtils; 23 import org.springframework.util.StringUtils;
24 import org.thingsboard.server.common.data.Dashboard; 24 import org.thingsboard.server.common.data.Dashboard;
25 -import org.thingsboard.server.common.data.ResourceType;  
26 -import org.thingsboard.server.common.data.TbResource;  
27 import org.thingsboard.server.common.data.id.CustomerId; 25 import org.thingsboard.server.common.data.id.CustomerId;
28 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
29 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
@@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
33 import org.thingsboard.server.common.data.widget.WidgetTypeDetails; 31 import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
34 import org.thingsboard.server.common.data.widget.WidgetsBundle; 32 import org.thingsboard.server.common.data.widget.WidgetsBundle;
35 import org.thingsboard.server.dao.dashboard.DashboardService; 33 import org.thingsboard.server.dao.dashboard.DashboardService;
36 -import org.thingsboard.server.dao.exception.DataValidationException;  
37 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; 34 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
38 import org.thingsboard.server.dao.resource.TbResourceService; 35 import org.thingsboard.server.dao.resource.TbResourceService;
39 import org.thingsboard.server.dao.rule.RuleChainService; 36 import org.thingsboard.server.dao.rule.RuleChainService;
@@ -45,7 +42,6 @@ import java.nio.file.DirectoryStream; @@ -45,7 +42,6 @@ import java.nio.file.DirectoryStream;
45 import java.nio.file.Files; 42 import java.nio.file.Files;
46 import java.nio.file.Path; 43 import java.nio.file.Path;
47 import java.nio.file.Paths; 44 import java.nio.file.Paths;
48 -import java.util.Base64;  
49 import java.util.Optional; 45 import java.util.Optional;
50 46
51 import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; 47 import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
@@ -196,29 +192,6 @@ public class InstallScripts { @@ -196,29 +192,6 @@ public class InstallScripts {
196 } 192 }
197 } 193 }
198 194
199 - public void loadSystemLwm2mResources() throws Exception {  
200 - Path modelsDir = Paths.get(getDataDir(), MODELS_DIR);  
201 - if (Files.isDirectory(modelsDir)) {  
202 - try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) {  
203 - dirStream.forEach(  
204 - path -> {  
205 - try {  
206 - byte[] fileBytes = Files.readAllBytes(path);  
207 - TbResource resource = new TbResource();  
208 - resource.setFileName(path.getFileName().toString());  
209 - resource.setTenantId(TenantId.SYS_TENANT_ID);  
210 - resource.setResourceType(ResourceType.LWM2M_MODEL);  
211 - resource.setData(Base64.getEncoder().encodeToString(fileBytes));  
212 - resourceService.saveResource(resource);  
213 - } catch (Exception e) {  
214 - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", path.toString()));  
215 - }  
216 - }  
217 - );  
218 - }  
219 - }  
220 - }  
221 -  
222 public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { 195 public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
223 Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); 196 Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
224 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { 197 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
@@ -449,26 +449,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -449,26 +449,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
449 case "3.2.2": 449 case "3.2.2":
450 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 450 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
451 log.info("Updating schema ..."); 451 log.info("Updating schema ...");
452 - try {  
453 - conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource ( " +  
454 - "id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY, " +  
455 - "created_time bigint NOT NULL, " +  
456 - "tenant_id uuid NOT NULL, " +  
457 - "title varchar(255) NOT NULL, " +  
458 - "resource_type varchar(32) NOT NULL, " +  
459 - "resource_key varchar(255) NOT NULL, " +  
460 - "search_text varchar(255), " +  
461 - "file_name varchar(255) NOT NULL, " +  
462 - "data varchar, " +  
463 - "CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)" +  
464 - ");");  
465 -  
466 - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");  
467 - installScripts.loadSystemLwm2mResources();  
468 - } catch (Exception e) {  
469 - log.error("Failed updating schema!!!", e);  
470 - } 452 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL);
  453 + loadSql(schemaUpdateFile, conn);
  454 + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
471 log.info("Schema updated."); 455 log.info("Schema updated.");
  456 + } catch (Exception e) {
  457 + log.error("Failed updating schema!!!", e);
472 } 458 }
473 break; 459 break;
474 default: 460 default:
@@ -33,6 +33,4 @@ public interface SystemDataLoaderService { @@ -33,6 +33,4 @@ public interface SystemDataLoaderService {
33 33
34 void deleteSystemWidgetBundle(String bundleAlias) throws Exception; 34 void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
35 35
36 - void loadSystemLwm2mResources() throws Exception;  
37 -  
38 } 36 }
@@ -37,7 +37,8 @@ public enum Resource { @@ -37,7 +37,8 @@ public enum Resource {
37 TENANT_PROFILE(EntityType.TENANT_PROFILE), 37 TENANT_PROFILE(EntityType.TENANT_PROFILE),
38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE), 38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
39 API_USAGE_STATE(EntityType.API_USAGE_STATE), 39 API_USAGE_STATE(EntityType.API_USAGE_STATE),
40 - TB_RESOURCE(EntityType.TB_RESOURCE); 40 + TB_RESOURCE(EntityType.TB_RESOURCE),
  41 + FIRMWARE(EntityType.FIRMWARE);
41 42
42 private final EntityType entityType; 43 private final EntityType entityType;
43 44
@@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions { @@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
42 put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); 42 put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
43 put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); 43 put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker); 44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
  45 + put(Resource.FIRMWARE, tenantEntityPermissionChecker);
45 } 46 }
46 47
47 public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { 48 public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {
@@ -23,16 +23,19 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -23,16 +23,19 @@ import com.google.common.util.concurrent.ListenableFuture;
23 import com.google.common.util.concurrent.MoreExecutors; 23 import com.google.common.util.concurrent.MoreExecutors;
24 import com.google.protobuf.ByteString; 24 import com.google.protobuf.ByteString;
25 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.cache.CacheManager;
26 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
27 import org.springframework.util.StringUtils; 28 import org.springframework.util.StringUtils;
28 import org.thingsboard.common.util.JacksonUtil; 29 import org.thingsboard.common.util.JacksonUtil;
  30 +import org.thingsboard.server.cache.firmware.FirmwareCacheWriter;
29 import org.thingsboard.server.common.data.ApiUsageState; 31 import org.thingsboard.server.common.data.ApiUsageState;
30 import org.thingsboard.server.common.data.DataConstants; 32 import org.thingsboard.server.common.data.DataConstants;
31 import org.thingsboard.server.common.data.Device; 33 import org.thingsboard.server.common.data.Device;
32 import org.thingsboard.server.common.data.DeviceProfile; 34 import org.thingsboard.server.common.data.DeviceProfile;
33 import org.thingsboard.server.common.data.EntityType; 35 import org.thingsboard.server.common.data.EntityType;
34 -import org.thingsboard.server.common.data.TbResource; 36 +import org.thingsboard.server.common.data.Firmware;
35 import org.thingsboard.server.common.data.ResourceType; 37 import org.thingsboard.server.common.data.ResourceType;
  38 +import org.thingsboard.server.common.data.TbResource;
36 import org.thingsboard.server.common.data.TenantProfile; 39 import org.thingsboard.server.common.data.TenantProfile;
37 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 40 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
38 import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; 41 import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
@@ -40,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileC @@ -40,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileC
40 import org.thingsboard.server.common.data.id.CustomerId; 43 import org.thingsboard.server.common.data.id.CustomerId;
41 import org.thingsboard.server.common.data.id.DeviceId; 44 import org.thingsboard.server.common.data.id.DeviceId;
42 import org.thingsboard.server.common.data.id.DeviceProfileId; 45 import org.thingsboard.server.common.data.id.DeviceProfileId;
  46 +import org.thingsboard.server.common.data.id.FirmwareId;
43 import org.thingsboard.server.common.data.id.TenantId; 47 import org.thingsboard.server.common.data.id.TenantId;
44 import org.thingsboard.server.common.data.relation.EntityRelation; 48 import org.thingsboard.server.common.data.relation.EntityRelation;
45 import org.thingsboard.server.common.data.security.DeviceCredentials; 49 import org.thingsboard.server.common.data.security.DeviceCredentials;
@@ -55,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -55,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService;
55 import org.thingsboard.server.dao.device.provision.ProvisionFailedException; 59 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
56 import org.thingsboard.server.dao.device.provision.ProvisionRequest; 60 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
57 import org.thingsboard.server.dao.device.provision.ProvisionResponse; 61 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  62 +import org.thingsboard.server.dao.firmware.FirmwareService;
58 import org.thingsboard.server.dao.relation.RelationService; 63 import org.thingsboard.server.dao.relation.RelationService;
59 import org.thingsboard.server.dao.resource.TbResourceService; 64 import org.thingsboard.server.dao.resource.TbResourceService;
60 import org.thingsboard.server.dao.tenant.TbTenantProfileCache; 65 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@@ -66,7 +71,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro @@ -66,7 +71,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro
66 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; 71 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
67 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; 72 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
68 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; 73 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
69 -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;  
70 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; 74 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
71 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; 75 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
72 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; 76 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
@@ -109,6 +113,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -109,6 +113,8 @@ public class DefaultTransportApiService implements TransportApiService {
109 private final DataDecodingEncodingService dataDecodingEncodingService; 113 private final DataDecodingEncodingService dataDecodingEncodingService;
110 private final DeviceProvisionService deviceProvisionService; 114 private final DeviceProvisionService deviceProvisionService;
111 private final TbResourceService resourceService; 115 private final TbResourceService resourceService;
  116 + private final FirmwareService firmwareService;
  117 + private final FirmwareCacheWriter firmwareCacheWriter;
112 118
113 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); 119 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
114 120
@@ -117,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -117,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService {
117 RelationService relationService, DeviceCredentialsService deviceCredentialsService, 123 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
118 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, 124 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
119 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, 125 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
120 - DeviceProvisionService deviceProvisionService, TbResourceService resourceService) { 126 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, CacheManager cacheManager, FirmwareCacheWriter firmwareCacheWriter) {
121 this.deviceProfileCache = deviceProfileCache; 127 this.deviceProfileCache = deviceProfileCache;
122 this.tenantProfileCache = tenantProfileCache; 128 this.tenantProfileCache = tenantProfileCache;
123 this.apiUsageStateService = apiUsageStateService; 129 this.apiUsageStateService = apiUsageStateService;
@@ -130,6 +136,8 @@ public class DefaultTransportApiService implements TransportApiService { @@ -130,6 +136,8 @@ public class DefaultTransportApiService implements TransportApiService {
130 this.dataDecodingEncodingService = dataDecodingEncodingService; 136 this.dataDecodingEncodingService = dataDecodingEncodingService;
131 this.deviceProvisionService = deviceProvisionService; 137 this.deviceProvisionService = deviceProvisionService;
132 this.resourceService = resourceService; 138 this.resourceService = resourceService;
  139 + this.firmwareService = firmwareService;
  140 + this.firmwareCacheWriter = firmwareCacheWriter;
133 } 141 }
134 142
135 @Override 143 @Override
@@ -166,6 +174,9 @@ public class DefaultTransportApiService implements TransportApiService { @@ -166,6 +174,9 @@ public class DefaultTransportApiService implements TransportApiService {
166 } else if (transportApiRequestMsg.hasResourceRequestMsg()) { 174 } else if (transportApiRequestMsg.hasResourceRequestMsg()) {
167 return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()), 175 return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
168 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); 176 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  177 + } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) {
  178 + return Futures.transform(handle(transportApiRequestMsg.getFirmwareRequestMsg()),
  179 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
169 } 180 }
170 return Futures.transform(getEmptyTransportApiResponseFuture(), 181 return Futures.transform(getEmptyTransportApiResponseFuture(),
171 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); 182 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
@@ -313,14 +324,14 @@ public class DefaultTransportApiService implements TransportApiService { @@ -313,14 +324,14 @@ public class DefaultTransportApiService implements TransportApiService {
313 } catch (ProvisionFailedException e) { 324 } catch (ProvisionFailedException e) {
314 return Futures.immediateFuture(getTransportApiResponseMsg( 325 return Futures.immediateFuture(getTransportApiResponseMsg(
315 new DeviceCredentials(), 326 new DeviceCredentials(),
316 - TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); 327 + TransportProtos.ResponseStatus.valueOf(e.getMessage())));
317 } 328 }
318 - return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS), 329 + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ResponseStatus.SUCCESS),
319 dbCallbackExecutorService); 330 dbCallbackExecutorService);
320 } 331 }
321 332
322 - private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) {  
323 - if (!status.equals(ProvisionResponseStatus.SUCCESS)) { 333 + private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ResponseStatus status) {
  334 + if (!status.equals(TransportProtos.ResponseStatus.SUCCESS)) {
324 return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build(); 335 return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build();
325 } 336 }
326 TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() 337 TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
@@ -438,6 +449,46 @@ public class DefaultTransportApiService implements TransportApiService { @@ -438,6 +449,46 @@ public class DefaultTransportApiService implements TransportApiService {
438 } 449 }
439 } 450 }
440 451
  452 + private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetFirmwareRequestMsg requestMsg) {
  453 + TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
  454 + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
  455 + Device device = deviceService.findDeviceById(tenantId, deviceId);
  456 +
  457 + if (device == null) {
  458 + return getEmptyTransportApiResponseFuture();
  459 + }
  460 +
  461 + FirmwareId firmwareId = device.getFirmwareId();
  462 +
  463 + if (firmwareId == null) {
  464 + firmwareId = deviceProfileCache.find(device.getDeviceProfileId()).getFirmwareId();
  465 + }
  466 +
  467 + TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder();
  468 +
  469 + if (firmwareId == null) {
  470 + builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
  471 + } else {
  472 + Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId);
  473 +
  474 + if (firmware == null) {
  475 + builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
  476 + } else {
  477 + builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
  478 + builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits());
  479 + builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits());
  480 + builder.setFileName(firmware.getFileName());
  481 + builder.setContentType(firmware.getContentType());
  482 + firmwareCacheWriter.put(firmwareId.toString(), firmware.getData().array());
  483 + }
  484 + }
  485 +
  486 + return Futures.immediateFuture(
  487 + TransportApiResponseMsg.newBuilder()
  488 + .setFirmwareResponseMsg(builder.build())
  489 + .build());
  490 + }
  491 +
441 private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) { 492 private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) {
442 TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId())); 493 TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId()));
443 String deviceName = msg.getEndpoint(); 494 String deviceName = msg.getEndpoint();
@@ -281,7 +281,7 @@ actors: @@ -281,7 +281,7 @@ actors:
281 tenant: 281 tenant:
282 create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" 282 create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}"
283 session: 283 session:
284 - max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:1}" 284 + max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:2}"
285 sync: 285 sync:
286 # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds 286 # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds
287 timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}" 287 timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}"
@@ -365,6 +365,9 @@ caffeine: @@ -365,6 +365,9 @@ caffeine:
365 tokensOutdatageTime: 365 tokensOutdatageTime:
366 timeToLiveInMinutes: 20000 366 timeToLiveInMinutes: 20000
367 maxSize: 10000 367 maxSize: 10000
  368 + firmwares:
  369 + timeToLiveInMinutes: 1440
  370 + maxSize: 100
368 371
369 redis: 372 redis:
370 # standalone or cluster 373 # standalone or cluster
@@ -437,6 +440,9 @@ spring.resources.chain: @@ -437,6 +440,9 @@ spring.resources.chain:
437 content: 440 content:
438 enabled: "true" 441 enabled: "true"
439 442
  443 +spring.servlet.multipart.max-file-size: "50MB"
  444 +spring.servlet.multipart.max-request-size: "50MB"
  445 +
440 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" 446 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
441 spring.jpa.properties.hibernate.order_by.default_null_ordering: "last" 447 spring.jpa.properties.hibernate.order_by.default_null_ordering: "last"
442 448
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.junit.After;
  20 +import org.junit.Assert;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.springframework.mock.web.MockMultipartFile;
  24 +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
  25 +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  26 +import org.thingsboard.common.util.JacksonUtil;
  27 +import org.thingsboard.server.common.data.Firmware;
  28 +import org.thingsboard.server.common.data.FirmwareInfo;
  29 +import org.thingsboard.server.common.data.Tenant;
  30 +import org.thingsboard.server.common.data.User;
  31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.common.data.security.Authority;
  34 +
  35 +import java.nio.ByteBuffer;
  36 +import java.util.ArrayList;
  37 +import java.util.Collections;
  38 +import java.util.List;
  39 +
  40 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  41 +
  42 +public abstract class BaseFirmwareControllerTest extends AbstractControllerTest {
  43 +
  44 + private IdComparator<FirmwareInfo> idComparator = new IdComparator<>();
  45 +
  46 + public static final String TITLE = "My firmware";
  47 + private static final String FILE_NAME = "filename.txt";
  48 + private static final String VERSION = "v1.0";
  49 + private static final String CONTENT_TYPE = "text/plain";
  50 + private static final String CHECKSUM_ALGORITHM = "sha256";
  51 + private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
  52 + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
  53 +
  54 + private Tenant savedTenant;
  55 + private User tenantAdmin;
  56 +
  57 + @Before
  58 + public void beforeTest() throws Exception {
  59 + loginSysAdmin();
  60 +
  61 + Tenant tenant = new Tenant();
  62 + tenant.setTitle("My tenant");
  63 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  64 + Assert.assertNotNull(savedTenant);
  65 +
  66 + tenantAdmin = new User();
  67 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  68 + tenantAdmin.setTenantId(savedTenant.getId());
  69 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  70 + tenantAdmin.setFirstName("Joe");
  71 + tenantAdmin.setLastName("Downs");
  72 +
  73 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  74 + }
  75 +
  76 + @After
  77 + public void afterTest() throws Exception {
  78 + loginSysAdmin();
  79 +
  80 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  81 + .andExpect(status().isOk());
  82 + }
  83 +
  84 + @Test
  85 + public void testSaveFirmware() throws Exception {
  86 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  87 + firmwareInfo.setTitle(TITLE);
  88 + firmwareInfo.setVersion(VERSION);
  89 +
  90 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  91 +
  92 + Assert.assertNotNull(savedFirmwareInfo);
  93 + Assert.assertNotNull(savedFirmwareInfo.getId());
  94 + Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0);
  95 + Assert.assertEquals(savedTenant.getId(), savedFirmwareInfo.getTenantId());
  96 + Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
  97 + Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion());
  98 +
  99 + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
  100 +
  101 + save(savedFirmwareInfo);
  102 +
  103 + FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class);
  104 + Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
  105 + }
  106 +
  107 + @Test
  108 + public void testSaveFirmwareData() throws Exception {
  109 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  110 + firmwareInfo.setTitle(TITLE);
  111 + firmwareInfo.setVersion(VERSION);
  112 +
  113 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  114 +
  115 + Assert.assertNotNull(savedFirmwareInfo);
  116 + Assert.assertNotNull(savedFirmwareInfo.getId());
  117 + Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0);
  118 + Assert.assertEquals(savedTenant.getId(), savedFirmwareInfo.getTenantId());
  119 + Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
  120 + Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion());
  121 +
  122 + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
  123 +
  124 + save(savedFirmwareInfo);
  125 +
  126 + FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class);
  127 + Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
  128 +
  129 + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
  130 +
  131 + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
  132 +
  133 + Assert.assertEquals(FILE_NAME, savedFirmware.getFileName());
  134 + Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType());
  135 + }
  136 +
  137 + @Test
  138 + public void testUpdateFirmwareFromDifferentTenant() throws Exception {
  139 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  140 + firmwareInfo.setTitle(TITLE);
  141 + firmwareInfo.setVersion(VERSION);
  142 +
  143 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  144 +
  145 + loginDifferentTenant();
  146 + doPost("/api/firmware", savedFirmwareInfo, FirmwareInfo.class, status().isForbidden());
  147 + deleteDifferentTenant();
  148 + }
  149 +
  150 + @Test
  151 + public void testFindFirmwareInfoById() throws Exception {
  152 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  153 + firmwareInfo.setTitle(TITLE);
  154 + firmwareInfo.setVersion(VERSION);
  155 +
  156 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  157 +
  158 + FirmwareInfo foundFirmware = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class);
  159 + Assert.assertNotNull(foundFirmware);
  160 + Assert.assertEquals(savedFirmwareInfo, foundFirmware);
  161 + }
  162 +
  163 + @Test
  164 + public void testFindFirmwareById() throws Exception {
  165 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  166 + firmwareInfo.setTitle(TITLE);
  167 + firmwareInfo.setVersion(VERSION);
  168 +
  169 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  170 +
  171 + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
  172 +
  173 + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
  174 +
  175 + Firmware foundFirmware = doGet("/api/firmware/" + savedFirmwareInfo.getId().getId().toString(), Firmware.class);
  176 + Assert.assertNotNull(foundFirmware);
  177 + Assert.assertEquals(savedFirmware, foundFirmware);
  178 + }
  179 +
  180 + @Test
  181 + public void testDeleteFirmware() throws Exception {
  182 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  183 + firmwareInfo.setTitle(TITLE);
  184 + firmwareInfo.setVersion(VERSION);
  185 +
  186 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  187 +
  188 + doDelete("/api/firmware/" + savedFirmwareInfo.getId().getId().toString())
  189 + .andExpect(status().isOk());
  190 +
  191 + doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString())
  192 + .andExpect(status().isNotFound());
  193 + }
  194 +
  195 + @Test
  196 + public void testFindTenantFirmwares() throws Exception {
  197 + List<FirmwareInfo> firmwares = new ArrayList<>();
  198 + for (int i = 0; i < 165; i++) {
  199 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  200 + firmwareInfo.setTitle(TITLE);
  201 + firmwareInfo.setVersion(VERSION + i);
  202 +
  203 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  204 +
  205 + if (i > 100) {
  206 + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
  207 +
  208 + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
  209 + firmwares.add(new FirmwareInfo(savedFirmware));
  210 + } else {
  211 + firmwares.add(savedFirmwareInfo);
  212 + }
  213 + }
  214 +
  215 + List<FirmwareInfo> loadedFirmwares = new ArrayList<>();
  216 + PageLink pageLink = new PageLink(24);
  217 + PageData<FirmwareInfo> pageData;
  218 + do {
  219 + pageData = doGetTypedWithPageLink("/api/firmwares?",
  220 + new TypeReference<>() {
  221 + }, pageLink);
  222 + loadedFirmwares.addAll(pageData.getData());
  223 + if (pageData.hasNext()) {
  224 + pageLink = pageLink.nextPageLink();
  225 + }
  226 + } while (pageData.hasNext());
  227 +
  228 + Collections.sort(firmwares, idComparator);
  229 + Collections.sort(loadedFirmwares, idComparator);
  230 +
  231 + Assert.assertEquals(firmwares, loadedFirmwares);
  232 + }
  233 +
  234 + @Test
  235 + public void testFindTenantFirmwaresByHasData() throws Exception {
  236 + List<FirmwareInfo> firmwaresWithData = new ArrayList<>();
  237 + List<FirmwareInfo> firmwaresWithoutData = new ArrayList<>();
  238 +
  239 + for (int i = 0; i < 165; i++) {
  240 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  241 + firmwareInfo.setTitle(TITLE);
  242 + firmwareInfo.setVersion(VERSION + i);
  243 +
  244 + FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
  245 +
  246 + if (i > 100) {
  247 + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
  248 +
  249 + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
  250 + firmwaresWithData.add(new FirmwareInfo(savedFirmware));
  251 + } else {
  252 + firmwaresWithoutData.add(savedFirmwareInfo);
  253 + }
  254 + }
  255 +
  256 + List<FirmwareInfo> loadedFirmwaresWithData = new ArrayList<>();
  257 + PageLink pageLink = new PageLink(24);
  258 + PageData<FirmwareInfo> pageData;
  259 + do {
  260 + pageData = doGetTypedWithPageLink("/api/firmwares/true?",
  261 + new TypeReference<>() {
  262 + }, pageLink);
  263 + loadedFirmwaresWithData.addAll(pageData.getData());
  264 + if (pageData.hasNext()) {
  265 + pageLink = pageLink.nextPageLink();
  266 + }
  267 + } while (pageData.hasNext());
  268 +
  269 + List<FirmwareInfo> loadedFirmwaresWithoutData = new ArrayList<>();
  270 + pageLink = new PageLink(24);
  271 + do {
  272 + pageData = doGetTypedWithPageLink("/api/firmwares/false?",
  273 + new TypeReference<>() {
  274 + }, pageLink);
  275 + loadedFirmwaresWithoutData.addAll(pageData.getData());
  276 + if (pageData.hasNext()) {
  277 + pageLink = pageLink.nextPageLink();
  278 + }
  279 + } while (pageData.hasNext());
  280 +
  281 + Collections.sort(firmwaresWithData, idComparator);
  282 + Collections.sort(firmwaresWithoutData, idComparator);
  283 + Collections.sort(loadedFirmwaresWithData, idComparator);
  284 + Collections.sort(loadedFirmwaresWithoutData, idComparator);
  285 +
  286 + Assert.assertEquals(firmwaresWithData, loadedFirmwaresWithData);
  287 + Assert.assertEquals(firmwaresWithoutData, loadedFirmwaresWithoutData);
  288 + }
  289 +
  290 +
  291 + private FirmwareInfo save(FirmwareInfo firmwareInfo) throws Exception {
  292 + return doPost("/api/firmware", firmwareInfo, FirmwareInfo.class);
  293 + }
  294 +
  295 + protected Firmware savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
  296 + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
  297 + postRequest.file(content);
  298 + setJwtToken(postRequest);
  299 + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), Firmware.class);
  300 + }
  301 +
  302 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.sql;
  17 +
  18 +import org.thingsboard.server.controller.BaseFirmwareControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class FirmwareControllerSqlTest extends BaseFirmwareControllerTest {
  23 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  19 +
  20 +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
  21 +
  22 +public abstract class AbstractRedisFirmwareCache {
  23 +
  24 + protected final RedisConnectionFactory redisConnectionFactory;
  25 +
  26 + protected AbstractRedisFirmwareCache(RedisConnectionFactory redisConnectionFactory) {
  27 + this.redisConnectionFactory = redisConnectionFactory;
  28 + }
  29 +
  30 + protected byte[] toFirmwareCacheKey(String key) {
  31 + return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes();
  32 + }
  33 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +import org.springframework.cache.CacheManager;
  20 +import org.springframework.stereotype.Service;
  21 +
  22 +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
  23 +
  24 +@Service
  25 +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')")
  26 +public class CaffeineFirmwareCacheReader implements FirmwareCacheReader {
  27 +
  28 + private final CacheManager cacheManager;
  29 +
  30 + public CaffeineFirmwareCacheReader(CacheManager cacheManager) {
  31 + this.cacheManager = cacheManager;
  32 + }
  33 +
  34 + @Override
  35 + public byte[] get(String key) {
  36 + return get(key, 0, 0);
  37 + }
  38 +
  39 + @Override
  40 + public byte[] get(String key, int chunkSize, int chunk) {
  41 + byte[] data = cacheManager.getCache(FIRMWARE_CACHE).get(key, byte[].class);
  42 +
  43 + if (chunkSize < 1) {
  44 + return data;
  45 + }
  46 +
  47 + if (data != null && data.length > 0) {
  48 + int startIndex = chunkSize * chunk;
  49 +
  50 + int size = Math.min(data.length - startIndex, chunkSize);
  51 +
  52 + if (startIndex < data.length && size > 0) {
  53 + byte[] result = new byte[size];
  54 + System.arraycopy(data, startIndex, result, 0, size);
  55 + return result;
  56 + }
  57 + }
  58 + return new byte[0];
  59 + }
  60 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +import org.springframework.cache.CacheManager;
  20 +import org.springframework.stereotype.Service;
  21 +
  22 +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
  23 +
  24 +@Service
  25 +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')")
  26 +public class CaffeineFirmwareCacheWriter implements FirmwareCacheWriter {
  27 +
  28 + private final CacheManager cacheManager;
  29 +
  30 + public CaffeineFirmwareCacheWriter(CacheManager cacheManager) {
  31 + this.cacheManager = cacheManager;
  32 + }
  33 +
  34 + @Override
  35 + public void put(String key, byte[] value) {
  36 + cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value);
  37 + }
  38 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +public interface FirmwareCacheReader {
  19 + byte[] get(String key);
  20 +
  21 + byte[] get(String key, int chunkSize, int chunk);
  22 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +public interface FirmwareCacheWriter {
  19 + void put(String key, byte[] value);
  20 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +import org.springframework.data.redis.connection.RedisConnection;
  20 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  21 +import org.springframework.stereotype.Service;
  22 +
  23 +@Service
  24 +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && '${cache.type:null}'=='redis'")
  25 +public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache implements FirmwareCacheReader {
  26 +
  27 + public RedisFirmwareCacheReader(RedisConnectionFactory redisConnectionFactory) {
  28 + super(redisConnectionFactory);
  29 + }
  30 +
  31 + @Override
  32 + public byte[] get(String key) {
  33 + return get(key, 0, 0);
  34 + }
  35 +
  36 + @Override
  37 + public byte[] get(String key, int chunkSize, int chunk) {
  38 + try (RedisConnection connection = redisConnectionFactory.getConnection()) {
  39 + if (chunkSize == 0) {
  40 + return connection.get(toFirmwareCacheKey(key));
  41 + }
  42 +
  43 + int startIndex = chunkSize * chunk;
  44 + int endIndex = startIndex + chunkSize - 1;
  45 + return connection.getRange(toFirmwareCacheKey(key), startIndex, endIndex);
  46 + }
  47 + }
  48 +
  49 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache.firmware;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +import org.springframework.data.redis.connection.RedisConnection;
  20 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  21 +import org.springframework.stereotype.Service;
  22 +
  23 +@Service
  24 +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && '${cache.type:null}'=='redis'")
  25 +public class RedisFirmwareCacheWriter extends AbstractRedisFirmwareCache implements FirmwareCacheWriter {
  26 +
  27 + public RedisFirmwareCacheWriter(RedisConnectionFactory redisConnectionFactory) {
  28 + super(redisConnectionFactory);
  29 + }
  30 +
  31 + @Override
  32 + public void put(String key, byte[] value) {
  33 + try (RedisConnection connection = redisConnectionFactory.getConnection()) {
  34 + connection.set(toFirmwareCacheKey(key), value);
  35 + }
  36 + }
  37 +
  38 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.firmware;
  17 +
  18 +import org.thingsboard.server.common.data.Firmware;
  19 +import org.thingsboard.server.common.data.FirmwareInfo;
  20 +import org.thingsboard.server.common.data.id.FirmwareId;
  21 +import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.data.page.PageData;
  23 +import org.thingsboard.server.common.data.page.PageLink;
  24 +
  25 +public interface FirmwareService {
  26 +
  27 + FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo);
  28 +
  29 + Firmware saveFirmware(Firmware firmware);
  30 +
  31 + Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId);
  32 +
  33 + FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId);
  34 +
  35 + PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink);
  36 +
  37 + PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink);
  38 +
  39 + void deleteFirmware(TenantId tenantId, FirmwareId firmwareId);
  40 +
  41 + void deleteFirmwaresByTenantId(TenantId tenantId);
  42 +}
@@ -32,7 +32,7 @@ import java.util.List; @@ -32,7 +32,7 @@ import java.util.List;
32 public interface TbResourceService { 32 public interface TbResourceService {
33 TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException; 33 TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException;
34 34
35 - TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); 35 + TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey);
36 36
37 TbResource findResourceById(TenantId tenantId, TbResourceId resourceId); 37 TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
38 38
@@ -28,4 +28,5 @@ public class CacheConstants { @@ -28,4 +28,5 @@ public class CacheConstants {
28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; 28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
29 public static final String ATTRIBUTES_CACHE = "attributes"; 29 public static final String ATTRIBUTES_CACHE = "attributes";
30 public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime"; 30 public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
  31 + public static final String FIRMWARE_CACHE = "firmwares";
31 } 32 }
@@ -91,4 +91,19 @@ public class DataConstants { @@ -91,4 +91,19 @@ public class DataConstants {
91 public static final String USERNAME = "username"; 91 public static final String USERNAME = "username";
92 public static final String PASSWORD = "password"; 92 public static final String PASSWORD = "password";
93 93
  94 + //firmware
  95 + //telemetry
  96 + public static final String CURRENT_FIRMWARE_TITLE = "cur_fw_title";
  97 + public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version";
  98 + public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
  99 + public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
  100 + public static final String CURRENT_FIRMWARE_STATE = "cur_fw_state";
  101 +
  102 + //attributes
  103 + //telemetry
  104 + public static final String FIRMWARE_TITLE = "fw_title";
  105 + public static final String FIRMWARE_VERSION = "fw_version";
  106 + public static final String FIRMWARE_SIZE = "fw_size";
  107 + public static final String FIRMWARE_CHECKSUM = "fw_checksum";
  108 + public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm";
94 } 109 }
@@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData;
23 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.DeviceId; 24 import org.thingsboard.server.common.data.id.DeviceId;
25 import org.thingsboard.server.common.data.id.DeviceProfileId; 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
  26 +import org.thingsboard.server.common.data.id.FirmwareId;
26 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.validation.NoXss; 28 import org.thingsboard.server.common.data.validation.NoXss;
28 29
@@ -48,6 +49,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -48,6 +49,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
48 @JsonIgnore 49 @JsonIgnore
49 private byte[] deviceDataBytes; 50 private byte[] deviceDataBytes;
50 51
  52 + private FirmwareId firmwareId;
  53 +
51 public Device() { 54 public Device() {
52 super(); 55 super();
53 } 56 }
@@ -65,6 +68,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -65,6 +68,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
65 this.label = device.getLabel(); 68 this.label = device.getLabel();
66 this.deviceProfileId = device.getDeviceProfileId(); 69 this.deviceProfileId = device.getDeviceProfileId();
67 this.setDeviceData(device.getDeviceData()); 70 this.setDeviceData(device.getDeviceData());
  71 + this.firmwareId = device.getFirmwareId();
68 } 72 }
69 73
70 public Device updateDevice(Device device) { 74 public Device updateDevice(Device device) {
@@ -159,6 +163,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -159,6 +163,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
159 return getName(); 163 return getName();
160 } 164 }
161 165
  166 + public FirmwareId getFirmwareId() {
  167 + return firmwareId;
  168 + }
  169 +
  170 + public void setFirmwareId(FirmwareId firmwareId) {
  171 + this.firmwareId = firmwareId;
  172 + }
  173 +
162 @Override 174 @Override
163 public String toString() { 175 public String toString() {
164 StringBuilder builder = new StringBuilder(); 176 StringBuilder builder = new StringBuilder();
@@ -175,6 +187,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -175,6 +187,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
175 builder.append(", deviceProfileId="); 187 builder.append(", deviceProfileId=");
176 builder.append(deviceProfileId); 188 builder.append(deviceProfileId);
177 builder.append(", deviceData="); 189 builder.append(", deviceData=");
  190 + builder.append(firmwareId);
  191 + builder.append(", firmwareId=");
178 builder.append(deviceData); 192 builder.append(deviceData);
179 builder.append(", additionalInfo="); 193 builder.append(", additionalInfo=");
180 builder.append(getAdditionalInfo()); 194 builder.append(getAdditionalInfo());
@@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode; @@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode;
22 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
23 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 23 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
24 import org.thingsboard.server.common.data.id.DeviceProfileId; 24 import org.thingsboard.server.common.data.id.DeviceProfileId;
  25 +import org.thingsboard.server.common.data.id.FirmwareId;
25 import org.thingsboard.server.common.data.id.RuleChainId; 26 import org.thingsboard.server.common.data.id.RuleChainId;
26 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.validation.NoXss; 28 import org.thingsboard.server.common.data.validation.NoXss;
@@ -56,6 +57,8 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H @@ -56,6 +57,8 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
56 @NoXss 57 @NoXss
57 private String provisionDeviceKey; 58 private String provisionDeviceKey;
58 59
  60 + private FirmwareId firmwareId;
  61 +
59 public DeviceProfile() { 62 public DeviceProfile() {
60 super(); 63 super();
61 } 64 }
@@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 * @author Andrew Shvayka 19 * @author Andrew Shvayka
20 */ 20 */
21 public enum EntityType { 21 public enum EntityType {
22 - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE; 22 + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, FIRMWARE;
23 } 23 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.id.FirmwareId;
  21 +
  22 +import java.nio.ByteBuffer;
  23 +
  24 +@Data
  25 +@EqualsAndHashCode(callSuper = true)
  26 +public class Firmware extends FirmwareInfo {
  27 +
  28 + private static final long serialVersionUID = 3091601761339422546L;
  29 +
  30 + private String fileName;
  31 +
  32 + private String contentType;
  33 +
  34 + private String checksumAlgorithm;
  35 +
  36 + private String checksum;
  37 +
  38 + private transient ByteBuffer data;
  39 +
  40 + public Firmware() {
  41 + super();
  42 + }
  43 +
  44 + public Firmware(FirmwareId id) {
  45 + super(id);
  46 + }
  47 +
  48 + public Firmware(Firmware firmware) {
  49 + super(firmware);
  50 + this.fileName = firmware.getFileName();
  51 + this.contentType = firmware.getContentType();
  52 + this.data = firmware.getData();
  53 + this.checksumAlgorithm = firmware.getChecksumAlgorithm();
  54 + this.checksum = firmware.getChecksum();
  55 + }
  56 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.thingsboard.server.common.data.id.FirmwareId;
  22 +import org.thingsboard.server.common.data.id.TenantId;
  23 +
  24 +@Slf4j
  25 +@Data
  26 +@EqualsAndHashCode(callSuper = true)
  27 +public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId> implements HasTenantId {
  28 +
  29 + private static final long serialVersionUID = 3168391583570815419L;
  30 +
  31 + private TenantId tenantId;
  32 + private String title;
  33 + private String version;
  34 + private boolean hasData;
  35 +
  36 + public FirmwareInfo() {
  37 + super();
  38 + }
  39 +
  40 + public FirmwareInfo(FirmwareId id) {
  41 + super(id);
  42 + }
  43 +
  44 + public FirmwareInfo(FirmwareInfo firmwareInfo) {
  45 + super(firmwareInfo);
  46 + this.tenantId = firmwareInfo.getTenantId();
  47 + this.title = firmwareInfo.getTitle();
  48 + this.version = firmwareInfo.getVersion();
  49 + this.hasData = firmwareInfo.isHasData();
  50 + }
  51 +
  52 + @Override
  53 + public String getSearchText() {
  54 + return title;
  55 + }
  56 +}
@@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.id.TenantId;
26 @EqualsAndHashCode(callSuper = true) 26 @EqualsAndHashCode(callSuper = true)
27 public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId { 27 public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
28 28
  29 + private static final long serialVersionUID = 7282664529021651736L;
  30 +
29 private TenantId tenantId; 31 private TenantId tenantId;
30 private String title; 32 private String title;
31 private ResourceType resourceType; 33 private ResourceType resourceType;
@@ -30,6 +30,9 @@ public class MqttTopics { @@ -30,6 +30,9 @@ public class MqttTopics {
30 private static final String CLAIM = "/claim"; 30 private static final String CLAIM = "/claim";
31 private static final String SUB_TOPIC = "+"; 31 private static final String SUB_TOPIC = "+";
32 private static final String PROVISION = "/provision"; 32 private static final String PROVISION = "/provision";
  33 + private static final String FIRMWARE = "/fw";
  34 + private static final String CHUNK = "/chunk/";
  35 + private static final String ERROR = "/error";
33 36
34 private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE; 37 private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
35 private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; 38 private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
@@ -69,6 +72,13 @@ public class MqttTopics { @@ -69,6 +72,13 @@ public class MqttTopics {
69 public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST; 72 public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST;
70 public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE; 73 public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE;
71 74
  75 + // v2 topics
  76 + public static final String BASE_DEVICE_API_TOPIC_V2 = "v2";
  77 +
  78 + public static final String DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/";
  79 + public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + SUB_TOPIC + CHUNK + SUB_TOPIC;
  80 + public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR;
  81 +
72 private MqttTopics() { 82 private MqttTopics() {
73 } 83 }
74 } 84 }
@@ -70,6 +70,8 @@ public class EntityIdFactory { @@ -70,6 +70,8 @@ public class EntityIdFactory {
70 return new ApiUsageStateId(uuid); 70 return new ApiUsageStateId(uuid);
71 case TB_RESOURCE: 71 case TB_RESOURCE:
72 return new TbResourceId(uuid); 72 return new TbResourceId(uuid);
  73 + case FIRMWARE:
  74 + return new FirmwareId(uuid);
73 } 75 }
74 throw new IllegalArgumentException("EntityType " + type + " is not supported!"); 76 throw new IllegalArgumentException("EntityType " + type + " is not supported!");
75 } 77 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonIgnore;
  20 +import com.fasterxml.jackson.annotation.JsonProperty;
  21 +import org.thingsboard.server.common.data.EntityType;
  22 +
  23 +import java.util.UUID;
  24 +
  25 +public class FirmwareId extends UUIDBased implements EntityId {
  26 +
  27 + private static final long serialVersionUID = 1L;
  28 +
  29 + @JsonCreator
  30 + public FirmwareId(@JsonProperty("id") UUID id) {
  31 + super(id);
  32 + }
  33 +
  34 + public static FirmwareId fromString(String firmwareId) {
  35 + return new FirmwareId(UUID.fromString(firmwareId));
  36 + }
  37 +
  38 + @JsonIgnore
  39 + @Override
  40 + public EntityType getEntityType() {
  41 + return EntityType.FIRMWARE;
  42 + }
  43 +
  44 +}
@@ -336,17 +336,33 @@ message ProvisionDeviceCredentialsMsg { @@ -336,17 +336,33 @@ message ProvisionDeviceCredentialsMsg {
336 } 336 }
337 337
338 message ProvisionDeviceResponseMsg { 338 message ProvisionDeviceResponseMsg {
339 - ProvisionResponseStatus status = 1; 339 + ResponseStatus status = 1;
340 CredentialsType credentialsType = 2; 340 CredentialsType credentialsType = 2;
341 string credentialsValue = 3; 341 string credentialsValue = 3;
342 } 342 }
343 343
344 -enum ProvisionResponseStatus { 344 +enum ResponseStatus {
345 UNKNOWN = 0; 345 UNKNOWN = 0;
346 SUCCESS = 1; 346 SUCCESS = 1;
347 NOT_FOUND = 2; 347 NOT_FOUND = 2;
348 FAILURE = 3; 348 FAILURE = 3;
349 } 349 }
  350 +
  351 +message GetFirmwareRequestMsg {
  352 + int64 deviceIdMSB = 1;
  353 + int64 deviceIdLSB = 2;
  354 + int64 tenantIdMSB = 3;
  355 + int64 tenantIdLSB = 4;
  356 +}
  357 +
  358 +message GetFirmwareResponseMsg {
  359 + ResponseStatus responseStatus = 1;
  360 + int64 firmwareIdMSB = 2;
  361 + int64 firmwareIdLSB = 3;
  362 + string contentType = 4;
  363 + string fileName = 5;
  364 +}
  365 +
350 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. 366 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
351 message SubscriptionInfoProto { 367 message SubscriptionInfoProto {
352 int64 lastActivityTime = 1; 368 int64 lastActivityTime = 1;
@@ -552,6 +568,7 @@ message TransportApiRequestMsg { @@ -552,6 +568,7 @@ message TransportApiRequestMsg {
552 ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; 568 ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
553 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; 569 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
554 GetResourceRequestMsg resourceRequestMsg = 9; 570 GetResourceRequestMsg resourceRequestMsg = 9;
  571 + GetFirmwareRequestMsg firmwareRequestMsg = 10;
555 } 572 }
556 573
557 /* Response from ThingsBoard Core Service to Transport Service */ 574 /* Response from ThingsBoard Core Service to Transport Service */
@@ -562,6 +579,7 @@ message TransportApiResponseMsg { @@ -562,6 +579,7 @@ message TransportApiResponseMsg {
562 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; 579 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
563 LwM2MResponseMsg lwM2MResponseMsg = 6; 580 LwM2MResponseMsg lwM2MResponseMsg = 6;
564 GetResourceResponseMsg resourceResponseMsg = 7; 581 GetResourceResponseMsg resourceResponseMsg = 7;
  582 + GetFirmwareResponseMsg firmwareResponseMsg = 8;
565 } 583 }
566 584
567 /* Messages that are handled by ThingsBoard Core Service */ 585 /* Messages that are handled by ThingsBoard Core Service */
@@ -398,7 +398,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -398,7 +398,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
398 @Override 398 @Override
399 public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) { 399 public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) {
400 CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED; 400 CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED;
401 - if (!msg.getStatus().equals(TransportProtos.ProvisionResponseStatus.SUCCESS)) { 401 + if (!msg.getStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
402 responseCode = CoAP.ResponseCode.BAD_REQUEST; 402 responseCode = CoAP.ResponseCode.BAD_REQUEST;
403 } 403 }
404 if (payloadType.equals(TransportPayloadType.JSON)) { 404 if (payloadType.equals(TransportPayloadType.JSON)) {
@@ -20,7 +20,10 @@ import com.google.gson.JsonParser; @@ -20,7 +20,10 @@ import com.google.gson.JsonParser;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
21 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 22 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  23 +import org.springframework.core.io.ByteArrayResource;
  24 +import org.springframework.http.HttpHeaders;
23 import org.springframework.http.HttpStatus; 25 import org.springframework.http.HttpStatus;
  26 +import org.springframework.http.MediaType;
24 import org.springframework.http.ResponseEntity; 27 import org.springframework.http.ResponseEntity;
25 import org.springframework.util.StringUtils; 28 import org.springframework.util.StringUtils;
26 import org.springframework.web.bind.annotation.PathVariable; 29 import org.springframework.web.bind.annotation.PathVariable;
@@ -41,7 +44,6 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator; @@ -41,7 +44,6 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
41 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 44 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
42 import org.thingsboard.server.gen.transport.TransportProtos; 45 import org.thingsboard.server.gen.transport.TransportProtos;
43 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; 46 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
44 -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;  
45 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; 47 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
46 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; 48 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
47 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; 49 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
@@ -53,7 +55,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs @@ -53,7 +55,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs
53 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; 55 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
54 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; 56 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
55 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; 57 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
56 -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;  
57 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; 58 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
58 59
59 import javax.servlet.http.HttpServletRequest; 60 import javax.servlet.http.HttpServletRequest;
@@ -204,6 +205,23 @@ public class DeviceApiController { @@ -204,6 +205,23 @@ public class DeviceApiController {
204 return responseWriter; 205 return responseWriter;
205 } 206 }
206 207
  208 + @RequestMapping(value = "/{deviceToken}/firmware", method = RequestMethod.GET)
  209 + public DeferredResult<ResponseEntity> getFirmware(@PathVariable("deviceToken") String deviceToken,
  210 + @RequestParam(value = "chunkSize", required = false, defaultValue = "0") int chunkSize,
  211 + @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
  212 + DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
  213 + transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
  214 + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
  215 + TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
  216 + .setTenantIdMSB(sessionInfo.getTenantIdMSB())
  217 + .setTenantIdLSB(sessionInfo.getTenantIdLSB())
  218 + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
  219 + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build();
  220 + transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, chunkSize, chunk));
  221 + }));
  222 + return responseWriter;
  223 + }
  224 +
207 @RequestMapping(value = "/provision", method = RequestMethod.POST) 225 @RequestMapping(value = "/provision", method = RequestMethod.POST)
208 public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) { 226 public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) {
209 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>(); 227 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
@@ -258,6 +276,41 @@ public class DeviceApiController { @@ -258,6 +276,41 @@ public class DeviceApiController {
258 } 276 }
259 } 277 }
260 278
  279 + private class GetFirmwareCallback implements TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> {
  280 + private final DeferredResult<ResponseEntity> responseWriter;
  281 + private final int chuckSize;
  282 + private final int chuck;
  283 +
  284 + GetFirmwareCallback(DeferredResult<ResponseEntity> responseWriter, int chuckSize, int chuck) {
  285 + this.responseWriter = responseWriter;
  286 + this.chuckSize = chuckSize;
  287 + this.chuck = chuck;
  288 + }
  289 +
  290 + @Override
  291 + public void onSuccess(TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg) {
  292 + if (!TransportProtos.ResponseStatus.SUCCESS.equals(firmwareResponseMsg.getResponseStatus())) {
  293 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND));
  294 + } else {
  295 + String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString();
  296 + ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareCacheReader().get(firmwareId, chuckSize, chuck));
  297 + ResponseEntity<ByteArrayResource> response = ResponseEntity.ok()
  298 + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmwareResponseMsg.getFileName())
  299 + .header("x-filename", firmwareResponseMsg.getFileName())
  300 + .contentLength(resource.contentLength())
  301 + .contentType(parseMediaType(firmwareResponseMsg.getContentType()))
  302 + .body(resource);
  303 + responseWriter.setResult(response);
  304 + }
  305 + }
  306 +
  307 + @Override
  308 + public void onError(Throwable e) {
  309 + log.warn("Failed to process request", e);
  310 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  311 + }
  312 + }
  313 +
261 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> { 314 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
262 private final TransportService transportService; 315 private final TransportService transportService;
263 private final SessionInfoProto sessionInfo; 316 private final SessionInfoProto sessionInfo;
@@ -338,4 +391,12 @@ public class DeviceApiController { @@ -338,4 +391,12 @@ public class DeviceApiController {
338 .build(), TransportServiceCallback.EMPTY); 391 .build(), TransportServiceCallback.EMPTY);
339 } 392 }
340 393
  394 + private static MediaType parseMediaType(String contentType) {
  395 + try {
  396 + return MediaType.parseMediaType(contentType);
  397 + } catch (Exception e) {
  398 + return MediaType.APPLICATION_OCTET_STREAM;
  399 + }
  400 + }
  401 +
341 } 402 }
@@ -29,7 +29,6 @@ import redis.clients.jedis.ScanResult; @@ -29,7 +29,6 @@ import redis.clients.jedis.ScanResult;
29 import java.util.Collection; 29 import java.util.Collection;
30 import java.util.LinkedList; 30 import java.util.LinkedList;
31 31
32 -@Service  
33 public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { 32 public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
34 private static final String SEC_EP = "SEC#EP#"; 33 private static final String SEC_EP = "SEC#EP#";
35 34
@@ -40,12 +40,14 @@ import io.netty.util.ReferenceCountUtil; @@ -40,12 +40,14 @@ import io.netty.util.ReferenceCountUtil;
40 import io.netty.util.concurrent.Future; 40 import io.netty.util.concurrent.Future;
41 import io.netty.util.concurrent.GenericFutureListener; 41 import io.netty.util.concurrent.GenericFutureListener;
42 import lombok.extern.slf4j.Slf4j; 42 import lombok.extern.slf4j.Slf4j;
  43 +import org.apache.commons.lang3.StringUtils;
43 import org.thingsboard.server.common.data.DataConstants; 44 import org.thingsboard.server.common.data.DataConstants;
44 import org.thingsboard.server.common.data.Device; 45 import org.thingsboard.server.common.data.Device;
45 import org.thingsboard.server.common.data.DeviceProfile; 46 import org.thingsboard.server.common.data.DeviceProfile;
46 import org.thingsboard.server.common.data.DeviceTransportType; 47 import org.thingsboard.server.common.data.DeviceTransportType;
47 import org.thingsboard.server.common.data.TransportPayloadType; 48 import org.thingsboard.server.common.data.TransportPayloadType;
48 import org.thingsboard.server.common.data.device.profile.MqttTopics; 49 import org.thingsboard.server.common.data.device.profile.MqttTopics;
  50 +import org.thingsboard.server.common.data.id.FirmwareId;
49 import org.thingsboard.server.common.msg.EncryptionUtil; 51 import org.thingsboard.server.common.msg.EncryptionUtil;
50 import org.thingsboard.server.common.msg.tools.TbRateLimitsException; 52 import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
51 import org.thingsboard.server.common.transport.SessionMsgListener; 53 import org.thingsboard.server.common.transport.SessionMsgListener;
@@ -69,10 +71,10 @@ import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; @@ -69,10 +71,10 @@ import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
69 import org.thingsboard.server.common.transport.util.SslUtil; 71 import org.thingsboard.server.common.transport.util.SslUtil;
70 72
71 import javax.net.ssl.SSLPeerUnverifiedException; 73 import javax.net.ssl.SSLPeerUnverifiedException;
72 -import java.security.cert.Certificate;  
73 -import java.security.cert.X509Certificate;  
74 import java.io.IOException; 74 import java.io.IOException;
75 import java.net.InetSocketAddress; 75 import java.net.InetSocketAddress;
  76 +import java.security.cert.Certificate;
  77 +import java.security.cert.X509Certificate;
76 import java.util.ArrayList; 78 import java.util.ArrayList;
77 import java.util.List; 79 import java.util.List;
78 import java.util.Optional; 80 import java.util.Optional;
@@ -80,7 +82,10 @@ import java.util.UUID; @@ -80,7 +82,10 @@ import java.util.UUID;
80 import java.util.concurrent.ConcurrentHashMap; 82 import java.util.concurrent.ConcurrentHashMap;
81 import java.util.concurrent.ConcurrentMap; 83 import java.util.concurrent.ConcurrentMap;
82 import java.util.concurrent.TimeUnit; 84 import java.util.concurrent.TimeUnit;
  85 +import java.util.regex.Matcher;
  86 +import java.util.regex.Pattern;
83 87
  88 +import static com.amazonaws.util.StringUtils.UTF8;
84 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; 89 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
85 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; 90 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
86 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; 91 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
@@ -99,6 +104,10 @@ import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE; @@ -99,6 +104,10 @@ import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE;
99 @Slf4j 104 @Slf4j
100 public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>>, SessionMsgListener { 105 public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
101 106
  107 + private static final Pattern FW_PATTERN = Pattern.compile("v2/fw/request/(?<requestId>\\d+)/chunk/(?<chunk>\\d+)");
  108 +
  109 + private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE";
  110 +
102 private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE; 111 private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE;
103 112
104 private final UUID sessionId; 113 private final UUID sessionId;
@@ -112,6 +121,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -112,6 +121,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
112 private volatile InetSocketAddress address; 121 private volatile InetSocketAddress address;
113 private volatile GatewaySessionHandler gatewaySessionHandler; 122 private volatile GatewaySessionHandler gatewaySessionHandler;
114 123
  124 + private final ConcurrentHashMap<String, String> fwSessions;
  125 + private final ConcurrentHashMap<String, Integer> fwChunkSizes;
  126 +
115 MqttTransportHandler(MqttTransportContext context, SslHandler sslHandler) { 127 MqttTransportHandler(MqttTransportContext context, SslHandler sslHandler) {
116 this.sessionId = UUID.randomUUID(); 128 this.sessionId = UUID.randomUUID();
117 this.context = context; 129 this.context = context;
@@ -120,6 +132,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -120,6 +132,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
120 this.sslHandler = sslHandler; 132 this.sslHandler = sslHandler;
121 this.mqttQoSMap = new ConcurrentHashMap<>(); 133 this.mqttQoSMap = new ConcurrentHashMap<>();
122 this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context); 134 this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context);
  135 + this.fwSessions = new ConcurrentHashMap<>();
  136 + this.fwChunkSizes = new ConcurrentHashMap<>();
123 } 137 }
124 138
125 @Override 139 @Override
@@ -280,6 +294,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -280,6 +294,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
280 294
281 private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { 295 private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
282 try { 296 try {
  297 + Matcher fwMatcher;
283 MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); 298 MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
284 if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { 299 if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
285 TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); 300 TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
@@ -299,6 +314,38 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -299,6 +314,38 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
299 } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { 314 } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) {
300 TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); 315 TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg);
301 transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); 316 transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg));
  317 + } else if ((fwMatcher = FW_PATTERN.matcher(topicName)).find()) {
  318 + String payload = mqttMsg.content().toString(UTF8);
  319 + int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0;
  320 + String requestId = fwMatcher.group("requestId");
  321 + int chunk = Integer.parseInt(fwMatcher.group("chunk"));
  322 +
  323 + if (chunkSize > 0) {
  324 + this.fwChunkSizes.put(requestId, chunkSize);
  325 + } else {
  326 + chunkSize = fwChunkSizes.getOrDefault(requestId, 0);
  327 + }
  328 +
  329 + if (chunkSize > context.getMaxPayloadSize()) {
  330 + sendFirmwareError(ctx, PAYLOAD_TOO_LARGE);
  331 + return;
  332 + }
  333 +
  334 + String firmwareId = fwSessions.get(requestId);
  335 +
  336 + if (firmwareId != null) {
  337 + sendFirmware(ctx, mqttMsg.variableHeader().packetId(), firmwareId, requestId, chunkSize, chunk);
  338 + } else {
  339 + TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo();
  340 + TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
  341 + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
  342 + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
  343 + .setTenantIdMSB(sessionInfo.getTenantIdMSB())
  344 + .setTenantIdLSB(sessionInfo.getTenantIdLSB())
  345 + .build();
  346 + transportService.process(deviceSessionCtx.getSessionInfo(), getFirmwareRequestMsg,
  347 + new FirmwareCallback(ctx, mqttMsg.variableHeader().packetId(), getFirmwareRequestMsg, requestId, chunkSize, chunk));
  348 + }
302 } else { 349 } else {
303 transportService.reportActivity(deviceSessionCtx.getSessionInfo()); 350 transportService.reportActivity(deviceSessionCtx.getSessionInfo());
304 ack(ctx, msgId); 351 ack(ctx, msgId);
@@ -366,6 +413,65 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -366,6 +413,65 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
366 } 413 }
367 } 414 }
368 415
  416 + private class FirmwareCallback implements TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> {
  417 + private final ChannelHandlerContext ctx;
  418 + private final int msgId;
  419 + private final TransportProtos.GetFirmwareRequestMsg msg;
  420 + private final String requestId;
  421 + private final int chunkSize;
  422 + private final int chunk;
  423 +
  424 + FirmwareCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetFirmwareRequestMsg msg, String requestId, int chunkSize, int chunk) {
  425 + this.ctx = ctx;
  426 + this.msgId = msgId;
  427 + this.msg = msg;
  428 + this.requestId = requestId;
  429 + this.chunkSize = chunkSize;
  430 + this.chunk = chunk;
  431 + }
  432 +
  433 + @Override
  434 + public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
  435 + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
  436 + FirmwareId firmwareId = new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB()));
  437 + fwSessions.put(requestId, firmwareId.toString());
  438 + sendFirmware(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk);
  439 + } else {
  440 + sendFirmwareError(ctx, response.getResponseStatus().toString());
  441 + }
  442 + }
  443 +
  444 + @Override
  445 + public void onError(Throwable e) {
  446 + log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
  447 + processDisconnect(ctx);
  448 + }
  449 + }
  450 +
  451 + private void sendFirmware(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk) {
  452 + log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
  453 + ack(ctx, msgId);
  454 + try {
  455 + byte[] firmwareChunk = context.getFirmwareCacheReader().get(firmwareId, chunkSize, chunk);
  456 + deviceSessionCtx.getPayloadAdaptor()
  457 + .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk)
  458 + .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
  459 + if (firmwareChunk != null && chunkSize != firmwareChunk.length) {
  460 + scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS);
  461 + }
  462 + } catch (Exception e) {
  463 + log.trace("[{}] Failed to send firmware response!", sessionId, e);
  464 + }
  465 + }
  466 +
  467 + private void sendFirmwareError(ChannelHandlerContext ctx, String error) {
  468 + log.warn("[{}] {}", sessionId, error);
  469 + deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx
  470 + .getPayloadAdaptor()
  471 + .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes()));
  472 + processDisconnect(ctx);
  473 + }
  474 +
369 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { 475 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
370 if (!checkConnected(ctx, mqttMsg)) { 476 if (!checkConnected(ctx, mqttMsg)) {
371 return; 477 return;
@@ -396,6 +502,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -396,6 +502,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
396 case MqttTopics.GATEWAY_RPC_TOPIC: 502 case MqttTopics.GATEWAY_RPC_TOPIC:
397 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: 503 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
398 case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: 504 case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
  505 + case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
  506 + case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
399 registerSubQoS(topic, grantedQoSList, reqQoS); 507 registerSubQoS(topic, grantedQoSList, reqQoS);
400 break; 508 break;
401 default: 509 default:
@@ -21,8 +21,6 @@ import com.google.gson.JsonObject; @@ -21,8 +21,6 @@ import com.google.gson.JsonObject;
21 import com.google.gson.JsonParser; 21 import com.google.gson.JsonParser;
22 import com.google.gson.JsonSyntaxException; 22 import com.google.gson.JsonSyntaxException;
23 import io.netty.buffer.ByteBuf; 23 import io.netty.buffer.ByteBuf;
24 -import io.netty.buffer.ByteBufAllocator;  
25 -import io.netty.buffer.UnpooledByteBufAllocator;  
26 import io.netty.handler.codec.mqtt.MqttFixedHeader; 24 import io.netty.handler.codec.mqtt.MqttFixedHeader;
27 import io.netty.handler.codec.mqtt.MqttMessage; 25 import io.netty.handler.codec.mqtt.MqttMessage;
28 import io.netty.handler.codec.mqtt.MqttMessageType; 26 import io.netty.handler.codec.mqtt.MqttMessageType;
@@ -31,10 +29,10 @@ import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; @@ -31,10 +29,10 @@ import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
31 import lombok.extern.slf4j.Slf4j; 29 import lombok.extern.slf4j.Slf4j;
32 import org.springframework.stereotype.Component; 30 import org.springframework.stereotype.Component;
33 import org.springframework.util.StringUtils; 31 import org.springframework.util.StringUtils;
  32 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
34 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 33 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
35 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 34 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
36 import org.thingsboard.server.gen.transport.TransportProtos; 35 import org.thingsboard.server.gen.transport.TransportProtos;
37 -import org.thingsboard.server.common.data.device.profile.MqttTopics;  
38 import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; 36 import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
39 37
40 import java.nio.charset.Charset; 38 import java.nio.charset.Charset;
@@ -55,7 +53,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @@ -55,7 +53,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
55 protected static final Charset UTF8 = StandardCharsets.UTF_8; 53 protected static final Charset UTF8 = StandardCharsets.UTF_8;
56 54
57 private static final Gson GSON = new Gson(); 55 private static final Gson GSON = new Gson();
58 - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);  
59 56
60 @Override 57 @Override
61 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { 58 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
@@ -153,6 +150,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @@ -153,6 +150,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
153 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); 150 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
154 } 151 }
155 152
  153 + @Override
  154 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) {
  155 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/chunk/" + chunk, firmwareChunk));
  156 + }
  157 +
156 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { 158 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
157 String payload = validatePayload(sessionId, payloadData, false); 159 String payload = validatePayload(sessionId, payloadData, false);
158 try { 160 try {
@@ -15,8 +15,14 @@ @@ -15,8 +15,14 @@
15 */ 15 */
16 package org.thingsboard.server.transport.mqtt.adaptors; 16 package org.thingsboard.server.transport.mqtt.adaptors;
17 17
  18 +import io.netty.buffer.ByteBuf;
  19 +import io.netty.buffer.ByteBufAllocator;
  20 +import io.netty.buffer.UnpooledByteBufAllocator;
  21 +import io.netty.handler.codec.mqtt.MqttFixedHeader;
18 import io.netty.handler.codec.mqtt.MqttMessage; 22 import io.netty.handler.codec.mqtt.MqttMessage;
  23 +import io.netty.handler.codec.mqtt.MqttMessageType;
19 import io.netty.handler.codec.mqtt.MqttPublishMessage; 24 import io.netty.handler.codec.mqtt.MqttPublishMessage;
  25 +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
20 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 26 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
21 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; 27 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
22 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; 28 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
@@ -39,6 +45,8 @@ import java.util.Optional; @@ -39,6 +45,8 @@ import java.util.Optional;
39 */ 45 */
40 public interface MqttTransportAdaptor { 46 public interface MqttTransportAdaptor {
41 47
  48 + ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
  49 +
42 PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; 50 PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
43 51
44 PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; 52 PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
@@ -69,4 +77,14 @@ public interface MqttTransportAdaptor { @@ -69,4 +77,14 @@ public interface MqttTransportAdaptor {
69 77
70 Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; 78 Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
71 79
  80 + Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException;
  81 +
  82 + default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) {
  83 + MqttFixedHeader mqttFixedHeader =
  84 + new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
  85 + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
  86 + ByteBuf payload = ALLOCATOR.buffer();
  87 + payload.writeBytes(payloadInBytes);
  88 + return new MqttPublishMessage(mqttFixedHeader, header, payload);
  89 + }
72 } 90 }
@@ -21,13 +21,8 @@ import com.google.protobuf.DynamicMessage; @@ -21,13 +21,8 @@ import com.google.protobuf.DynamicMessage;
21 import com.google.protobuf.InvalidProtocolBufferException; 21 import com.google.protobuf.InvalidProtocolBufferException;
22 import com.google.protobuf.util.JsonFormat; 22 import com.google.protobuf.util.JsonFormat;
23 import io.netty.buffer.ByteBuf; 23 import io.netty.buffer.ByteBuf;
24 -import io.netty.buffer.ByteBufAllocator;  
25 -import io.netty.buffer.UnpooledByteBufAllocator;  
26 -import io.netty.handler.codec.mqtt.MqttFixedHeader;  
27 import io.netty.handler.codec.mqtt.MqttMessage; 24 import io.netty.handler.codec.mqtt.MqttMessage;
28 -import io.netty.handler.codec.mqtt.MqttMessageType;  
29 import io.netty.handler.codec.mqtt.MqttPublishMessage; 25 import io.netty.handler.codec.mqtt.MqttPublishMessage;
30 -import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;  
31 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
32 import org.springframework.stereotype.Component; 27 import org.springframework.stereotype.Component;
33 import org.springframework.util.StringUtils; 28 import org.springframework.util.StringUtils;
@@ -46,8 +41,6 @@ import java.util.Optional; @@ -46,8 +41,6 @@ import java.util.Optional;
46 @Slf4j 41 @Slf4j
47 public class ProtoMqttAdaptor implements MqttTransportAdaptor { 42 public class ProtoMqttAdaptor implements MqttTransportAdaptor {
48 43
49 - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);  
50 -  
51 @Override 44 @Override
52 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { 45 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
53 DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; 46 DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
@@ -143,7 +136,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -143,7 +136,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
143 } 136 }
144 } 137 }
145 138
146 -  
147 @Override 139 @Override
148 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { 140 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
149 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest))); 141 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest)));
@@ -165,6 +157,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -165,6 +157,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
165 } 157 }
166 158
167 @Override 159 @Override
  160 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException {
  161 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk));
  162 + }
  163 +
  164 + @Override
168 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { 165 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
169 if (!StringUtils.isEmpty(responseMsg.getError())) { 166 if (!StringUtils.isEmpty(responseMsg.getError())) {
170 throw new AdaptorException(responseMsg.getError()); 167 throw new AdaptorException(responseMsg.getError());
@@ -202,15 +199,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -202,15 +199,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
202 return bytes; 199 return bytes;
203 } 200 }
204 201
205 - private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadBytes) {  
206 - MqttFixedHeader mqttFixedHeader =  
207 - new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);  
208 - MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());  
209 - ByteBuf payload = ALLOCATOR.buffer();  
210 - payload.writeBytes(payloadBytes);  
211 - return new MqttPublishMessage(mqttFixedHeader, header, payload);  
212 - }  
213 -  
214 private int getRequestId(String topicName, String topic) { 202 private int getRequestId(String topicName, String topic) {
215 return Integer.parseInt(topicName.substring(topic.length())); 203 return Integer.parseInt(topicName.substring(topic.length()));
216 } 204 }
@@ -20,10 +20,9 @@ import lombok.Data; @@ -20,10 +20,9 @@ import lombok.Data;
20 import lombok.Getter; 20 import lombok.Getter;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
22 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
23 -import org.springframework.stereotype.Service; 23 +import org.thingsboard.server.cache.firmware.FirmwareCacheReader;
24 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; 24 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
25 import org.thingsboard.server.queue.scheduler.SchedulerComponent; 25 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
26 -import org.thingsboard.server.queue.util.TbTransportComponent;  
27 26
28 import javax.annotation.PostConstruct; 27 import javax.annotation.PostConstruct;
29 import javax.annotation.PreDestroy; 28 import javax.annotation.PreDestroy;
@@ -51,6 +50,11 @@ public abstract class TransportContext { @@ -51,6 +50,11 @@ public abstract class TransportContext {
51 @Getter 50 @Getter
52 private ExecutorService executor; 51 private ExecutorService executor;
53 52
  53 +
  54 + @Getter
  55 + @Autowired
  56 + private FirmwareCacheReader firmwareCacheReader;
  57 +
54 @PostConstruct 58 @PostConstruct
55 public void init() { 59 public void init() {
56 executor = Executors.newWorkStealingPool(50); 60 executor = Executors.newWorkStealingPool(50);
@@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; 24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
25 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; 25 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
26 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; 26 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
  27 +import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareRequestMsg;
  28 +import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareResponseMsg;
27 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; 29 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
28 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; 30 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
29 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceResponseMsg; 31 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceResponseMsg;
@@ -98,6 +100,8 @@ public interface TransportService { @@ -98,6 +100,8 @@ public interface TransportService {
98 100
99 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback); 101 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
100 102
  103 + void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback<GetFirmwareResponseMsg> callback);
  104 +
101 SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); 105 SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
102 106
103 SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); 107 SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
@@ -44,7 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; @@ -44,7 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
44 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; 44 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
45 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; 45 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
46 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; 46 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
47 -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; 47 +import org.thingsboard.server.gen.transport.TransportProtos.ResponseStatus;
48 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; 48 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
49 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; 49 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
50 import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; 50 import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg;
@@ -423,12 +423,12 @@ public class JsonConverter { @@ -423,12 +423,12 @@ public class JsonConverter {
423 423
424 private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { 424 private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
425 JsonObject result = new JsonObject(); 425 JsonObject result = new JsonObject();
426 - if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { 426 + if (payload.getStatus() == ResponseStatus.NOT_FOUND) {
427 result.addProperty("errorMsg", "Provision data was not found!"); 427 result.addProperty("errorMsg", "Provision data was not found!");
428 - result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name());  
429 - } else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { 428 + result.addProperty("status", ResponseStatus.NOT_FOUND.name());
  429 + } else if (payload.getStatus() == TransportProtos.ResponseStatus.FAILURE) {
430 result.addProperty("errorMsg", "Failed to provision device!"); 430 result.addProperty("errorMsg", "Failed to provision device!");
431 - result.addProperty("status", ProvisionResponseStatus.FAILURE.name()); 431 + result.addProperty("status", ResponseStatus.FAILURE.name());
432 } else { 432 } else {
433 if (toGateway) { 433 if (toGateway) {
434 result.addProperty("id", requestId); 434 result.addProperty("id", requestId);
@@ -445,7 +445,7 @@ public class JsonConverter { @@ -445,7 +445,7 @@ public class JsonConverter {
445 break; 445 break;
446 } 446 }
447 result.addProperty("credentialsType", payload.getCredentialsType().name()); 447 result.addProperty("credentialsType", payload.getCredentialsType().name());
448 - result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); 448 + result.addProperty("status", ResponseStatus.SUCCESS.name());
449 } 449 }
450 return result; 450 return result;
451 } 451 }
@@ -377,7 +377,6 @@ public class DefaultTransportService implements TransportService { @@ -377,7 +377,6 @@ public class DefaultTransportService implements TransportService {
377 AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); 377 AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
378 } 378 }
379 379
380 -  
381 @Override 380 @Override
382 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) { 381 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
383 if (log.isTraceEnabled()) { 382 if (log.isTraceEnabled()) {
@@ -530,6 +529,19 @@ public class DefaultTransportService implements TransportService { @@ -530,6 +529,19 @@ public class DefaultTransportService implements TransportService {
530 } 529 }
531 530
532 @Override 531 @Override
  532 + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetFirmwareRequestMsg msg, TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> callback) {
  533 + if (checkLimits(sessionInfo, msg, callback)) {
  534 + TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg =
  535 + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setFirmwareRequestMsg(msg).build());
  536 +
  537 + AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), response -> {
  538 + TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg = response.getValue().getFirmwareResponseMsg();
  539 + callback.onSuccess(firmwareResponseMsg);
  540 + }, callback::onError, transportCallbackExecutor);
  541 + }
  542 + }
  543 +
  544 + @Override
533 public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) { 545 public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
534 return reportActivityInternal(sessionInfo); 546 return reportActivityInternal(sessionInfo);
535 } 547 }
@@ -608,11 +620,11 @@ public class DefaultTransportService implements TransportService { @@ -608,11 +620,11 @@ public class DefaultTransportService implements TransportService {
608 sessions.remove(toSessionId(sessionInfo)); 620 sessions.remove(toSessionId(sessionInfo));
609 } 621 }
610 622
611 - private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) { 623 + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback) {
612 return checkLimits(sessionInfo, msg, callback, 0); 624 return checkLimits(sessionInfo, msg, callback, 0);
613 } 625 }
614 626
615 - private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints) { 627 + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback, int dataPoints) {
616 if (log.isTraceEnabled()) { 628 if (log.isTraceEnabled()) {
617 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); 629 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
618 } 630 }
@@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo; @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
40 import org.thingsboard.server.common.data.DeviceProfileProvisionType; 40 import org.thingsboard.server.common.data.DeviceProfileProvisionType;
41 import org.thingsboard.server.common.data.DeviceProfileType; 41 import org.thingsboard.server.common.data.DeviceProfileType;
42 import org.thingsboard.server.common.data.DeviceTransportType; 42 import org.thingsboard.server.common.data.DeviceTransportType;
  43 +import org.thingsboard.server.common.data.Firmware;
43 import org.thingsboard.server.common.data.Tenant; 44 import org.thingsboard.server.common.data.Tenant;
44 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; 45 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
45 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; 46 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
@@ -59,6 +60,7 @@ import org.thingsboard.server.common.data.page.PageData; @@ -59,6 +60,7 @@ import org.thingsboard.server.common.data.page.PageData;
59 import org.thingsboard.server.common.data.page.PageLink; 60 import org.thingsboard.server.common.data.page.PageLink;
60 import org.thingsboard.server.dao.entity.AbstractEntityService; 61 import org.thingsboard.server.dao.entity.AbstractEntityService;
61 import org.thingsboard.server.dao.exception.DataValidationException; 62 import org.thingsboard.server.dao.exception.DataValidationException;
  63 +import org.thingsboard.server.dao.firmware.FirmwareService;
62 import org.thingsboard.server.dao.service.DataValidator; 64 import org.thingsboard.server.dao.service.DataValidator;
63 import org.thingsboard.server.dao.service.PaginatedRemover; 65 import org.thingsboard.server.dao.service.PaginatedRemover;
64 import org.thingsboard.server.dao.service.Validator; 66 import org.thingsboard.server.dao.service.Validator;
@@ -107,6 +109,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -107,6 +109,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
107 @Autowired 109 @Autowired
108 private CacheManager cacheManager; 110 private CacheManager cacheManager;
109 111
  112 + @Autowired
  113 + private FirmwareService firmwareService;
  114 +
110 private final Lock findOrCreateLock = new ReentrantLock(); 115 private final Lock findOrCreateLock = new ReentrantLock();
111 116
112 @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") 117 @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}")
@@ -389,6 +394,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @@ -389,6 +394,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
389 } 394 }
390 } 395 }
391 396
  397 + if (deviceProfile.getFirmwareId() != null) {
  398 + Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId());
  399 + if (firmware == null) {
  400 + throw new DataValidationException("Can't assign non-existent firmware!");
  401 + }
  402 + if (firmware.getData() == null) {
  403 + throw new DataValidationException("Can't assign firmware with empty data!");
  404 + }
  405 + }
392 } 406 }
393 407
394 @Override 408 @Override
@@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.DeviceProfile; @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
39 import org.thingsboard.server.common.data.EntitySubtype; 39 import org.thingsboard.server.common.data.EntitySubtype;
40 import org.thingsboard.server.common.data.EntityType; 40 import org.thingsboard.server.common.data.EntityType;
41 import org.thingsboard.server.common.data.EntityView; 41 import org.thingsboard.server.common.data.EntityView;
  42 +import org.thingsboard.server.common.data.Firmware;
42 import org.thingsboard.server.common.data.Tenant; 43 import org.thingsboard.server.common.data.Tenant;
43 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 44 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
44 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 45 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
@@ -68,6 +69,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -68,6 +69,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
68 import org.thingsboard.server.dao.entityview.EntityViewService; 69 import org.thingsboard.server.dao.entityview.EntityViewService;
69 import org.thingsboard.server.dao.event.EventService; 70 import org.thingsboard.server.dao.event.EventService;
70 import org.thingsboard.server.dao.exception.DataValidationException; 71 import org.thingsboard.server.dao.exception.DataValidationException;
  72 +import org.thingsboard.server.dao.firmware.FirmwareService;
71 import org.thingsboard.server.dao.service.DataValidator; 73 import org.thingsboard.server.dao.service.DataValidator;
72 import org.thingsboard.server.dao.service.PaginatedRemover; 74 import org.thingsboard.server.dao.service.PaginatedRemover;
73 import org.thingsboard.server.dao.tenant.TbTenantProfileCache; 75 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@@ -128,6 +130,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @@ -128,6 +130,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
128 @Lazy 130 @Lazy
129 private TbTenantProfileCache tenantProfileCache; 131 private TbTenantProfileCache tenantProfileCache;
130 132
  133 + @Autowired
  134 + private FirmwareService firmwareService;
  135 +
131 @Override 136 @Override
132 public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { 137 public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
133 log.trace("Executing findDeviceInfoById [{}]", deviceId); 138 log.trace("Executing findDeviceInfoById [{}]", deviceId);
@@ -598,6 +603,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @@ -598,6 +603,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
598 throw new DataValidationException("Can't assign device to customer from different tenant!"); 603 throw new DataValidationException("Can't assign device to customer from different tenant!");
599 } 604 }
600 } 605 }
  606 +
  607 + if (device.getFirmwareId() != null) {
  608 + Firmware firmware = firmwareService.findFirmwareById(tenantId, device.getFirmwareId());
  609 + if (firmware == null) {
  610 + throw new DataValidationException("Can't assign non-existent firmware!");
  611 + }
  612 + if (firmware.getData() == null) {
  613 + throw new DataValidationException("Can't assign firmware with empty data!");
  614 + }
  615 + }
601 } 616 }
602 }; 617 };
603 618
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.firmware;
  17 +
  18 +import com.google.common.hash.HashFunction;
  19 +import com.google.common.hash.Hashing;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.apache.commons.lang3.StringUtils;
  22 +import org.hibernate.exception.ConstraintViolationException;
  23 +import org.springframework.cache.Cache;
  24 +import org.springframework.cache.CacheManager;
  25 +import org.springframework.stereotype.Service;
  26 +import org.thingsboard.server.common.data.Firmware;
  27 +import org.thingsboard.server.common.data.FirmwareInfo;
  28 +import org.thingsboard.server.common.data.Tenant;
  29 +import org.thingsboard.server.common.data.id.FirmwareId;
  30 +import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.dao.exception.DataValidationException;
  34 +import org.thingsboard.server.dao.service.DataValidator;
  35 +import org.thingsboard.server.dao.service.PaginatedRemover;
  36 +import org.thingsboard.server.dao.tenant.TenantDao;
  37 +
  38 +import java.nio.ByteBuffer;
  39 +import java.util.Collections;
  40 +import java.util.Optional;
  41 +
  42 +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
  43 +import static org.thingsboard.server.dao.service.Validator.validateId;
  44 +import static org.thingsboard.server.dao.service.Validator.validatePageLink;
  45 +
  46 +@Service
  47 +@Slf4j
  48 +public class BaseFirmwareService implements FirmwareService {
  49 + public static final String INCORRECT_FIRMWARE_ID = "Incorrect firmwareId ";
  50 + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  51 +
  52 + private final TenantDao tenantDao;
  53 + private final FirmwareDao firmwareDao;
  54 + private final FirmwareInfoDao firmwareInfoDao;
  55 + private final CacheManager cacheManager;
  56 +
  57 + public BaseFirmwareService(TenantDao tenantDao, FirmwareDao firmwareDao, FirmwareInfoDao firmwareInfoDao, CacheManager cacheManager) {
  58 + this.tenantDao = tenantDao;
  59 + this.firmwareDao = firmwareDao;
  60 + this.firmwareInfoDao = firmwareInfoDao;
  61 + this.cacheManager = cacheManager;
  62 + }
  63 +
  64 + @Override
  65 + public FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo) {
  66 + log.trace("Executing saveFirmwareInfo [{}]", firmwareInfo);
  67 + firmwareInfoValidator.validate(firmwareInfo, FirmwareInfo::getTenantId);
  68 + try {
  69 + FirmwareId firmwareId = firmwareInfo.getId();
  70 + if (firmwareId != null) {
  71 + cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString());
  72 + }
  73 + return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo);
  74 + } catch (Exception t) {
  75 + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
  76 + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) {
  77 + throw new DataValidationException("Firmware with such title and version already exists!");
  78 + } else {
  79 + throw t;
  80 + }
  81 + }
  82 + }
  83 +
  84 + @Override
  85 + public Firmware saveFirmware(Firmware firmware) {
  86 + log.trace("Executing saveFirmware [{}]", firmware);
  87 + firmwareValidator.validate(firmware, FirmwareInfo::getTenantId);
  88 + try {
  89 + FirmwareId firmwareId = firmware.getId();
  90 + if (firmwareId != null) {
  91 + cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString());
  92 + }
  93 + return firmwareDao.save(firmware.getTenantId(), firmware);
  94 + } catch (Exception t) {
  95 + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
  96 + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) {
  97 + throw new DataValidationException("Firmware with such title and version already exists!");
  98 + } else {
  99 + throw t;
  100 + }
  101 + }
  102 + }
  103 +
  104 + @Override
  105 + public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) {
  106 + log.trace("Executing findFirmwareById [{}]", firmwareId);
  107 + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId);
  108 + return firmwareDao.findById(tenantId, firmwareId.getId());
  109 + }
  110 +
  111 + @Override
  112 + public FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId) {
  113 + log.trace("Executing findFirmwareInfoById [{}]", firmwareId);
  114 + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId);
  115 + return firmwareInfoDao.findById(tenantId, firmwareId.getId());
  116 + }
  117 +
  118 + @Override
  119 + public PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink) {
  120 + log.trace("Executing findTenantFirmwaresByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
  121 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  122 + validatePageLink(pageLink);
  123 + return firmwareInfoDao.findFirmwareInfoByTenantId(tenantId, pageLink);
  124 + }
  125 +
  126 + @Override
  127 + public PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) {
  128 + log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink);
  129 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  130 + validatePageLink(pageLink);
  131 + return firmwareInfoDao.findFirmwareInfoByTenantIdAndHasData(tenantId, hasData, pageLink);
  132 + }
  133 +
  134 + @Override
  135 + public void deleteFirmware(TenantId tenantId, FirmwareId firmwareId) {
  136 + log.trace("Executing deleteFirmware [{}]", firmwareId);
  137 + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId);
  138 + try {
  139 + Cache cache = cacheManager.getCache(FIRMWARE_CACHE);
  140 + cache.evict(Collections.singletonList(firmwareId));
  141 + firmwareDao.removeById(tenantId, firmwareId.getId());
  142 + } catch (Exception t) {
  143 + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
  144 + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device")) {
  145 + throw new DataValidationException("The firmware referenced by the devices cannot be deleted!");
  146 + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device_profile")) {
  147 + throw new DataValidationException("The firmware referenced by the device profile cannot be deleted!");
  148 + } else {
  149 + throw t;
  150 + }
  151 + }
  152 + }
  153 +
  154 + @Override
  155 + public void deleteFirmwaresByTenantId(TenantId tenantId) {
  156 + log.trace("Executing deleteFirmwaresByTenantId, tenantId [{}]", tenantId);
  157 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  158 + tenantFirmwareRemover.removeEntities(tenantId, tenantId);
  159 + }
  160 +
  161 + private DataValidator<FirmwareInfo> firmwareInfoValidator = new DataValidator<>() {
  162 +
  163 + @Override
  164 + protected void validateDataImpl(TenantId tenantId, FirmwareInfo firmware) {
  165 + if (firmware.getTenantId() == null) {
  166 + throw new DataValidationException("Firmware should be assigned to tenant!");
  167 + } else {
  168 + Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId());
  169 + if (tenant == null) {
  170 + throw new DataValidationException("Firmware is referencing to non-existent tenant!");
  171 + }
  172 + }
  173 +
  174 + if (StringUtils.isEmpty(firmware.getTitle())) {
  175 + throw new DataValidationException("Firmware title should be specified!");
  176 + }
  177 +
  178 + if (StringUtils.isEmpty(firmware.getVersion())) {
  179 + throw new DataValidationException("Firmware version should be specified!");
  180 + }
  181 + }
  182 +
  183 + @Override
  184 + protected void validateUpdate(TenantId tenantId, FirmwareInfo firmware) {
  185 + FirmwareInfo firmwareOld = firmwareInfoDao.findById(tenantId, firmware.getUuidId());
  186 +
  187 + if (!firmwareOld.getTitle().equals(firmware.getTitle())) {
  188 + throw new DataValidationException("Updating firmware title is prohibited!");
  189 + }
  190 +
  191 + if (!firmwareOld.getVersion().equals(firmware.getVersion())) {
  192 + throw new DataValidationException("Updating firmware version is prohibited!");
  193 + }
  194 + }
  195 + };
  196 +
  197 + private DataValidator<Firmware> firmwareValidator = new DataValidator<>() {
  198 +
  199 + @Override
  200 + protected void validateDataImpl(TenantId tenantId, Firmware firmware) {
  201 + if (firmware.getTenantId() == null) {
  202 + throw new DataValidationException("Firmware should be assigned to tenant!");
  203 + } else {
  204 + Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId());
  205 + if (tenant == null) {
  206 + throw new DataValidationException("Firmware is referencing to non-existent tenant!");
  207 + }
  208 + }
  209 +
  210 + if (StringUtils.isEmpty(firmware.getTitle())) {
  211 + throw new DataValidationException("Firmware title should be specified!");
  212 + }
  213 +
  214 + if (StringUtils.isEmpty(firmware.getVersion())) {
  215 + throw new DataValidationException("Firmware version should be specified!");
  216 + }
  217 +
  218 + if (StringUtils.isEmpty(firmware.getFileName())) {
  219 + throw new DataValidationException("Firmware file name should be specified!");
  220 + }
  221 +
  222 + if (StringUtils.isEmpty(firmware.getContentType())) {
  223 + throw new DataValidationException("Firmware content type should be specified!");
  224 + }
  225 +
  226 + ByteBuffer data = firmware.getData();
  227 + if (data == null || !data.hasArray() || data.array().length == 0) {
  228 + throw new DataValidationException("Firmware data should be specified!");
  229 + }
  230 +
  231 + if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) {
  232 + throw new DataValidationException("Firmware checksum algorithm should be specified!");
  233 + }
  234 + if (StringUtils.isEmpty(firmware.getChecksum())) {
  235 + throw new DataValidationException("Firmware checksum should be specified!");
  236 + }
  237 +
  238 + HashFunction hashFunction;
  239 + switch (firmware.getChecksumAlgorithm()) {
  240 + case "sha256":
  241 + hashFunction = Hashing.sha256();
  242 + break;
  243 + case "md5":
  244 + hashFunction = Hashing.md5();
  245 + break;
  246 + case "crc32":
  247 + hashFunction = Hashing.crc32();
  248 + break;
  249 + default:
  250 + throw new DataValidationException("Unknown checksum algorithm!");
  251 + }
  252 +
  253 + String currentChecksum = hashFunction.hashBytes(data.array()).toString();
  254 +
  255 + if (!currentChecksum.equals(firmware.getChecksum())) {
  256 + throw new DataValidationException("Wrong firmware file!");
  257 + }
  258 + }
  259 +
  260 + @Override
  261 + protected void validateUpdate(TenantId tenantId, Firmware firmware) {
  262 + Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId());
  263 +
  264 + if (!firmwareOld.getTitle().equals(firmware.getTitle())) {
  265 + throw new DataValidationException("Updating firmware title is prohibited!");
  266 + }
  267 +
  268 + if (!firmwareOld.getVersion().equals(firmware.getVersion())) {
  269 + throw new DataValidationException("Updating firmware version is prohibited!");
  270 + }
  271 +
  272 + if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) {
  273 + throw new DataValidationException("Updating firmware file name is prohibited!");
  274 + }
  275 +
  276 + if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) {
  277 + throw new DataValidationException("Updating firmware content type is prohibited!");
  278 + }
  279 +
  280 + if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) {
  281 + throw new DataValidationException("Updating firmware content type is prohibited!");
  282 + }
  283 +
  284 + if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) {
  285 + throw new DataValidationException("Updating firmware content type is prohibited!");
  286 + }
  287 +
  288 + if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) {
  289 + throw new DataValidationException("Updating firmware data is prohibited!");
  290 + }
  291 + }
  292 + };
  293 +
  294 + private PaginatedRemover<TenantId, FirmwareInfo> tenantFirmwareRemover =
  295 + new PaginatedRemover<>() {
  296 +
  297 + @Override
  298 + protected PageData<FirmwareInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
  299 + return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink);
  300 + }
  301 +
  302 + @Override
  303 + protected void removeEntity(TenantId tenantId, FirmwareInfo entity) {
  304 + deleteFirmware(tenantId, entity.getId());
  305 + }
  306 + };
  307 +
  308 + protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) {
  309 + if (t instanceof ConstraintViolationException) {
  310 + return Optional.of((ConstraintViolationException) t);
  311 + } else if (t.getCause() instanceof ConstraintViolationException) {
  312 + return Optional.of((ConstraintViolationException) (t.getCause()));
  313 + } else {
  314 + return Optional.empty();
  315 + }
  316 + }
  317 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.firmware;
  17 +
  18 +import org.thingsboard.server.common.data.Firmware;
  19 +import org.thingsboard.server.dao.Dao;
  20 +
  21 +public interface FirmwareDao extends Dao<Firmware> {
  22 +
  23 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.firmware;
  17 +
  18 +import org.thingsboard.server.common.data.FirmwareInfo;
  19 +import org.thingsboard.server.common.data.id.TenantId;
  20 +import org.thingsboard.server.common.data.page.PageData;
  21 +import org.thingsboard.server.common.data.page.PageLink;
  22 +import org.thingsboard.server.dao.Dao;
  23 +
  24 +import java.util.UUID;
  25 +
  26 +public interface FirmwareInfoDao extends Dao<FirmwareInfo> {
  27 +
  28 + PageData<FirmwareInfo> findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink);
  29 +
  30 + PageData<FirmwareInfo> findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink);
  31 +
  32 +}
@@ -153,6 +153,7 @@ public class ModelConstants { @@ -153,6 +153,7 @@ public class ModelConstants {
153 public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; 153 public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
154 public static final String DEVICE_DEVICE_PROFILE_ID_PROPERTY = "device_profile_id"; 154 public static final String DEVICE_DEVICE_PROFILE_ID_PROPERTY = "device_profile_id";
155 public static final String DEVICE_DEVICE_DATA_PROPERTY = "device_data"; 155 public static final String DEVICE_DEVICE_DATA_PROPERTY = "device_data";
  156 + public static final String DEVICE_FIRMWARE_ID_PROPERTY = "firmware_id";
156 157
157 public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; 158 public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
158 public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; 159 public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
@@ -176,6 +177,7 @@ public class ModelConstants { @@ -176,6 +177,7 @@ public class ModelConstants {
176 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; 177 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
177 public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; 178 public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
178 public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; 179 public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key";
  180 + public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id";
179 181
180 /** 182 /**
181 * Cassandra entityView constants. 183 * Cassandra entityView constants.
@@ -468,6 +470,22 @@ public class ModelConstants { @@ -468,6 +470,22 @@ public class ModelConstants {
468 public static final String RESOURCE_DATA_COLUMN = "data"; 470 public static final String RESOURCE_DATA_COLUMN = "data";
469 471
470 /** 472 /**
  473 + * Firmware constants.
  474 + */
  475 + public static final String FIRMWARE_TABLE_NAME = "firmware";
  476 + public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN;
  477 + public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY;
  478 + public static final String FIRMWARE_VERSION_COLUMN = "version";
  479 + public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name";
  480 + public static final String FIRMWARE_CONTENT_TYPE_COLUMN = "content_type";
  481 + public static final String FIRMWARE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm";
  482 + public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum";
  483 + public static final String FIRMWARE_DATA_COLUMN = "data";
  484 + public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY;
  485 + public static final String FIRMWARE_HAS_DATA_PROPERTY = "has_data";
  486 +
  487 +
  488 + /**
471 * Cassandra attributes and timeseries constants. 489 * Cassandra attributes and timeseries constants.
472 */ 490 */
473 public static final String ATTRIBUTES_KV_CF = "attributes_kv_cf"; 491 public static final String ATTRIBUTES_KV_CF = "attributes_kv_cf";
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData;
27 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
28 import org.thingsboard.server.common.data.id.DeviceId; 28 import org.thingsboard.server.common.data.id.DeviceId;
29 import org.thingsboard.server.common.data.id.DeviceProfileId; 29 import org.thingsboard.server.common.data.id.DeviceProfileId;
  30 +import org.thingsboard.server.common.data.id.FirmwareId;
30 import org.thingsboard.server.common.data.id.TenantId; 31 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.dao.model.BaseSqlEntity; 32 import org.thingsboard.server.dao.model.BaseSqlEntity;
32 import org.thingsboard.server.dao.model.ModelConstants; 33 import org.thingsboard.server.dao.model.ModelConstants;
@@ -73,6 +74,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti @@ -73,6 +74,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
73 @Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_ID_PROPERTY, columnDefinition = "uuid") 74 @Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_ID_PROPERTY, columnDefinition = "uuid")
74 private UUID deviceProfileId; 75 private UUID deviceProfileId;
75 76
  77 + @Column(name = ModelConstants.DEVICE_FIRMWARE_ID_PROPERTY, columnDefinition = "uuid")
  78 + private UUID firmwareId;
  79 +
76 @Type(type = "jsonb") 80 @Type(type = "jsonb")
77 @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb") 81 @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb")
78 private JsonNode deviceData; 82 private JsonNode deviceData;
@@ -95,6 +99,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti @@ -95,6 +99,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
95 if (device.getDeviceProfileId() != null) { 99 if (device.getDeviceProfileId() != null) {
96 this.deviceProfileId = device.getDeviceProfileId().getId(); 100 this.deviceProfileId = device.getDeviceProfileId().getId();
97 } 101 }
  102 + if (device.getFirmwareId() != null) {
  103 + this.firmwareId = device.getFirmwareId().getId();
  104 + }
98 this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class); 105 this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class);
99 this.name = device.getName(); 106 this.name = device.getName();
100 this.type = device.getType(); 107 this.type = device.getType();
@@ -114,6 +121,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti @@ -114,6 +121,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
114 this.label = deviceEntity.getLabel(); 121 this.label = deviceEntity.getLabel();
115 this.searchText = deviceEntity.getSearchText(); 122 this.searchText = deviceEntity.getSearchText();
116 this.additionalInfo = deviceEntity.getAdditionalInfo(); 123 this.additionalInfo = deviceEntity.getAdditionalInfo();
  124 + this.firmwareId = deviceEntity.getFirmwareId();
117 } 125 }
118 126
119 @Override 127 @Override
@@ -138,6 +146,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti @@ -138,6 +146,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
138 if (deviceProfileId != null) { 146 if (deviceProfileId != null) {
139 device.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); 147 device.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
140 } 148 }
  149 + if (firmwareId != null) {
  150 + device.setFirmwareId(new FirmwareId(firmwareId));
  151 + }
141 device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class)); 152 device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class));
142 device.setName(name); 153 device.setName(name);
143 device.setType(type); 154 device.setType(type);
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType;
27 import org.thingsboard.server.common.data.DeviceTransportType; 27 import org.thingsboard.server.common.data.DeviceTransportType;
28 import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 28 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
29 import org.thingsboard.server.common.data.id.DeviceProfileId; 29 import org.thingsboard.server.common.data.id.DeviceProfileId;
  30 +import org.thingsboard.server.common.data.id.FirmwareId;
30 import org.thingsboard.server.common.data.id.RuleChainId; 31 import org.thingsboard.server.common.data.id.RuleChainId;
31 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
32 import org.thingsboard.server.dao.model.BaseSqlEntity; 33 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -89,6 +90,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -89,6 +90,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
89 @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY) 90 @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY)
90 private String provisionDeviceKey; 91 private String provisionDeviceKey;
91 92
  93 + @Column(name=ModelConstants.DEVICE_PROFILE_FIRMWARE_ID_PROPERTY)
  94 + private UUID firmwareId;
  95 +
92 public DeviceProfileEntity() { 96 public DeviceProfileEntity() {
93 super(); 97 super();
94 } 98 }
@@ -113,6 +117,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -113,6 +117,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
113 } 117 }
114 this.defaultQueueName = deviceProfile.getDefaultQueueName(); 118 this.defaultQueueName = deviceProfile.getDefaultQueueName();
115 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); 119 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
  120 + if (deviceProfile.getFirmwareId() != null) {
  121 + this.firmwareId = deviceProfile.getFirmwareId().getId();
  122 + }
116 } 123 }
117 124
118 @Override 125 @Override
@@ -148,6 +155,11 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl @@ -148,6 +155,11 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
148 } 155 }
149 deviceProfile.setDefaultQueueName(defaultQueueName); 156 deviceProfile.setDefaultQueueName(defaultQueueName);
150 deviceProfile.setProvisionDeviceKey(provisionDeviceKey); 157 deviceProfile.setProvisionDeviceKey(provisionDeviceKey);
  158 +
  159 + if (firmwareId != null) {
  160 + deviceProfile.setFirmwareId(new FirmwareId(firmwareId));
  161 + }
  162 +
151 return deviceProfile; 163 return deviceProfile;
152 } 164 }
153 } 165 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.hibernate.annotations.Type;
  22 +import org.hibernate.annotations.TypeDef;
  23 +import org.thingsboard.server.common.data.Firmware;
  24 +import org.thingsboard.server.common.data.id.FirmwareId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  27 +import org.thingsboard.server.dao.model.ModelConstants;
  28 +import org.thingsboard.server.dao.model.SearchTextEntity;
  29 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  30 +
  31 +import javax.persistence.Column;
  32 +import javax.persistence.Entity;
  33 +import javax.persistence.Table;
  34 +import java.nio.ByteBuffer;
  35 +import java.util.UUID;
  36 +
  37 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ALGORITHM_COLUMN;
  38 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN;
  39 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
  47 +
  48 +@Data
  49 +@EqualsAndHashCode(callSuper = true)
  50 +@Entity
  51 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  52 +@Table(name = FIRMWARE_TABLE_NAME)
  53 +public class FirmwareEntity extends BaseSqlEntity<Firmware> implements SearchTextEntity<Firmware> {
  54 +
  55 + @Column(name = FIRMWARE_TENANT_ID_COLUMN)
  56 + private UUID tenantId;
  57 +
  58 + @Column(name = FIRMWARE_TITLE_COLUMN)
  59 + private String title;
  60 +
  61 + @Column(name = FIRMWARE_VERSION_COLUMN)
  62 + private String version;
  63 +
  64 + @Column(name = FIRMWARE_FILE_NAME_COLUMN)
  65 + private String fileName;
  66 +
  67 + @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN)
  68 + private String contentType;
  69 +
  70 + @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN)
  71 + private String checksumAlgorithm;
  72 +
  73 + @Column(name = FIRMWARE_CHECKSUM_COLUMN)
  74 + private String checksum;
  75 +
  76 + @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY")
  77 + private byte[] data;
  78 +
  79 + @Type(type = "json")
  80 + @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN)
  81 + private JsonNode additionalInfo;
  82 +
  83 + @Column(name = SEARCH_TEXT_PROPERTY)
  84 + private String searchText;
  85 +
  86 + public FirmwareEntity() {
  87 + super();
  88 + }
  89 +
  90 + public FirmwareEntity(Firmware firmware) {
  91 + this.createdTime = firmware.getCreatedTime();
  92 + this.setUuid(firmware.getUuidId());
  93 + this.tenantId = firmware.getTenantId().getId();
  94 + this.title = firmware.getTitle();
  95 + this.version = firmware.getVersion();
  96 + this.fileName = firmware.getFileName();
  97 + this.contentType = firmware.getContentType();
  98 + this.checksumAlgorithm = firmware.getChecksumAlgorithm();
  99 + this.checksum = firmware.getChecksum();
  100 + this.data = firmware.getData().array();
  101 + this.additionalInfo = firmware.getAdditionalInfo();
  102 + }
  103 +
  104 + @Override
  105 + public String getSearchTextSource() {
  106 + return title;
  107 + }
  108 +
  109 + @Override
  110 + public void setSearchText(String searchText) {
  111 + this.searchText = searchText;
  112 + }
  113 +
  114 + @Override
  115 + public Firmware toData() {
  116 + Firmware firmware = new Firmware(new FirmwareId(id));
  117 + firmware.setCreatedTime(createdTime);
  118 + firmware.setTenantId(new TenantId(tenantId));
  119 + firmware.setTitle(title);
  120 + firmware.setVersion(version);
  121 + firmware.setFileName(fileName);
  122 + firmware.setContentType(contentType);
  123 + firmware.setChecksumAlgorithm(checksumAlgorithm);
  124 + firmware.setChecksum(checksum);
  125 + if (data != null) {
  126 + firmware.setData(ByteBuffer.wrap(data));
  127 + firmware.setHasData(true);
  128 + }
  129 + firmware.setAdditionalInfo(additionalInfo);
  130 + return firmware;
  131 + }
  132 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import org.hibernate.annotations.Type;
  22 +import org.hibernate.annotations.TypeDef;
  23 +import org.thingsboard.common.util.JacksonUtil;
  24 +import org.thingsboard.server.common.data.FirmwareInfo;
  25 +import org.thingsboard.server.common.data.id.FirmwareId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  28 +import org.thingsboard.server.dao.model.ModelConstants;
  29 +import org.thingsboard.server.dao.model.SearchTextEntity;
  30 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  31 +
  32 +import javax.persistence.Column;
  33 +import javax.persistence.Entity;
  34 +import javax.persistence.Table;
  35 +import javax.persistence.Transient;
  36 +import java.util.UUID;
  37 +
  38 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_HAS_DATA_PROPERTY;
  39 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
  44 +
  45 +@Data
  46 +@EqualsAndHashCode(callSuper = true)
  47 +@Entity
  48 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  49 +@Table(name = FIRMWARE_TABLE_NAME)
  50 +public class FirmwareInfoEntity extends BaseSqlEntity<FirmwareInfo> implements SearchTextEntity<FirmwareInfo> {
  51 +
  52 + @Column(name = FIRMWARE_TENANT_ID_COLUMN)
  53 + private UUID tenantId;
  54 +
  55 + @Column(name = FIRMWARE_TITLE_COLUMN)
  56 + private String title;
  57 +
  58 + @Column(name = FIRMWARE_VERSION_COLUMN)
  59 + private String version;
  60 +
  61 + @Type(type = "json")
  62 + @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN)
  63 + private JsonNode additionalInfo;
  64 +
  65 + @Column(name = SEARCH_TEXT_PROPERTY)
  66 + private String searchText;
  67 +
  68 +// @Column(name = FIRMWARE_HAS_DATA_PROPERTY, insertable = false, updatable = false)
  69 + @Transient
  70 + private boolean hasData;
  71 +
  72 + public FirmwareInfoEntity() {
  73 + super();
  74 + }
  75 +
  76 + public FirmwareInfoEntity(FirmwareInfo firmware) {
  77 + this.createdTime = firmware.getCreatedTime();
  78 + this.setUuid(firmware.getUuidId());
  79 + this.tenantId = firmware.getTenantId().getId();
  80 + this.title = firmware.getTitle();
  81 + this.version = firmware.getVersion();
  82 + this.additionalInfo = firmware.getAdditionalInfo();
  83 + }
  84 +
  85 + public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, Object additionalInfo, boolean hasData) {
  86 + this.id = id;
  87 + this.createdTime = createdTime;
  88 + this.tenantId = tenantId;
  89 + this.title = title;
  90 + this.version = version;
  91 + this.hasData = hasData;
  92 + this.additionalInfo = JacksonUtil.convertValue(additionalInfo, JsonNode.class);
  93 + }
  94 +
  95 + @Override
  96 + public String getSearchTextSource() {
  97 + return title;
  98 + }
  99 +
  100 + @Override
  101 + public void setSearchText(String searchText) {
  102 + this.searchText = searchText;
  103 + }
  104 +
  105 + @Override
  106 + public FirmwareInfo toData() {
  107 + FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id));
  108 + firmware.setCreatedTime(createdTime);
  109 + firmware.setTenantId(new TenantId(tenantId));
  110 + firmware.setTitle(title);
  111 + firmware.setVersion(version);
  112 + firmware.setAdditionalInfo(additionalInfo);
  113 + firmware.setHasData(hasData);
  114 + return firmware;
  115 + }
  116 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.firmware;
  17 +
  18 +import org.springframework.data.domain.Page;
  19 +import org.springframework.data.domain.Pageable;
  20 +import org.springframework.data.jpa.repository.Query;
  21 +import org.springframework.data.repository.CrudRepository;
  22 +import org.springframework.data.repository.query.Param;
  23 +import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity;
  24 +
  25 +import java.util.UUID;
  26 +
  27 +public interface FirmwareInfoRepository extends CrudRepository<FirmwareInfoEntity, UUID> {
  28 + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " +
  29 + "f.tenantId = :tenantId " +
  30 + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
  31 + Page<FirmwareInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
  32 + @Param("searchText") String searchText,
  33 + Pageable pageable);
  34 +
  35 + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " +
  36 + "f.tenantId = :tenantId " +
  37 + "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" +
  38 + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
  39 + Page<FirmwareInfoEntity> findAllByTenantIdAndHasData(@Param("tenantId") UUID tenantId,
  40 + @Param("hasData") boolean hasData,
  41 + @Param("searchText") String searchText,
  42 + Pageable pageable);
  43 +
  44 + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE f.id = :id")
  45 + FirmwareInfoEntity findFirmwareInfoById(@Param("id") UUID id);
  46 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.firmware;
  17 +
  18 +import org.springframework.data.repository.CrudRepository;
  19 +import org.thingsboard.server.dao.model.sql.FirmwareEntity;
  20 +
  21 +import java.util.UUID;
  22 +
  23 +public interface FirmwareRepository extends CrudRepository<FirmwareEntity, UUID> {
  24 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.firmware;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.Firmware;
  23 +import org.thingsboard.server.dao.firmware.FirmwareDao;
  24 +import org.thingsboard.server.dao.model.sql.FirmwareEntity;
  25 +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
  26 +
  27 +import java.util.UUID;
  28 +
  29 +@Slf4j
  30 +@Component
  31 +public class JpaFirmwareDao extends JpaAbstractSearchTextDao<FirmwareEntity, Firmware> implements FirmwareDao {
  32 +
  33 + @Autowired
  34 + private FirmwareRepository firmwareRepository;
  35 +
  36 + @Override
  37 + protected Class<FirmwareEntity> getEntityClass() {
  38 + return FirmwareEntity.class;
  39 + }
  40 +
  41 + @Override
  42 + protected CrudRepository<FirmwareEntity, UUID> getCrudRepository() {
  43 + return firmwareRepository;
  44 + }
  45 +
  46 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.firmware;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.FirmwareInfo;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.PageData;
  25 +import org.thingsboard.server.common.data.page.PageLink;
  26 +import org.thingsboard.server.dao.DaoUtil;
  27 +import org.thingsboard.server.dao.firmware.FirmwareInfoDao;
  28 +import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity;
  29 +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
  30 +
  31 +import java.util.Objects;
  32 +import java.util.UUID;
  33 +
  34 +@Slf4j
  35 +@Component
  36 +public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao<FirmwareInfoEntity, FirmwareInfo> implements FirmwareInfoDao {
  37 +
  38 + @Autowired
  39 + private FirmwareInfoRepository firmwareInfoRepository;
  40 +
  41 + @Override
  42 + protected Class<FirmwareInfoEntity> getEntityClass() {
  43 + return FirmwareInfoEntity.class;
  44 + }
  45 +
  46 + @Override
  47 + protected CrudRepository<FirmwareInfoEntity, UUID> getCrudRepository() {
  48 + return firmwareInfoRepository;
  49 + }
  50 +
  51 + @Override
  52 + public FirmwareInfo findById(TenantId tenantId, UUID id) {
  53 + return DaoUtil.getData(firmwareInfoRepository.findFirmwareInfoById(id));
  54 + }
  55 +
  56 + @Override
  57 + public FirmwareInfo save(TenantId tenantId, FirmwareInfo firmwareInfo) {
  58 + FirmwareInfo savedFirmware = super.save(tenantId, firmwareInfo);
  59 + if (firmwareInfo.getId() == null) {
  60 + return savedFirmware;
  61 + } else {
  62 + return findById(tenantId, savedFirmware.getId().getId());
  63 + }
  64 + }
  65 +
  66 + @Override
  67 + public PageData<FirmwareInfo> findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink) {
  68 + return DaoUtil.toPageData(firmwareInfoRepository
  69 + .findAllByTenantId(
  70 + tenantId.getId(),
  71 + Objects.toString(pageLink.getTextSearch(), ""),
  72 + DaoUtil.toPageable(pageLink)));
  73 + }
  74 +
  75 + @Override
  76 + public PageData<FirmwareInfo> findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) {
  77 + return DaoUtil.toPageData(firmwareInfoRepository
  78 + .findAllByTenantIdAndHasData(
  79 + tenantId.getId(),
  80 + hasData,
  81 + Objects.toString(pageLink.getTextSearch(), ""),
  82 + DaoUtil.toPageable(pageLink)));
  83 + }
  84 +}
@@ -48,8 +48,6 @@ public interface TbResourceRepository extends CrudRepository<TbResourceEntity, U @@ -48,8 +48,6 @@ public interface TbResourceRepository extends CrudRepository<TbResourceEntity, U
48 @Param("searchText") String search, 48 @Param("searchText") String search,
49 Pageable pageable); 49 Pageable pageable);
50 50
51 - void removeAllByTenantId(UUID tenantId);  
52 -  
53 @Query("SELECT tr FROM TbResourceEntity tr " + 51 @Query("SELECT tr FROM TbResourceEntity tr " +
54 "WHERE tr.resourceType = :resourceType " + 52 "WHERE tr.resourceType = :resourceType " +
55 "AND LOWER(tr.searchText) LIKE LOWER(CONCAT('%', :searchText, '%')) " + 53 "AND LOWER(tr.searchText) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
@@ -157,6 +157,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state ( @@ -157,6 +157,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
157 CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE 157 CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
158 ); 158 );
159 159
  160 +CREATE TABLE IF NOT EXISTS firmware (
  161 + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
  162 + created_time bigint NOT NULL,
  163 + tenant_id uuid NOT NULL,
  164 + title varchar(255) NOT NULL,
  165 + version varchar(255) NOT NULL,
  166 + file_name varchar(255),
  167 + content_type varchar(255),
  168 + checksum_algorithm varchar(32),
  169 + checksum varchar(1020),
  170 + data binary,
  171 + additional_info varchar,
  172 + search_text varchar(255),
  173 + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
  174 +);
  175 +
160 CREATE TABLE IF NOT EXISTS device_profile ( 176 CREATE TABLE IF NOT EXISTS device_profile (
161 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY, 177 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
162 created_time bigint NOT NULL, 178 created_time bigint NOT NULL,
@@ -169,12 +185,14 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -169,12 +185,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
169 search_text varchar(255), 185 search_text varchar(255),
170 is_default boolean, 186 is_default boolean,
171 tenant_id uuid, 187 tenant_id uuid,
  188 + firmware_id uuid,
172 default_rule_chain_id uuid, 189 default_rule_chain_id uuid,
173 default_queue_name varchar(255), 190 default_queue_name varchar(255),
174 provision_device_key varchar, 191 provision_device_key varchar,
175 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 192 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
176 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), 193 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
177 - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 194 + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
  195 + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id)
178 ); 196 );
179 197
180 CREATE TABLE IF NOT EXISTS device ( 198 CREATE TABLE IF NOT EXISTS device (
@@ -189,8 +207,10 @@ CREATE TABLE IF NOT EXISTS device ( @@ -189,8 +207,10 @@ CREATE TABLE IF NOT EXISTS device (
189 label varchar(255), 207 label varchar(255),
190 search_text varchar(255), 208 search_text varchar(255),
191 tenant_id uuid, 209 tenant_id uuid,
  210 + firmware_id uuid,
192 CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), 211 CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
193 - CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) 212 + CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
  213 + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id)
194 ); 214 );
195 215
196 CREATE TABLE IF NOT EXISTS device_credentials ( 216 CREATE TABLE IF NOT EXISTS device_credentials (
@@ -25,7 +25,7 @@ CREATE OR REPLACE PROCEDURE insert_tb_schema_settings() @@ -25,7 +25,7 @@ CREATE OR REPLACE PROCEDURE insert_tb_schema_settings()
25 $$ 25 $$
26 BEGIN 26 BEGIN
27 IF (SELECT COUNT(*) FROM tb_schema_settings) = 0 THEN 27 IF (SELECT COUNT(*) FROM tb_schema_settings) = 0 THEN
28 - INSERT INTO tb_schema_settings (schema_version) VALUES (3002000); 28 + INSERT INTO tb_schema_settings (schema_version) VALUES (3003000);
29 END IF; 29 END IF;
30 END; 30 END;
31 $$; 31 $$;
@@ -175,6 +175,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state ( @@ -175,6 +175,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
175 CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE 175 CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
176 ); 176 );
177 177
  178 +CREATE TABLE IF NOT EXISTS firmware (
  179 + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
  180 + created_time bigint NOT NULL,
  181 + tenant_id uuid NOT NULL,
  182 + title varchar(255) NOT NULL,
  183 + version varchar(255) NOT NULL,
  184 + file_name varchar(255),
  185 + content_type varchar(255),
  186 + checksum_algorithm varchar(32),
  187 + checksum varchar(1020),
  188 + data bytea,
  189 + additional_info varchar,
  190 + search_text varchar(255),
  191 + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
  192 +);
  193 +
178 CREATE TABLE IF NOT EXISTS device_profile ( 194 CREATE TABLE IF NOT EXISTS device_profile (
179 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY, 195 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
180 created_time bigint NOT NULL, 196 created_time bigint NOT NULL,
@@ -187,12 +203,14 @@ CREATE TABLE IF NOT EXISTS device_profile ( @@ -187,12 +203,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
187 search_text varchar(255), 203 search_text varchar(255),
188 is_default boolean, 204 is_default boolean,
189 tenant_id uuid, 205 tenant_id uuid,
  206 + firmware_id uuid,
190 default_rule_chain_id uuid, 207 default_rule_chain_id uuid,
191 default_queue_name varchar(255), 208 default_queue_name varchar(255),
192 provision_device_key varchar, 209 provision_device_key varchar,
193 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), 210 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
194 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), 211 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
195 - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) 212 + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
  213 + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id)
196 ); 214 );
197 215
198 CREATE TABLE IF NOT EXISTS device ( 216 CREATE TABLE IF NOT EXISTS device (
@@ -207,8 +225,10 @@ CREATE TABLE IF NOT EXISTS device ( @@ -207,8 +225,10 @@ CREATE TABLE IF NOT EXISTS device (
207 label varchar(255), 225 label varchar(255),
208 search_text varchar(255), 226 search_text varchar(255),
209 tenant_id uuid, 227 tenant_id uuid,
  228 + firmware_id uuid,
210 CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), 229 CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
211 - CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) 230 + CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
  231 + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id)
212 ); 232 );
213 233
214 CREATE TABLE IF NOT EXISTS device_credentials ( 234 CREATE TABLE IF NOT EXISTS device_credentials (
@@ -51,6 +51,7 @@ import org.thingsboard.server.dao.device.DeviceService; @@ -51,6 +51,7 @@ import org.thingsboard.server.dao.device.DeviceService;
51 import org.thingsboard.server.dao.entity.EntityService; 51 import org.thingsboard.server.dao.entity.EntityService;
52 import org.thingsboard.server.dao.entityview.EntityViewService; 52 import org.thingsboard.server.dao.entityview.EntityViewService;
53 import org.thingsboard.server.dao.event.EventService; 53 import org.thingsboard.server.dao.event.EventService;
  54 +import org.thingsboard.server.dao.firmware.FirmwareService;
54 import org.thingsboard.server.dao.relation.RelationService; 55 import org.thingsboard.server.dao.relation.RelationService;
55 import org.thingsboard.server.dao.resource.TbResourceService; 56 import org.thingsboard.server.dao.resource.TbResourceService;
56 import org.thingsboard.server.dao.rule.RuleChainService; 57 import org.thingsboard.server.dao.rule.RuleChainService;
@@ -146,6 +147,9 @@ public abstract class AbstractServiceTest { @@ -146,6 +147,9 @@ public abstract class AbstractServiceTest {
146 @Autowired 147 @Autowired
147 protected TbResourceService resourceService; 148 protected TbResourceService resourceService;
148 149
  150 + @Autowired
  151 + protected FirmwareService firmwareService;
  152 +
149 class IdComparator<D extends HasId> implements Comparator<D> { 153 class IdComparator<D extends HasId> implements Comparator<D> {
150 @Override 154 @Override
151 public int compare(D o1, D o2) { 155 public int compare(D o1, D o2) {
@@ -192,7 +196,7 @@ public abstract class AbstractServiceTest { @@ -192,7 +196,7 @@ public abstract class AbstractServiceTest {
192 196
193 @Bean 197 @Bean
194 public AuditLogLevelFilter auditLogLevelFilter() { 198 public AuditLogLevelFilter auditLogLevelFilter() {
195 - Map<String,String> mask = new HashMap<>(); 199 + Map<String, String> mask = new HashMap<>();
196 for (EntityType entityType : EntityType.values()) { 200 for (EntityType entityType : EntityType.values()) {
197 mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name()); 201 mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name());
198 } 202 }
@@ -27,19 +27,19 @@ import org.junit.Test; @@ -27,19 +27,19 @@ import org.junit.Test;
27 import org.thingsboard.server.common.data.Device; 27 import org.thingsboard.server.common.data.Device;
28 import org.thingsboard.server.common.data.DeviceProfile; 28 import org.thingsboard.server.common.data.DeviceProfile;
29 import org.thingsboard.server.common.data.DeviceProfileInfo; 29 import org.thingsboard.server.common.data.DeviceProfileInfo;
30 -import org.thingsboard.server.common.data.DeviceProfileType;  
31 import org.thingsboard.server.common.data.DeviceTransportType; 30 import org.thingsboard.server.common.data.DeviceTransportType;
  31 +import org.thingsboard.server.common.data.Firmware;
32 import org.thingsboard.server.common.data.Tenant; 32 import org.thingsboard.server.common.data.Tenant;
33 import org.thingsboard.server.common.data.id.TenantId; 33 import org.thingsboard.server.common.data.id.TenantId;
34 import org.thingsboard.server.common.data.page.PageData; 34 import org.thingsboard.server.common.data.page.PageData;
35 import org.thingsboard.server.common.data.page.PageLink; 35 import org.thingsboard.server.common.data.page.PageLink;
36 import org.thingsboard.server.dao.exception.DataValidationException; 36 import org.thingsboard.server.dao.exception.DataValidationException;
37 37
  38 +import java.nio.ByteBuffer;
38 import java.util.ArrayList; 39 import java.util.ArrayList;
39 import java.util.Collections; 40 import java.util.Collections;
40 import java.util.List; 41 import java.util.List;
41 import java.util.concurrent.ExecutionException; 42 import java.util.concurrent.ExecutionException;
42 -import java.util.concurrent.ExecutorService;  
43 import java.util.concurrent.Executors; 43 import java.util.concurrent.Executors;
44 import java.util.stream.Collectors; 44 import java.util.stream.Collectors;
45 45
@@ -83,17 +83,48 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -83,17 +83,48 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
83 } 83 }
84 84
85 @Test 85 @Test
  86 + public void testSaveDeviceProfileWithFirmware() {
  87 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
  88 + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
  89 + Assert.assertNotNull(savedDeviceProfile);
  90 + Assert.assertNotNull(savedDeviceProfile.getId());
  91 + Assert.assertTrue(savedDeviceProfile.getCreatedTime() > 0);
  92 + Assert.assertEquals(deviceProfile.getName(), savedDeviceProfile.getName());
  93 + Assert.assertEquals(deviceProfile.getDescription(), savedDeviceProfile.getDescription());
  94 + Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData());
  95 + Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault());
  96 + Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId());
  97 +
  98 + Firmware firmware = new Firmware();
  99 + firmware.setTenantId(tenantId);
  100 + firmware.setTitle("my firmware");
  101 + firmware.setVersion("v1.0");
  102 + firmware.setFileName("test.txt");
  103 + firmware.setContentType("text/plain");
  104 + firmware.setChecksumAlgorithm("sha256");
  105 + firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
  106 + firmware.setData(ByteBuffer.wrap(new byte[]{1}));
  107 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  108 +
  109 + deviceProfile.setFirmwareId(savedFirmware.getId());
  110 +
  111 + deviceProfileService.saveDeviceProfile(savedDeviceProfile);
  112 + DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
  113 + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName());
  114 + }
  115 +
  116 + @Test
86 public void testFindDeviceProfileById() { 117 public void testFindDeviceProfileById() {
87 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 118 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
88 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); 119 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
89 DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId()); 120 DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
90 Assert.assertNotNull(foundDeviceProfile); 121 Assert.assertNotNull(foundDeviceProfile);
91 Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); 122 Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
92 - } 123 + }
93 124
94 @Test 125 @Test
95 public void testFindDeviceProfileInfoById() { 126 public void testFindDeviceProfileInfoById() {
96 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 127 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
97 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); 128 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
98 DeviceProfileInfo foundDeviceProfileInfo = deviceProfileService.findDeviceProfileInfoById(tenantId, savedDeviceProfile.getId()); 129 DeviceProfileInfo foundDeviceProfileInfo = deviceProfileService.findDeviceProfileInfoById(tenantId, savedDeviceProfile.getId());
99 Assert.assertNotNull(foundDeviceProfileInfo); 130 Assert.assertNotNull(foundDeviceProfileInfo);
@@ -124,7 +155,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -124,7 +155,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
124 ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100)); 155 ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100));
125 try { 156 try {
126 List<ListenableFuture<DeviceProfile>> futures = new ArrayList<>(); 157 List<ListenableFuture<DeviceProfile>> futures = new ArrayList<>();
127 - for (int i = 0; i < 50; i ++) { 158 + for (int i = 0; i < 50; i++) {
128 futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1"))); 159 futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1")));
129 futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2"))); 160 futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2")));
130 } 161 }
@@ -138,8 +169,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -138,8 +169,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
138 169
139 @Test 170 @Test
140 public void testSetDefaultDeviceProfile() { 171 public void testSetDefaultDeviceProfile() {
141 - DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1");  
142 - DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile 2"); 172 + DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId, "Device Profile 1");
  173 + DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile 2");
143 174
144 DeviceProfile savedDeviceProfile1 = deviceProfileService.saveDeviceProfile(deviceProfile1); 175 DeviceProfile savedDeviceProfile1 = deviceProfileService.saveDeviceProfile(deviceProfile1);
145 DeviceProfile savedDeviceProfile2 = deviceProfileService.saveDeviceProfile(deviceProfile2); 176 DeviceProfile savedDeviceProfile2 = deviceProfileService.saveDeviceProfile(deviceProfile2);
@@ -165,16 +196,16 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -165,16 +196,16 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
165 196
166 @Test(expected = DataValidationException.class) 197 @Test(expected = DataValidationException.class)
167 public void testSaveDeviceProfileWithSameName() { 198 public void testSaveDeviceProfileWithSameName() {
168 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 199 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
169 deviceProfileService.saveDeviceProfile(deviceProfile); 200 deviceProfileService.saveDeviceProfile(deviceProfile);
170 - DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile"); 201 + DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile");
171 deviceProfileService.saveDeviceProfile(deviceProfile2); 202 deviceProfileService.saveDeviceProfile(deviceProfile2);
172 } 203 }
173 204
174 @Ignore 205 @Ignore
175 @Test(expected = DataValidationException.class) 206 @Test(expected = DataValidationException.class)
176 public void testChangeDeviceProfileTypeWithExistingDevices() { 207 public void testChangeDeviceProfileTypeWithExistingDevices() {
177 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 208 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
178 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); 209 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
179 Device device = new Device(); 210 Device device = new Device();
180 device.setTenantId(tenantId); 211 device.setTenantId(tenantId);
@@ -189,7 +220,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -189,7 +220,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
189 220
190 @Test(expected = DataValidationException.class) 221 @Test(expected = DataValidationException.class)
191 public void testChangeDeviceProfileTransportTypeWithExistingDevices() { 222 public void testChangeDeviceProfileTransportTypeWithExistingDevices() {
192 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 223 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
193 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); 224 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
194 Device device = new Device(); 225 Device device = new Device();
195 device.setTenantId(tenantId); 226 device.setTenantId(tenantId);
@@ -203,7 +234,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -203,7 +234,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
203 234
204 @Test(expected = DataValidationException.class) 235 @Test(expected = DataValidationException.class)
205 public void testDeleteDeviceProfileWithExistingDevice() { 236 public void testDeleteDeviceProfileWithExistingDevice() {
206 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 237 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
207 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); 238 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
208 Device device = new Device(); 239 Device device = new Device();
209 device.setTenantId(tenantId); 240 device.setTenantId(tenantId);
@@ -216,7 +247,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -216,7 +247,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
216 247
217 @Test 248 @Test
218 public void testDeleteDeviceProfile() { 249 public void testDeleteDeviceProfile() {
219 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); 250 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
220 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); 251 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
221 deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); 252 deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
222 DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId()); 253 DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
@@ -233,8 +264,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -233,8 +264,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
233 Assert.assertEquals(1, pageData.getTotalElements()); 264 Assert.assertEquals(1, pageData.getTotalElements());
234 deviceProfiles.addAll(pageData.getData()); 265 deviceProfiles.addAll(pageData.getData());
235 266
236 - for (int i=0;i<28;i++) {  
237 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"+i); 267 + for (int i = 0; i < 28; i++) {
  268 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile" + i);
238 deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile)); 269 deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile));
239 } 270 }
240 271
@@ -275,8 +306,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -275,8 +306,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
275 Assert.assertEquals(1, deviceProfilePageData.getTotalElements()); 306 Assert.assertEquals(1, deviceProfilePageData.getTotalElements());
276 deviceProfiles.addAll(deviceProfilePageData.getData()); 307 deviceProfiles.addAll(deviceProfilePageData.getData());
277 308
278 - for (int i=0;i<28;i++) {  
279 - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"+i); 309 + for (int i = 0; i < 28; i++) {
  310 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile" + i);
280 deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile)); 311 deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile));
281 } 312 }
282 313
@@ -297,7 +328,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -297,7 +328,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
297 328
298 List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream() 329 List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream()
299 .map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), 330 .map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(),
300 - deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); 331 + deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
301 332
302 Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); 333 Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);
303 334
@@ -312,4 +343,5 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -312,4 +343,5 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
312 Assert.assertFalse(pageData.hasNext()); 343 Assert.assertFalse(pageData.hasNext());
313 Assert.assertEquals(1, pageData.getTotalElements()); 344 Assert.assertEquals(1, pageData.getTotalElements());
314 } 345 }
  346 +
315 } 347 }
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
30 import org.thingsboard.server.common.data.security.DeviceCredentialsType; 30 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
31 import org.thingsboard.server.dao.exception.DataValidationException; 31 import org.thingsboard.server.dao.exception.DataValidationException;
32 32
  33 +import java.nio.ByteBuffer;
33 import java.util.ArrayList; 34 import java.util.ArrayList;
34 import java.util.Collections; 35 import java.util.Collections;
35 import java.util.List; 36 import java.util.List;
@@ -88,6 +89,49 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { @@ -88,6 +89,49 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
88 89
89 deviceService.deleteDevice(tenantId, savedDevice.getId()); 90 deviceService.deleteDevice(tenantId, savedDevice.getId());
90 } 91 }
  92 +
  93 + @Test
  94 + public void testSaveDeviceWithFirmware() {
  95 + Device device = new Device();
  96 + device.setTenantId(tenantId);
  97 + device.setName("My device");
  98 + device.setType("default");
  99 + Device savedDevice = deviceService.saveDevice(device);
  100 +
  101 + Assert.assertNotNull(savedDevice);
  102 + Assert.assertNotNull(savedDevice.getId());
  103 + Assert.assertTrue(savedDevice.getCreatedTime() > 0);
  104 + Assert.assertEquals(device.getTenantId(), savedDevice.getTenantId());
  105 + Assert.assertNotNull(savedDevice.getCustomerId());
  106 + Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
  107 + Assert.assertEquals(device.getName(), savedDevice.getName());
  108 +
  109 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId());
  110 + Assert.assertNotNull(deviceCredentials);
  111 + Assert.assertNotNull(deviceCredentials.getId());
  112 + Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
  113 + Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType());
  114 + Assert.assertNotNull(deviceCredentials.getCredentialsId());
  115 + Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
  116 +
  117 +
  118 + Firmware firmware = new Firmware();
  119 + firmware.setTenantId(tenantId);
  120 + firmware.setTitle("my firmware");
  121 + firmware.setVersion("v1.0");
  122 + firmware.setFileName("test.txt");
  123 + firmware.setContentType("text/plain");
  124 + firmware.setChecksumAlgorithm("sha256");
  125 + firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
  126 + firmware.setData(ByteBuffer.wrap(new byte[]{1}));
  127 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  128 +
  129 + savedDevice.setFirmwareId(savedFirmware.getId());
  130 +
  131 + deviceService.saveDevice(savedDevice);
  132 + Device foundDevice = deviceService.findDeviceById(tenantId, savedDevice.getId());
  133 + Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
  134 + }
91 135
92 @Test(expected = DataValidationException.class) 136 @Test(expected = DataValidationException.class)
93 public void testSaveDeviceWithEmptyName() { 137 public void testSaveDeviceWithEmptyName() {
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import com.datastax.oss.driver.api.core.uuid.Uuids;
  19 +import org.junit.After;
  20 +import org.junit.Assert;
  21 +import org.junit.Before;
  22 +import org.junit.Test;
  23 +import org.thingsboard.common.util.JacksonUtil;
  24 +import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.DeviceProfile;
  26 +import org.thingsboard.server.common.data.Firmware;
  27 +import org.thingsboard.server.common.data.FirmwareInfo;
  28 +import org.thingsboard.server.common.data.Tenant;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.page.PageData;
  31 +import org.thingsboard.server.common.data.page.PageLink;
  32 +import org.thingsboard.server.dao.exception.DataValidationException;
  33 +
  34 +import java.nio.ByteBuffer;
  35 +import java.util.ArrayList;
  36 +import java.util.Collections;
  37 +import java.util.List;
  38 +
  39 +public abstract class BaseFirmwareServiceTest extends AbstractServiceTest {
  40 +
  41 + public static final String TITLE = "My firmware";
  42 + private static final String FILE_NAME = "filename.txt";
  43 + private static final String VERSION = "v1.0";
  44 + private static final String CONTENT_TYPE = "text/plain";
  45 + private static final String CHECKSUM_ALGORITHM = "sha256";
  46 + private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
  47 + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
  48 +
  49 + private IdComparator<FirmwareInfo> idComparator = new IdComparator<>();
  50 +
  51 + private TenantId tenantId;
  52 +
  53 + @Before
  54 + public void before() {
  55 + Tenant tenant = new Tenant();
  56 + tenant.setTitle("My tenant");
  57 + Tenant savedTenant = tenantService.saveTenant(tenant);
  58 + Assert.assertNotNull(savedTenant);
  59 + tenantId = savedTenant.getId();
  60 + }
  61 +
  62 + @After
  63 + public void after() {
  64 + tenantService.deleteTenant(tenantId);
  65 + }
  66 +
  67 + @Test
  68 + public void testSaveFirmware() {
  69 + Firmware firmware = new Firmware();
  70 + firmware.setTenantId(tenantId);
  71 + firmware.setTitle(TITLE);
  72 + firmware.setVersion(VERSION);
  73 + firmware.setFileName(FILE_NAME);
  74 + firmware.setContentType(CONTENT_TYPE);
  75 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  76 + firmware.setChecksum(CHECKSUM);
  77 + firmware.setData(DATA);
  78 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  79 +
  80 + Assert.assertNotNull(savedFirmware);
  81 + Assert.assertNotNull(savedFirmware.getId());
  82 + Assert.assertTrue(savedFirmware.getCreatedTime() > 0);
  83 + Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId());
  84 + Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle());
  85 + Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName());
  86 + Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType());
  87 + Assert.assertEquals(firmware.getData(), savedFirmware.getData());
  88 +
  89 + savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode());
  90 + firmwareService.saveFirmware(savedFirmware);
  91 +
  92 + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
  93 + Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle());
  94 +
  95 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  96 + }
  97 +
  98 + @Test
  99 + public void testSaveFirmwareInfoAndUpdateWithData() {
  100 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  101 + firmwareInfo.setTenantId(tenantId);
  102 + firmwareInfo.setTitle(TITLE);
  103 + firmwareInfo.setVersion(VERSION);
  104 + FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo);
  105 +
  106 + Assert.assertNotNull(savedFirmwareInfo);
  107 + Assert.assertNotNull(savedFirmwareInfo.getId());
  108 + Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0);
  109 + Assert.assertEquals(firmwareInfo.getTenantId(), savedFirmwareInfo.getTenantId());
  110 + Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
  111 +
  112 + Firmware firmware = new Firmware(savedFirmwareInfo.getId());
  113 + firmware.setCreatedTime(firmwareInfo.getCreatedTime());
  114 + firmware.setTenantId(tenantId);
  115 + firmware.setTitle(TITLE);
  116 + firmware.setVersion(VERSION);
  117 + firmware.setFileName(FILE_NAME);
  118 + firmware.setContentType(CONTENT_TYPE);
  119 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  120 + firmware.setChecksum(CHECKSUM);
  121 + firmware.setData(DATA);
  122 +
  123 + firmwareService.saveFirmware(firmware);
  124 +
  125 + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
  126 + firmwareService.saveFirmwareInfo(savedFirmwareInfo);
  127 +
  128 + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, firmware.getId());
  129 + firmware.setAdditionalInfo(JacksonUtil.newObjectNode());
  130 +
  131 + Assert.assertEquals(foundFirmware.getTitle(), firmware.getTitle());
  132 + Assert.assertTrue(foundFirmware.isHasData());
  133 +
  134 + firmwareService.deleteFirmware(tenantId, savedFirmwareInfo.getId());
  135 + }
  136 +
  137 + @Test(expected = DataValidationException.class)
  138 + public void testSaveFirmwareWithEmptyTenant() {
  139 + Firmware firmware = new Firmware();
  140 + firmware.setTitle(TITLE);
  141 + firmware.setVersion(VERSION);
  142 + firmware.setFileName(FILE_NAME);
  143 + firmware.setContentType(CONTENT_TYPE);
  144 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  145 + firmware.setChecksum(CHECKSUM);
  146 + firmware.setData(DATA);
  147 + firmwareService.saveFirmware(firmware);
  148 + }
  149 +
  150 + @Test(expected = DataValidationException.class)
  151 + public void testSaveFirmwareWithEmptyTitle() {
  152 + Firmware firmware = new Firmware();
  153 + firmware.setTenantId(tenantId);
  154 + firmware.setVersion(VERSION);
  155 + firmware.setFileName(FILE_NAME);
  156 + firmware.setContentType(CONTENT_TYPE);
  157 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  158 + firmware.setChecksum(CHECKSUM);
  159 + firmware.setData(DATA);
  160 + firmwareService.saveFirmware(firmware);
  161 + }
  162 +
  163 + @Test(expected = DataValidationException.class)
  164 + public void testSaveFirmwareWithEmptyFileName() {
  165 + Firmware firmware = new Firmware();
  166 + firmware.setTenantId(tenantId);
  167 + firmware.setTitle(TITLE);
  168 + firmware.setVersion(VERSION);
  169 + firmware.setContentType(CONTENT_TYPE);
  170 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  171 + firmware.setChecksum(CHECKSUM);
  172 + firmware.setData(DATA);
  173 + firmwareService.saveFirmware(firmware);
  174 + }
  175 +
  176 + @Test(expected = DataValidationException.class)
  177 + public void testSaveFirmwareWithEmptyContentType() {
  178 + Firmware firmware = new Firmware();
  179 + firmware.setTenantId(tenantId);
  180 + firmware.setTitle(TITLE);
  181 + firmware.setVersion(VERSION);
  182 + firmware.setFileName(FILE_NAME);
  183 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  184 + firmware.setChecksum(CHECKSUM);
  185 + firmware.setData(DATA);
  186 + firmwareService.saveFirmware(firmware);
  187 + }
  188 +
  189 + @Test(expected = DataValidationException.class)
  190 + public void testSaveFirmwareWithEmptyData() {
  191 + Firmware firmware = new Firmware();
  192 + firmware.setTenantId(tenantId);
  193 + firmware.setTitle(TITLE);
  194 + firmware.setVersion(VERSION);
  195 + firmware.setFileName(FILE_NAME);
  196 + firmware.setContentType(CONTENT_TYPE);
  197 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  198 + firmware.setChecksum(CHECKSUM);
  199 + firmwareService.saveFirmware(firmware);
  200 + }
  201 +
  202 + @Test(expected = DataValidationException.class)
  203 + public void testSaveFirmwareWithInvalidTenant() {
  204 + Firmware firmware = new Firmware();
  205 + firmware.setTenantId(new TenantId(Uuids.timeBased()));
  206 + firmware.setTitle(TITLE);
  207 + firmware.setVersion(VERSION);
  208 + firmware.setFileName(FILE_NAME);
  209 + firmware.setContentType(CONTENT_TYPE);
  210 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  211 + firmware.setChecksum(CHECKSUM);
  212 + firmware.setData(DATA);
  213 + firmwareService.saveFirmware(firmware);
  214 + }
  215 +
  216 + @Test(expected = DataValidationException.class)
  217 + public void testSaveFirmwareWithEmptyChecksum() {
  218 + Firmware firmware = new Firmware();
  219 + firmware.setTenantId(new TenantId(Uuids.timeBased()));
  220 + firmware.setTitle(TITLE);
  221 + firmware.setVersion(VERSION);
  222 + firmware.setFileName(FILE_NAME);
  223 + firmware.setContentType(CONTENT_TYPE);
  224 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  225 + firmware.setData(DATA);
  226 + firmwareService.saveFirmware(firmware);
  227 + }
  228 +
  229 + @Test(expected = DataValidationException.class)
  230 + public void testSaveFirmwareInfoWithExistingTitleAndVersion() {
  231 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  232 + firmwareInfo.setTenantId(tenantId);
  233 + firmwareInfo.setTitle(TITLE);
  234 + firmwareInfo.setVersion(VERSION);
  235 + firmwareService.saveFirmwareInfo(firmwareInfo);
  236 +
  237 + FirmwareInfo newFirmwareInfo = new FirmwareInfo();
  238 + newFirmwareInfo.setTenantId(tenantId);
  239 + newFirmwareInfo.setTitle(TITLE);
  240 + newFirmwareInfo.setVersion(VERSION);
  241 + firmwareService.saveFirmwareInfo(newFirmwareInfo);
  242 + }
  243 +
  244 + @Test(expected = DataValidationException.class)
  245 + public void testSaveFirmwareWithExistingTitleAndVersion() {
  246 + Firmware firmware = new Firmware();
  247 + firmware.setTenantId(tenantId);
  248 + firmware.setTitle(TITLE);
  249 + firmware.setVersion(VERSION);
  250 + firmware.setFileName(FILE_NAME);
  251 + firmware.setContentType(CONTENT_TYPE);
  252 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  253 + firmware.setChecksum(CHECKSUM);
  254 + firmware.setData(DATA);
  255 + firmwareService.saveFirmware(firmware);
  256 +
  257 + Firmware newFirmware = new Firmware();
  258 + newFirmware.setTenantId(tenantId);
  259 + newFirmware.setTitle(TITLE);
  260 + newFirmware.setVersion(VERSION);
  261 + newFirmware.setFileName(FILE_NAME);
  262 + newFirmware.setContentType(CONTENT_TYPE);
  263 + newFirmware.setData(DATA);
  264 + firmwareService.saveFirmware(newFirmware);
  265 + }
  266 +
  267 + @Test(expected = DataValidationException.class)
  268 + public void testDeleteFirmwareWithReferenceByDevice() {
  269 + Firmware firmware = new Firmware();
  270 + firmware.setTenantId(tenantId);
  271 + firmware.setTitle(TITLE);
  272 + firmware.setVersion(VERSION);
  273 + firmware.setFileName(FILE_NAME);
  274 + firmware.setContentType(CONTENT_TYPE);
  275 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  276 + firmware.setChecksum(CHECKSUM);
  277 + firmware.setData(DATA);
  278 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  279 +
  280 + Device device = new Device();
  281 + device.setTenantId(tenantId);
  282 + device.setName("My device");
  283 + device.setType("default");
  284 + device.setFirmwareId(savedFirmware.getId());
  285 + Device savedDevice = deviceService.saveDevice(device);
  286 +
  287 + try {
  288 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  289 + } finally {
  290 + deviceService.deleteDevice(tenantId, savedDevice.getId());
  291 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  292 + }
  293 + }
  294 +
  295 + @Test(expected = DataValidationException.class)
  296 + public void testDeleteFirmwareWithReferenceByDeviceProfile() {
  297 + Firmware firmware = new Firmware();
  298 + firmware.setTenantId(tenantId);
  299 + firmware.setTitle(TITLE);
  300 + firmware.setVersion(VERSION);
  301 + firmware.setFileName(FILE_NAME);
  302 + firmware.setContentType(CONTENT_TYPE);
  303 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  304 + firmware.setChecksum(CHECKSUM);
  305 + firmware.setData(DATA);
  306 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  307 +
  308 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
  309 + deviceProfile.setFirmwareId(savedFirmware.getId());
  310 + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
  311 +
  312 + try {
  313 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  314 + } finally {
  315 + deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
  316 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  317 + }
  318 + }
  319 +
  320 + @Test
  321 + public void testFindFirmwareById() {
  322 + Firmware firmware = new Firmware();
  323 + firmware.setTenantId(tenantId);
  324 + firmware.setTitle(TITLE);
  325 + firmware.setVersion(VERSION);
  326 + firmware.setFileName(FILE_NAME);
  327 + firmware.setContentType(CONTENT_TYPE);
  328 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  329 + firmware.setChecksum(CHECKSUM);
  330 + firmware.setData(DATA);
  331 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  332 +
  333 + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
  334 + Assert.assertNotNull(foundFirmware);
  335 + Assert.assertEquals(savedFirmware, foundFirmware);
  336 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  337 + }
  338 +
  339 + @Test
  340 + public void testFindFirmwareInfoById() {
  341 + FirmwareInfo firmware = new FirmwareInfo();
  342 + firmware.setTenantId(tenantId);
  343 + firmware.setTitle(TITLE);
  344 + firmware.setVersion(VERSION);
  345 + FirmwareInfo savedFirmware = firmwareService.saveFirmwareInfo(firmware);
  346 +
  347 + FirmwareInfo foundFirmware = firmwareService.findFirmwareInfoById(tenantId, savedFirmware.getId());
  348 + Assert.assertNotNull(foundFirmware);
  349 + Assert.assertEquals(savedFirmware, foundFirmware);
  350 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  351 + }
  352 +
  353 + @Test
  354 + public void testDeleteFirmware() {
  355 + Firmware firmware = new Firmware();
  356 + firmware.setTenantId(tenantId);
  357 + firmware.setTitle(TITLE);
  358 + firmware.setVersion(VERSION);
  359 + firmware.setFileName(FILE_NAME);
  360 + firmware.setContentType(CONTENT_TYPE);
  361 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  362 + firmware.setChecksum(CHECKSUM);
  363 + firmware.setData(DATA);
  364 + Firmware savedFirmware = firmwareService.saveFirmware(firmware);
  365 +
  366 + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
  367 + Assert.assertNotNull(foundFirmware);
  368 + firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
  369 + foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
  370 + Assert.assertNull(foundFirmware);
  371 + }
  372 +
  373 + @Test
  374 + public void testFindTenantFirmwaresByTenantId() {
  375 + List<FirmwareInfo> firmwares = new ArrayList<>();
  376 + for (int i = 0; i < 165; i++) {
  377 + Firmware firmware = new Firmware();
  378 + firmware.setTenantId(tenantId);
  379 + firmware.setTitle(TITLE);
  380 + firmware.setVersion(VERSION + i);
  381 + firmware.setFileName(FILE_NAME);
  382 + firmware.setContentType(CONTENT_TYPE);
  383 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  384 + firmware.setChecksum(CHECKSUM);
  385 + firmware.setData(DATA);
  386 +
  387 + FirmwareInfo info = new FirmwareInfo(firmwareService.saveFirmware(firmware));
  388 + info.setHasData(true);
  389 + firmwares.add(info);
  390 + }
  391 +
  392 + List<FirmwareInfo> loadedFirmwares = new ArrayList<>();
  393 + PageLink pageLink = new PageLink(16);
  394 + PageData<FirmwareInfo> pageData;
  395 + do {
  396 + pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink);
  397 + loadedFirmwares.addAll(pageData.getData());
  398 + if (pageData.hasNext()) {
  399 + pageLink = pageLink.nextPageLink();
  400 + }
  401 + } while (pageData.hasNext());
  402 +
  403 + Collections.sort(firmwares, idComparator);
  404 + Collections.sort(loadedFirmwares, idComparator);
  405 +
  406 + Assert.assertEquals(firmwares, loadedFirmwares);
  407 +
  408 + firmwareService.deleteFirmwaresByTenantId(tenantId);
  409 +
  410 + pageLink = new PageLink(31);
  411 + pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink);
  412 + Assert.assertFalse(pageData.hasNext());
  413 + Assert.assertTrue(pageData.getData().isEmpty());
  414 + }
  415 +
  416 + @Test
  417 + public void testFindTenantFirmwaresByTenantIdAndHasData() {
  418 + List<FirmwareInfo> firmwares = new ArrayList<>();
  419 + for (int i = 0; i < 165; i++) {
  420 + FirmwareInfo firmwareInfo = new FirmwareInfo();
  421 + firmwareInfo.setTenantId(tenantId);
  422 + firmwareInfo.setTitle(TITLE);
  423 + firmwareInfo.setVersion(VERSION + i);
  424 + firmwares.add(firmwareService.saveFirmwareInfo(firmwareInfo));
  425 + }
  426 +
  427 + List<FirmwareInfo> loadedFirmwares = new ArrayList<>();
  428 + PageLink pageLink = new PageLink(16);
  429 + PageData<FirmwareInfo> pageData;
  430 + do {
  431 + pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, false, pageLink);
  432 + loadedFirmwares.addAll(pageData.getData());
  433 + if (pageData.hasNext()) {
  434 + pageLink = pageLink.nextPageLink();
  435 + }
  436 + } while (pageData.hasNext());
  437 +
  438 + Collections.sort(firmwares, idComparator);
  439 + Collections.sort(loadedFirmwares, idComparator);
  440 +
  441 + Assert.assertEquals(firmwares, loadedFirmwares);
  442 +
  443 + firmwares.forEach(f -> {
  444 + Firmware firmware = new Firmware(f.getId());
  445 + firmware.setCreatedTime(f.getCreatedTime());
  446 + firmware.setTenantId(f.getTenantId());
  447 + firmware.setTitle(f.getTitle());
  448 + firmware.setVersion(f.getVersion());
  449 + firmware.setFileName(FILE_NAME);
  450 + firmware.setContentType(CONTENT_TYPE);
  451 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  452 + firmware.setChecksum(CHECKSUM);
  453 + firmware.setData(DATA);
  454 + firmwareService.saveFirmware(firmware);
  455 + f.setHasData(true);
  456 + });
  457 +
  458 + loadedFirmwares = new ArrayList<>();
  459 + pageLink = new PageLink(16);
  460 + do {
  461 + pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, true, pageLink);
  462 + loadedFirmwares.addAll(pageData.getData());
  463 + if (pageData.hasNext()) {
  464 + pageLink = pageLink.nextPageLink();
  465 + }
  466 + } while (pageData.hasNext());
  467 +
  468 + Collections.sort(firmwares, idComparator);
  469 + Collections.sort(loadedFirmwares, idComparator);
  470 +
  471 + Assert.assertEquals(firmwares, loadedFirmwares);
  472 +
  473 + firmwareService.deleteFirmwaresByTenantId(tenantId);
  474 +
  475 + pageLink = new PageLink(31);
  476 + pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink);
  477 + Assert.assertFalse(pageData.hasNext());
  478 + Assert.assertTrue(pageData.getData().isEmpty());
  479 + }
  480 +
  481 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.BaseFirmwareServiceTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class FirmwareServiceSqlTest extends BaseFirmwareServiceTest {
  23 +}
@@ -36,6 +36,9 @@ caffeine.specs.tenantProfiles.maxSize=100000 @@ -36,6 +36,9 @@ caffeine.specs.tenantProfiles.maxSize=100000
36 caffeine.specs.deviceProfiles.timeToLiveInMinutes=1440 36 caffeine.specs.deviceProfiles.timeToLiveInMinutes=1440
37 caffeine.specs.deviceProfiles.maxSize=100000 37 caffeine.specs.deviceProfiles.maxSize=100000
38 38
  39 +caffeine.specs.firmwares.timeToLiveInMinutes=1440
  40 +caffeine.specs.firmwares.maxSize=100000
  41 +
39 redis.connection.host=localhost 42 redis.connection.host=localhost
40 redis.connection.port=6379 43 redis.connection.port=6379
41 redis.connection.db=0 44 redis.connection.db=0
@@ -29,4 +29,5 @@ DROP TABLE IF EXISTS oauth2_client_registration_info; @@ -29,4 +29,5 @@ DROP TABLE IF EXISTS oauth2_client_registration_info;
29 DROP TABLE IF EXISTS oauth2_client_registration_template; 29 DROP TABLE IF EXISTS oauth2_client_registration_template;
30 DROP TABLE IF EXISTS api_usage_state; 30 DROP TABLE IF EXISTS api_usage_state;
31 DROP TABLE IF EXISTS resource; 31 DROP TABLE IF EXISTS resource;
  32 +DROP TABLE IF EXISTS firmware;
32 DROP FUNCTION IF EXISTS to_uuid; 33 DROP FUNCTION IF EXISTS to_uuid;
@@ -30,3 +30,4 @@ DROP TABLE IF EXISTS oauth2_client_registration_info; @@ -30,3 +30,4 @@ DROP TABLE IF EXISTS oauth2_client_registration_info;
30 DROP TABLE IF EXISTS oauth2_client_registration_template; 30 DROP TABLE IF EXISTS oauth2_client_registration_template;
31 DROP TABLE IF EXISTS api_usage_state; 31 DROP TABLE IF EXISTS api_usage_state;
32 DROP TABLE IF EXISTS resource; 32 DROP TABLE IF EXISTS resource;
  33 +DROP TABLE IF EXISTS firmware;
@@ -28,4 +28,6 @@ DROP TABLE IF EXISTS tb_schema_settings; @@ -28,4 +28,6 @@ DROP TABLE IF EXISTS tb_schema_settings;
28 DROP TABLE IF EXISTS oauth2_client_registration; 28 DROP TABLE IF EXISTS oauth2_client_registration;
29 DROP TABLE IF EXISTS oauth2_client_registration_info; 29 DROP TABLE IF EXISTS oauth2_client_registration_info;
30 DROP TABLE IF EXISTS oauth2_client_registration_template; 30 DROP TABLE IF EXISTS oauth2_client_registration_template;
31 -DROP TABLE IF EXISTS api_usage_state;  
  31 +DROP TABLE IF EXISTS api_usage_state;
  32 +DROP TABLE IF EXISTS resource;
  33 +DROP TABLE IF EXISTS firmware;
@@ -74,6 +74,7 @@ import { @@ -74,6 +74,7 @@ import {
74 StringOperation 74 StringOperation
75 } from '@shared/models/query/query.models'; 75 } from '@shared/models/query/query.models';
76 import { alarmFields } from '@shared/models/alarm.models'; 76 import { alarmFields } from '@shared/models/alarm.models';
  77 +import { FirmwareService } from '@core/http/firmware.service';
77 78
78 @Injectable({ 79 @Injectable({
79 providedIn: 'root' 80 providedIn: 'root'
@@ -93,6 +94,7 @@ export class EntityService { @@ -93,6 +94,7 @@ export class EntityService {
93 private dashboardService: DashboardService, 94 private dashboardService: DashboardService,
94 private entityRelationService: EntityRelationService, 95 private entityRelationService: EntityRelationService,
95 private attributeService: AttributeService, 96 private attributeService: AttributeService,
  97 + private firmwareService: FirmwareService,
96 private utils: UtilsService 98 private utils: UtilsService
97 ) { } 99 ) { }
98 100
@@ -128,6 +130,9 @@ export class EntityService { @@ -128,6 +130,9 @@ export class EntityService {
128 case EntityType.ALARM: 130 case EntityType.ALARM:
129 console.error('Get Alarm Entity is not implemented!'); 131 console.error('Get Alarm Entity is not implemented!');
130 break; 132 break;
  133 + case EntityType.FIRMWARE:
  134 + observable = this.firmwareService.getFirmwareInfo(entityId, config);
  135 + break;
131 } 136 }
132 return observable; 137 return observable;
133 } 138 }
@@ -326,6 +331,10 @@ export class EntityService { @@ -326,6 +331,10 @@ export class EntityService {
326 case EntityType.ALARM: 331 case EntityType.ALARM:
327 console.error('Get Alarm Entities is not implemented!'); 332 console.error('Get Alarm Entities is not implemented!');
328 break; 333 break;
  334 + case EntityType.FIRMWARE:
  335 + pageLink.sortOrder.property = 'title';
  336 + entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config);
  337 + break;
329 } 338 }
330 return entitiesObservable; 339 return entitiesObservable;
331 } 340 }
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { HttpClient } from '@angular/common/http';
  19 +import { PageLink } from '@shared/models/page/page-link';
  20 +import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils';
  21 +import { Observable } from 'rxjs';
  22 +import { PageData } from '@shared/models/page/page-data';
  23 +import { Firmware, FirmwareInfo } from '@shared/models/firmware.models';
  24 +import { catchError, map, mergeMap } from 'rxjs/operators';
  25 +import { deepClone, isDefinedAndNotNull } from '@core/utils';
  26 +
  27 +@Injectable({
  28 + providedIn: 'root'
  29 +})
  30 +export class FirmwareService {
  31 + constructor(
  32 + private http: HttpClient
  33 + ) {
  34 +
  35 + }
  36 +
  37 + public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable<PageData<FirmwareInfo>> {
  38 + let url = `/api/firmwares`;
  39 + if (isDefinedAndNotNull(hasData)) {
  40 + url += `/${hasData}`;
  41 + }
  42 + url += `${pageLink.toQuery()}`;
  43 + return this.http.get<PageData<FirmwareInfo>>(url, defaultHttpOptionsFromConfig(config));
  44 + }
  45 +
  46 + public getFirmware(firmwareId: string, config?: RequestConfig): Observable<Firmware> {
  47 + return this.http.get<Firmware>(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config));
  48 + }
  49 +
  50 + public getFirmwareInfo(firmwareId: string, config?: RequestConfig): Observable<FirmwareInfo> {
  51 + return this.http.get<FirmwareInfo>(`/api/firmware/info/${firmwareId}`, defaultHttpOptionsFromConfig(config));
  52 + }
  53 +
  54 + public downloadFirmware(firmwareId: string): Observable<any> {
  55 + return this.http.get(`/api/firmware/${firmwareId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe(
  56 + map((response) => {
  57 + const headers = response.headers;
  58 + const filename = headers.get('x-filename');
  59 + const contentType = headers.get('content-type');
  60 + const linkElement = document.createElement('a');
  61 + try {
  62 + const blob = new Blob([response.body], { type: contentType });
  63 + const url = URL.createObjectURL(blob);
  64 + linkElement.setAttribute('href', url);
  65 + linkElement.setAttribute('download', filename);
  66 + const clickEvent = new MouseEvent('click',
  67 + {
  68 + view: window,
  69 + bubbles: true,
  70 + cancelable: false
  71 + }
  72 + );
  73 + linkElement.dispatchEvent(clickEvent);
  74 + return null;
  75 + } catch (e) {
  76 + throw e;
  77 + }
  78 + })
  79 + );
  80 + }
  81 +
  82 + public saveFirmware(firmware: Firmware, config?: RequestConfig): Observable<Firmware> {
  83 + if (!firmware.file) {
  84 + return this.saveFirmwareInfo(firmware, config);
  85 + }
  86 + const firmwareInfo = deepClone(firmware);
  87 + delete firmwareInfo.file;
  88 + delete firmwareInfo.checksum;
  89 + delete firmwareInfo.checksumAlgorithm;
  90 + return this.saveFirmwareInfo(firmwareInfo, config).pipe(
  91 + mergeMap(res => {
  92 + return this.uploadFirmwareFile(res.id.id, firmware.file, firmware.checksumAlgorithm, firmware.checksum).pipe(
  93 + catchError(() => this.deleteFirmware(res.id.id))
  94 + );
  95 + })
  96 + );
  97 + }
  98 +
  99 + public saveFirmwareInfo(firmware: FirmwareInfo, config?: RequestConfig): Observable<Firmware> {
  100 + return this.http.post<Firmware>('/api/firmware', firmware, defaultHttpOptionsFromConfig(config));
  101 + }
  102 +
  103 + public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm?: string,
  104 + checksum?: string, config?: RequestConfig): Observable<any> {
  105 + if (!config) {
  106 + config = {};
  107 + }
  108 + const formData = new FormData();
  109 + formData.append('file', file);
  110 + let url = `/api/firmware/${firmwareId}`;
  111 + if (checksumAlgorithm && checksum) {
  112 + url += `?checksumAlgorithm=${checksumAlgorithm}&checksum=${checksum}`;
  113 + }
  114 + return this.http.post(url, formData,
  115 + defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
  116 + }
  117 +
  118 + public deleteFirmware(firmwareId: string, config?: RequestConfig) {
  119 + return this.http.delete(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config));
  120 + }
  121 +
  122 +}
@@ -39,3 +39,11 @@ export function defaultHttpOptions(ignoreLoading: boolean = false, @@ -39,3 +39,11 @@ export function defaultHttpOptions(ignoreLoading: boolean = false,
39 params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) 39 params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest))
40 }; 40 };
41 } 41 }
  42 +
  43 +export function defaultHttpUploadOptions(ignoreLoading: boolean = false,
  44 + ignoreErrors: boolean = false,
  45 + resendRequest: boolean = false) {
  46 + return {
  47 + params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest))
  48 + };
  49 +}
@@ -18,10 +18,10 @@ import { Injectable } from '@angular/core'; @@ -18,10 +18,10 @@ import { Injectable } from '@angular/core';
18 import { HttpClient } from '@angular/common/http'; 18 import { HttpClient } from '@angular/common/http';
19 import { PageLink } from '@shared/models/page/page-link'; 19 import { PageLink } from '@shared/models/page/page-link';
20 import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; 20 import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
21 -import { Observable } from 'rxjs'; 21 +import { forkJoin, Observable, of } from 'rxjs';
22 import { PageData } from '@shared/models/page/page-data'; 22 import { PageData } from '@shared/models/page/page-data';
23 import { Resource, ResourceInfo } from '@shared/models/resource.models'; 23 import { Resource, ResourceInfo } from '@shared/models/resource.models';
24 -import { map } from 'rxjs/operators'; 24 +import { catchError, map, mergeMap } from 'rxjs/operators';
25 25
26 @Injectable({ 26 @Injectable({
27 providedIn: 'root' 27 providedIn: 'root'
@@ -70,6 +70,25 @@ export class ResourceService { @@ -70,6 +70,25 @@ export class ResourceService {
70 ); 70 );
71 } 71 }
72 72
  73 + public saveResources(resources: Resource[], config?: RequestConfig) {
  74 + let partSize = 100;
  75 + partSize = resources.length > partSize ? partSize : resources.length;
  76 + const resourceObservables = [];
  77 + for (let i = 0; i < partSize; i++) {
  78 + resourceObservables.push(this.saveResource(resources[i], config).pipe(catchError(error => of(error))));
  79 + }
  80 + return forkJoin(resourceObservables).pipe(
  81 + mergeMap((resource) => {
  82 + resources.splice(0, partSize);
  83 + if (resources.length) {
  84 + return this.saveResources(resources, config);
  85 + } else {
  86 + return of(resource);
  87 + }
  88 + })
  89 + );
  90 + }
  91 +
73 public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> { 92 public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> {
74 return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config)); 93 return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config));
75 } 94 }
@@ -281,6 +281,13 @@ export class MenuService { @@ -281,6 +281,13 @@ export class MenuService {
281 }, 281 },
282 { 282 {
283 id: guid(), 283 id: guid(),
  284 + name: 'firmware.firmware',
  285 + type: 'link',
  286 + path: '/firmwares',
  287 + icon: 'memory'
  288 + },
  289 + {
  290 + id: guid(),
284 name: 'entity-view.entity-views', 291 name: 'entity-view.entity-views',
285 type: 'link', 292 type: 'link',
286 path: '/entityViews', 293 path: '/entityViews',
@@ -379,6 +386,11 @@ export class MenuService { @@ -379,6 +386,11 @@ export class MenuService {
379 icon: 'mdi:alpha-d-box', 386 icon: 'mdi:alpha-d-box',
380 isMdiIcon: true, 387 isMdiIcon: true,
381 path: '/deviceProfiles' 388 path: '/deviceProfiles'
  389 + },
  390 + {
  391 + name: 'firmware.firmware',
  392 + icon: 'memory',
  393 + path: '/firmwares'
382 } 394 }
383 ] 395 ]
384 }, 396 },
@@ -407,7 +407,7 @@ export function sortObjectKeys<T>(obj: T): T { @@ -407,7 +407,7 @@ export function sortObjectKeys<T>(obj: T): T {
407 } 407 }
408 408
409 export function deepTrim<T>(obj: T): T { 409 export function deepTrim<T>(obj: T): T {
410 - if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null) { 410 + if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || obj instanceof File) {
411 return obj; 411 return obj;
412 } 412 }
413 return Object.keys(obj).reduce((acc, curr) => { 413 return Object.keys(obj).reduce((acc, curr) => {