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 24 import org.apache.commons.lang3.StringUtils;
25 25 import org.springframework.beans.factory.annotation.Autowired;
26 26 import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.http.MediaType;
27 28 import org.springframework.security.core.Authentication;
28 29 import org.springframework.security.core.context.SecurityContextHolder;
29 30 import org.springframework.web.bind.annotation.ExceptionHandler;
... ... @@ -37,6 +38,8 @@ import org.thingsboard.server.common.data.DeviceProfile;
37 38 import org.thingsboard.server.common.data.EntityType;
38 39 import org.thingsboard.server.common.data.EntityView;
39 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 43 import org.thingsboard.server.common.data.HasName;
41 44 import org.thingsboard.server.common.data.HasTenantId;
42 45 import org.thingsboard.server.common.data.TbResourceInfo;
... ... @@ -61,6 +64,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
61 64 import org.thingsboard.server.common.data.id.EntityId;
62 65 import org.thingsboard.server.common.data.id.EntityIdFactory;
63 66 import org.thingsboard.server.common.data.id.EntityViewId;
  67 +import org.thingsboard.server.common.data.id.FirmwareId;
64 68 import org.thingsboard.server.common.data.id.TbResourceId;
65 69 import org.thingsboard.server.common.data.id.RuleChainId;
66 70 import org.thingsboard.server.common.data.id.RuleNodeId;
... ... @@ -97,6 +101,7 @@ import org.thingsboard.server.dao.device.DeviceService;
97 101 import org.thingsboard.server.dao.entityview.EntityViewService;
98 102 import org.thingsboard.server.dao.exception.DataValidationException;
99 103 import org.thingsboard.server.dao.exception.IncorrectParameterException;
  104 +import org.thingsboard.server.dao.firmware.FirmwareService;
100 105 import org.thingsboard.server.dao.model.ModelConstants;
101 106 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
102 107 import org.thingsboard.server.dao.oauth2.OAuth2Service;
... ... @@ -114,6 +119,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
114 119 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
115 120 import org.thingsboard.server.queue.util.TbCoreComponent;
116 121 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  122 +import org.thingsboard.server.service.firmware.FirmwareStateService;
117 123 import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
118 124 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
119 125 import org.thingsboard.server.service.queue.TbClusterService;
... ... @@ -233,6 +239,12 @@ public abstract class BaseController {
233 239 protected TbResourceService resourceService;
234 240
235 241 @Autowired
  242 + protected FirmwareService firmwareService;
  243 +
  244 + @Autowired
  245 + protected FirmwareStateService firmwareStateService;
  246 +
  247 + @Autowired
236 248 protected TbQueueProducerProvider producerProvider;
237 249
238 250 @Autowired
... ... @@ -470,6 +482,9 @@ public abstract class BaseController {
470 482 case TB_RESOURCE:
471 483 checkResourceId(new TbResourceId(entityId.getId()), operation);
472 484 return;
  485 + case FIRMWARE:
  486 + checkFirmwareId(new FirmwareId(entityId.getId()), operation);
  487 + return;
473 488 default:
474 489 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
475 490 }
... ... @@ -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 743 @SuppressWarnings("unchecked")
705 744 protected <I extends EntityId> I emptyId(EntityType entityType) {
706 745 return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
... ... @@ -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 70 import java.io.IOException;
71 71 import java.util.ArrayList;
72 72 import java.util.List;
  73 +import java.util.Objects;
73 74 import java.util.stream.Collectors;
74 75
75 76 @RestController
... ... @@ -117,13 +118,25 @@ public class DeviceController extends BaseController {
117 118
118 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 134 Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
121 135
122 136 tbClusterService.onDeviceChange(savedDevice, null);
123 137 tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
124 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 141 logEntityAction(savedDevice.getId(), savedDevice,
129 142 savedDevice.getCustomerId(),
... ... @@ -134,12 +147,19 @@ public class DeviceController extends BaseController {
134 147 } else {
135 148 deviceStateService.onDeviceUpdated(savedDevice);
136 149 }
  150 +
  151 + if (isFirmwareChanged) {
  152 + firmwareStateService.update(savedDevice, created);
  153 + }
  154 +
137 155 return savedDevice;
138   - } catch (Exception e) {
  156 + } catch (
  157 + Exception e) {
139 158 logEntityAction(emptyId(EntityType.DEVICE), device,
140 159 null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
141 160 throw handleException(e);
142 161 }
  162 +
143 163 }
144 164
145 165 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
... ...
... ... @@ -43,6 +43,7 @@ import org.thingsboard.server.service.security.permission.Operation;
43 43 import org.thingsboard.server.service.security.permission.Resource;
44 44
45 45 import java.util.List;
  46 +import java.util.Objects;
46 47 import java.util.UUID;
47 48
48 49 @RestController
... ... @@ -143,6 +144,15 @@ public class DeviceProfileController extends BaseController {
143 144
144 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 156 DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
147 157
148 158 tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
... ... @@ -153,6 +163,10 @@ public class DeviceProfileController extends BaseController {
153 163 null,
154 164 created ? ActionType.ADDED : ActionType.UPDATED, null);
155 165
  166 + if (isFirmwareChanged) {
  167 + firmwareStateService.update(savedDeviceProfile);
  168 + }
  169 +
156 170 return savedDeviceProfile;
157 171 } catch (Exception e) {
158 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 55
56 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 58 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
65 59 @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
66 60 @ResponseBody
... ... @@ -187,7 +181,7 @@ public class TbResourceController extends BaseController {
187 181 @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE)
188 182 @ResponseBody
189 183 public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException {
190   - checkParameter("resourceId", strResourceId);
  184 + checkParameter(RESOURCE_ID, strResourceId);
191 185 try {
192 186 TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
193 187 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);
... ...
... ... @@ -230,7 +230,6 @@ public class ThingsboardInstallService {
230 230 systemDataLoaderService.createAdminSettings();
231 231 systemDataLoaderService.loadSystemWidgets();
232 232 systemDataLoaderService.createOAuth2Templates();
233   - systemDataLoaderService.loadSystemLwm2mResources();
234 233 // systemDataLoaderService.loadSystemPlugins();
235 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 58 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
59 59 import org.thingsboard.server.common.data.query.DynamicValue;
60 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 61 import org.thingsboard.server.common.data.query.EntityKeyValueType;
64 62 import org.thingsboard.server.common.data.query.FilterPredicateValue;
65   -import org.thingsboard.server.common.data.query.KeyFilter;
66 63 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
67 64 import org.thingsboard.server.common.data.security.Authority;
68 65 import org.thingsboard.server.common.data.security.DeviceCredentials;
... ... @@ -445,11 +442,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
445 442 installScripts.loadSystemWidgets();
446 443 }
447 444
448   - @Override
449   - public void loadSystemLwm2mResources() throws Exception {
450   - installScripts.loadSystemLwm2mResources();
451   - }
452   -
453 445 private User createUser(Authority authority,
454 446 TenantId tenantId,
455 447 CustomerId customerId,
... ...
... ... @@ -22,8 +22,6 @@ import org.springframework.beans.factory.annotation.Value;
22 22 import org.springframework.stereotype.Component;
23 23 import org.springframework.util.StringUtils;
24 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 25 import org.thingsboard.server.common.data.id.CustomerId;
28 26 import org.thingsboard.server.common.data.id.EntityId;
29 27 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
33 31 import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
34 32 import org.thingsboard.server.common.data.widget.WidgetsBundle;
35 33 import org.thingsboard.server.dao.dashboard.DashboardService;
36   -import org.thingsboard.server.dao.exception.DataValidationException;
37 34 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
38 35 import org.thingsboard.server.dao.resource.TbResourceService;
39 36 import org.thingsboard.server.dao.rule.RuleChainService;
... ... @@ -45,7 +42,6 @@ import java.nio.file.DirectoryStream;
45 42 import java.nio.file.Files;
46 43 import java.nio.file.Path;
47 44 import java.nio.file.Paths;
48   -import java.util.Base64;
49 45 import java.util.Optional;
50 46
51 47 import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
... ... @@ -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 195 public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
223 196 Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
224 197 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
... ...
... ... @@ -449,26 +449,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
449 449 case "3.2.2":
450 450 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
451 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 455 log.info("Schema updated.");
  456 + } catch (Exception e) {
  457 + log.error("Failed updating schema!!!", e);
472 458 }
473 459 break;
474 460 default:
... ...
... ... @@ -33,6 +33,4 @@ public interface SystemDataLoaderService {
33 33
34 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 37 TENANT_PROFILE(EntityType.TENANT_PROFILE),
38 38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
39 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 43 private final EntityType entityType;
43 44
... ...
... ... @@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
42 42 put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
43 43 put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
44 44 put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
  45 + put(Resource.FIRMWARE, tenantEntityPermissionChecker);
45 46 }
46 47
47 48 public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {
... ...
... ... @@ -23,16 +23,19 @@ import com.google.common.util.concurrent.ListenableFuture;
23 23 import com.google.common.util.concurrent.MoreExecutors;
24 24 import com.google.protobuf.ByteString;
25 25 import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.cache.CacheManager;
26 27 import org.springframework.stereotype.Service;
27 28 import org.springframework.util.StringUtils;
28 29 import org.thingsboard.common.util.JacksonUtil;
  30 +import org.thingsboard.server.cache.firmware.FirmwareCacheWriter;
29 31 import org.thingsboard.server.common.data.ApiUsageState;
30 32 import org.thingsboard.server.common.data.DataConstants;
31 33 import org.thingsboard.server.common.data.Device;
32 34 import org.thingsboard.server.common.data.DeviceProfile;
33 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 37 import org.thingsboard.server.common.data.ResourceType;
  38 +import org.thingsboard.server.common.data.TbResource;
36 39 import org.thingsboard.server.common.data.TenantProfile;
37 40 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
38 41 import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
... ... @@ -40,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileC
40 43 import org.thingsboard.server.common.data.id.CustomerId;
41 44 import org.thingsboard.server.common.data.id.DeviceId;
42 45 import org.thingsboard.server.common.data.id.DeviceProfileId;
  46 +import org.thingsboard.server.common.data.id.FirmwareId;
43 47 import org.thingsboard.server.common.data.id.TenantId;
44 48 import org.thingsboard.server.common.data.relation.EntityRelation;
45 49 import org.thingsboard.server.common.data.security.DeviceCredentials;
... ... @@ -55,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService;
55 59 import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
56 60 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
57 61 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
  62 +import org.thingsboard.server.dao.firmware.FirmwareService;
58 63 import org.thingsboard.server.dao.relation.RelationService;
59 64 import org.thingsboard.server.dao.resource.TbResourceService;
60 65 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
... ... @@ -66,7 +71,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro
66 71 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
67 72 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
68 73 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
69   -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;
70 74 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
71 75 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
72 76 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
... ... @@ -109,6 +113,8 @@ public class DefaultTransportApiService implements TransportApiService {
109 113 private final DataDecodingEncodingService dataDecodingEncodingService;
110 114 private final DeviceProvisionService deviceProvisionService;
111 115 private final TbResourceService resourceService;
  116 + private final FirmwareService firmwareService;
  117 + private final FirmwareCacheWriter firmwareCacheWriter;
112 118
113 119 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
114 120
... ... @@ -117,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService {
117 123 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
118 124 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
119 125 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
120   - DeviceProvisionService deviceProvisionService, TbResourceService resourceService) {
  126 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, CacheManager cacheManager, FirmwareCacheWriter firmwareCacheWriter) {
121 127 this.deviceProfileCache = deviceProfileCache;
122 128 this.tenantProfileCache = tenantProfileCache;
123 129 this.apiUsageStateService = apiUsageStateService;
... ... @@ -130,6 +136,8 @@ public class DefaultTransportApiService implements TransportApiService {
130 136 this.dataDecodingEncodingService = dataDecodingEncodingService;
131 137 this.deviceProvisionService = deviceProvisionService;
132 138 this.resourceService = resourceService;
  139 + this.firmwareService = firmwareService;
  140 + this.firmwareCacheWriter = firmwareCacheWriter;
133 141 }
134 142
135 143 @Override
... ... @@ -166,6 +174,9 @@ public class DefaultTransportApiService implements TransportApiService {
166 174 } else if (transportApiRequestMsg.hasResourceRequestMsg()) {
167 175 return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
168 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 181 return Futures.transform(getEmptyTransportApiResponseFuture(),
171 182 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
... ... @@ -313,14 +324,14 @@ public class DefaultTransportApiService implements TransportApiService {
313 324 } catch (ProvisionFailedException e) {
314 325 return Futures.immediateFuture(getTransportApiResponseMsg(
315 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 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 335 return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build();
325 336 }
326 337 TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
... ... @@ -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 492 private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) {
442 493 TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId()));
443 494 String deviceName = msg.getEndpoint();
... ...
... ... @@ -281,7 +281,7 @@ actors:
281 281 tenant:
282 282 create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}"
283 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 285 sync:
286 286 # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds
287 287 timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}"
... ... @@ -365,6 +365,9 @@ caffeine:
365 365 tokensOutdatageTime:
366 366 timeToLiveInMinutes: 20000
367 367 maxSize: 10000
  368 + firmwares:
  369 + timeToLiveInMinutes: 1440
  370 + maxSize: 100
368 371
369 372 redis:
370 373 # standalone or cluster
... ... @@ -437,6 +440,9 @@ spring.resources.chain:
437 440 content:
438 441 enabled: "true"
439 442
  443 +spring.servlet.multipart.max-file-size: "50MB"
  444 +spring.servlet.multipart.max-request-size: "50MB"
  445 +
440 446 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
441 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 32 public interface TbResourceService {
33 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 37 TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
38 38
... ...
... ... @@ -28,4 +28,5 @@ public class CacheConstants {
28 28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
29 29 public static final String ATTRIBUTES_CACHE = "attributes";
30 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 91 public static final String USERNAME = "username";
92 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 23 import org.thingsboard.server.common.data.id.CustomerId;
24 24 import org.thingsboard.server.common.data.id.DeviceId;
25 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
  26 +import org.thingsboard.server.common.data.id.FirmwareId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.validation.NoXss;
28 29
... ... @@ -48,6 +49,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
48 49 @JsonIgnore
49 50 private byte[] deviceDataBytes;
50 51
  52 + private FirmwareId firmwareId;
  53 +
51 54 public Device() {
52 55 super();
53 56 }
... ... @@ -65,6 +68,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
65 68 this.label = device.getLabel();
66 69 this.deviceProfileId = device.getDeviceProfileId();
67 70 this.setDeviceData(device.getDeviceData());
  71 + this.firmwareId = device.getFirmwareId();
68 72 }
69 73
70 74 public Device updateDevice(Device device) {
... ... @@ -159,6 +163,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
159 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 174 @Override
163 175 public String toString() {
164 176 StringBuilder builder = new StringBuilder();
... ... @@ -175,6 +187,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
175 187 builder.append(", deviceProfileId=");
176 188 builder.append(deviceProfileId);
177 189 builder.append(", deviceData=");
  190 + builder.append(firmwareId);
  191 + builder.append(", firmwareId=");
178 192 builder.append(deviceData);
179 193 builder.append(", additionalInfo=");
180 194 builder.append(getAdditionalInfo());
... ...
... ... @@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
24 24 import org.thingsboard.server.common.data.id.DeviceProfileId;
  25 +import org.thingsboard.server.common.data.id.FirmwareId;
25 26 import org.thingsboard.server.common.data.id.RuleChainId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.validation.NoXss;
... ... @@ -56,6 +57,8 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
56 57 @NoXss
57 58 private String provisionDeviceKey;
58 59
  60 + private FirmwareId firmwareId;
  61 +
59 62 public DeviceProfile() {
60 63 super();
61 64 }
... ...
... ... @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 19 * @author Andrew Shvayka
20 20 */
21 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 26 @EqualsAndHashCode(callSuper = true)
27 27 public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
28 28
  29 + private static final long serialVersionUID = 7282664529021651736L;
  30 +
29 31 private TenantId tenantId;
30 32 private String title;
31 33 private ResourceType resourceType;
... ...
... ... @@ -30,6 +30,9 @@ public class MqttTopics {
30 30 private static final String CLAIM = "/claim";
31 31 private static final String SUB_TOPIC = "+";
32 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 37 private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
35 38 private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
... ... @@ -69,6 +72,13 @@ public class MqttTopics {
69 72 public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST;
70 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 82 private MqttTopics() {
73 83 }
74 84 }
... ...
... ... @@ -70,6 +70,8 @@ public class EntityIdFactory {
70 70 return new ApiUsageStateId(uuid);
71 71 case TB_RESOURCE:
72 72 return new TbResourceId(uuid);
  73 + case FIRMWARE:
  74 + return new FirmwareId(uuid);
73 75 }
74 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 336 }
337 337
338 338 message ProvisionDeviceResponseMsg {
339   - ProvisionResponseStatus status = 1;
  339 + ResponseStatus status = 1;
340 340 CredentialsType credentialsType = 2;
341 341 string credentialsValue = 3;
342 342 }
343 343
344   -enum ProvisionResponseStatus {
  344 +enum ResponseStatus {
345 345 UNKNOWN = 0;
346 346 SUCCESS = 1;
347 347 NOT_FOUND = 2;
348 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 366 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
351 367 message SubscriptionInfoProto {
352 368 int64 lastActivityTime = 1;
... ... @@ -552,6 +568,7 @@ message TransportApiRequestMsg {
552 568 ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
553 569 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
554 570 GetResourceRequestMsg resourceRequestMsg = 9;
  571 + GetFirmwareRequestMsg firmwareRequestMsg = 10;
555 572 }
556 573
557 574 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -562,6 +579,7 @@ message TransportApiResponseMsg {
562 579 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
563 580 LwM2MResponseMsg lwM2MResponseMsg = 6;
564 581 GetResourceResponseMsg resourceResponseMsg = 7;
  582 + GetFirmwareResponseMsg firmwareResponseMsg = 8;
565 583 }
566 584
567 585 /* Messages that are handled by ThingsBoard Core Service */
... ...
... ... @@ -398,7 +398,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
398 398 @Override
399 399 public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) {
400 400 CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED;
401   - if (!msg.getStatus().equals(TransportProtos.ProvisionResponseStatus.SUCCESS)) {
  401 + if (!msg.getStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
402 402 responseCode = CoAP.ResponseCode.BAD_REQUEST;
403 403 }
404 404 if (payloadType.equals(TransportPayloadType.JSON)) {
... ...
... ... @@ -20,7 +20,10 @@ import com.google.gson.JsonParser;
20 20 import lombok.extern.slf4j.Slf4j;
21 21 import org.springframework.beans.factory.annotation.Autowired;
22 22 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  23 +import org.springframework.core.io.ByteArrayResource;
  24 +import org.springframework.http.HttpHeaders;
23 25 import org.springframework.http.HttpStatus;
  26 +import org.springframework.http.MediaType;
24 27 import org.springframework.http.ResponseEntity;
25 28 import org.springframework.util.StringUtils;
26 29 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -41,7 +44,6 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
41 44 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
42 45 import org.thingsboard.server.gen.transport.TransportProtos;
43 46 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
44   -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
45 47 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
46 48 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
47 49 import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
... ... @@ -53,7 +55,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs
53 55 import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
54 56 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
55 57 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
56   -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
57 58 import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
58 59
59 60 import javax.servlet.http.HttpServletRequest;
... ... @@ -204,6 +205,23 @@ public class DeviceApiController {
204 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 225 @RequestMapping(value = "/provision", method = RequestMethod.POST)
208 226 public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) {
209 227 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
... ... @@ -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 314 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
262 315 private final TransportService transportService;
263 316 private final SessionInfoProto sessionInfo;
... ... @@ -338,4 +391,12 @@ public class DeviceApiController {
338 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 29 import java.util.Collection;
30 30 import java.util.LinkedList;
31 31
32   -@Service
33 32 public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
34 33 private static final String SEC_EP = "SEC#EP#";
35 34
... ...
... ... @@ -40,12 +40,14 @@ import io.netty.util.ReferenceCountUtil;
40 40 import io.netty.util.concurrent.Future;
41 41 import io.netty.util.concurrent.GenericFutureListener;
42 42 import lombok.extern.slf4j.Slf4j;
  43 +import org.apache.commons.lang3.StringUtils;
43 44 import org.thingsboard.server.common.data.DataConstants;
44 45 import org.thingsboard.server.common.data.Device;
45 46 import org.thingsboard.server.common.data.DeviceProfile;
46 47 import org.thingsboard.server.common.data.DeviceTransportType;
47 48 import org.thingsboard.server.common.data.TransportPayloadType;
48 49 import org.thingsboard.server.common.data.device.profile.MqttTopics;
  50 +import org.thingsboard.server.common.data.id.FirmwareId;
49 51 import org.thingsboard.server.common.msg.EncryptionUtil;
50 52 import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
51 53 import org.thingsboard.server.common.transport.SessionMsgListener;
... ... @@ -69,10 +71,10 @@ import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
69 71 import org.thingsboard.server.common.transport.util.SslUtil;
70 72
71 73 import javax.net.ssl.SSLPeerUnverifiedException;
72   -import java.security.cert.Certificate;
73   -import java.security.cert.X509Certificate;
74 74 import java.io.IOException;
75 75 import java.net.InetSocketAddress;
  76 +import java.security.cert.Certificate;
  77 +import java.security.cert.X509Certificate;
76 78 import java.util.ArrayList;
77 79 import java.util.List;
78 80 import java.util.Optional;
... ... @@ -80,7 +82,10 @@ import java.util.UUID;
80 82 import java.util.concurrent.ConcurrentHashMap;
81 83 import java.util.concurrent.ConcurrentMap;
82 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 89 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
85 90 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
86 91 import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
... ... @@ -99,6 +104,10 @@ import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE;
99 104 @Slf4j
100 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 111 private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE;
103 112
104 113 private final UUID sessionId;
... ... @@ -112,6 +121,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
112 121 private volatile InetSocketAddress address;
113 122 private volatile GatewaySessionHandler gatewaySessionHandler;
114 123
  124 + private final ConcurrentHashMap<String, String> fwSessions;
  125 + private final ConcurrentHashMap<String, Integer> fwChunkSizes;
  126 +
115 127 MqttTransportHandler(MqttTransportContext context, SslHandler sslHandler) {
116 128 this.sessionId = UUID.randomUUID();
117 129 this.context = context;
... ... @@ -120,6 +132,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
120 132 this.sslHandler = sslHandler;
121 133 this.mqttQoSMap = new ConcurrentHashMap<>();
122 134 this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context);
  135 + this.fwSessions = new ConcurrentHashMap<>();
  136 + this.fwChunkSizes = new ConcurrentHashMap<>();
123 137 }
124 138
125 139 @Override
... ... @@ -280,6 +294,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
280 294
281 295 private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
282 296 try {
  297 + Matcher fwMatcher;
283 298 MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
284 299 if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
285 300 TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
... ... @@ -299,6 +314,38 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
299 314 } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) {
300 315 TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg);
301 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 349 } else {
303 350 transportService.reportActivity(deviceSessionCtx.getSessionInfo());
304 351 ack(ctx, msgId);
... ... @@ -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 475 private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
370 476 if (!checkConnected(ctx, mqttMsg)) {
371 477 return;
... ... @@ -396,6 +502,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
396 502 case MqttTopics.GATEWAY_RPC_TOPIC:
397 503 case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
398 504 case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
  505 + case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
  506 + case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
399 507 registerSubQoS(topic, grantedQoSList, reqQoS);
400 508 break;
401 509 default:
... ...
... ... @@ -21,8 +21,6 @@ import com.google.gson.JsonObject;
21 21 import com.google.gson.JsonParser;
22 22 import com.google.gson.JsonSyntaxException;
23 23 import io.netty.buffer.ByteBuf;
24   -import io.netty.buffer.ByteBufAllocator;
25   -import io.netty.buffer.UnpooledByteBufAllocator;
26 24 import io.netty.handler.codec.mqtt.MqttFixedHeader;
27 25 import io.netty.handler.codec.mqtt.MqttMessage;
28 26 import io.netty.handler.codec.mqtt.MqttMessageType;
... ... @@ -31,10 +29,10 @@ import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
31 29 import lombok.extern.slf4j.Slf4j;
32 30 import org.springframework.stereotype.Component;
33 31 import org.springframework.util.StringUtils;
  32 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
34 33 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
35 34 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
36 35 import org.thingsboard.server.gen.transport.TransportProtos;
37   -import org.thingsboard.server.common.data.device.profile.MqttTopics;
38 36 import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
39 37
40 38 import java.nio.charset.Charset;
... ... @@ -55,7 +53,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
55 53 protected static final Charset UTF8 = StandardCharsets.UTF_8;
56 54
57 55 private static final Gson GSON = new Gson();
58   - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
59 56
60 57 @Override
61 58 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
... ... @@ -153,6 +150,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
153 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 158 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
157 159 String payload = validatePayload(sessionId, payloadData, false);
158 160 try {
... ...
... ... @@ -15,8 +15,14 @@
15 15 */
16 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 22 import io.netty.handler.codec.mqtt.MqttMessage;
  23 +import io.netty.handler.codec.mqtt.MqttMessageType;
19 24 import io.netty.handler.codec.mqtt.MqttPublishMessage;
  25 +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
20 26 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
21 27 import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
22 28 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
... ... @@ -39,6 +45,8 @@ import java.util.Optional;
39 45 */
40 46 public interface MqttTransportAdaptor {
41 47
  48 + ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
  49 +
42 50 PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
43 51
44 52 PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
... ... @@ -69,4 +77,14 @@ public interface MqttTransportAdaptor {
69 77
70 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 21 import com.google.protobuf.InvalidProtocolBufferException;
22 22 import com.google.protobuf.util.JsonFormat;
23 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 24 import io.netty.handler.codec.mqtt.MqttMessage;
28   -import io.netty.handler.codec.mqtt.MqttMessageType;
29 25 import io.netty.handler.codec.mqtt.MqttPublishMessage;
30   -import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
31 26 import lombok.extern.slf4j.Slf4j;
32 27 import org.springframework.stereotype.Component;
33 28 import org.springframework.util.StringUtils;
... ... @@ -46,8 +41,6 @@ import java.util.Optional;
46 41 @Slf4j
47 42 public class ProtoMqttAdaptor implements MqttTransportAdaptor {
48 43
49   - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
50   -
51 44 @Override
52 45 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
53 46 DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
... ... @@ -143,7 +136,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
143 136 }
144 137 }
145 138
146   -
147 139 @Override
148 140 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
149 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 157 }
166 158
167 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 165 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
169 166 if (!StringUtils.isEmpty(responseMsg.getError())) {
170 167 throw new AdaptorException(responseMsg.getError());
... ... @@ -202,15 +199,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
202 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 202 private int getRequestId(String topicName, String topic) {
215 203 return Integer.parseInt(topicName.substring(topic.length()));
216 204 }
... ...
... ... @@ -20,10 +20,9 @@ import lombok.Data;
20 20 import lombok.Getter;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.springframework.beans.factory.annotation.Autowired;
23   -import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.cache.firmware.FirmwareCacheReader;
24 24 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
25 25 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
26   -import org.thingsboard.server.queue.util.TbTransportComponent;
27 26
28 27 import javax.annotation.PostConstruct;
29 28 import javax.annotation.PreDestroy;
... ... @@ -51,6 +50,11 @@ public abstract class TransportContext {
51 50 @Getter
52 51 private ExecutorService executor;
53 52
  53 +
  54 + @Getter
  55 + @Autowired
  56 + private FirmwareCacheReader firmwareCacheReader;
  57 +
54 58 @PostConstruct
55 59 public void init() {
56 60 executor = Executors.newWorkStealingPool(50);
... ...
... ... @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
26 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 29 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
28 30 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
29 31 import org.thingsboard.server.gen.transport.TransportProtos.GetResourceResponseMsg;
... ... @@ -98,6 +100,8 @@ public interface TransportService {
98 100
99 101 void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
100 102
  103 + void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback<GetFirmwareResponseMsg> callback);
  104 +
101 105 SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
102 106
103 107 SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
... ...
... ... @@ -44,7 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
44 44 import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
45 45 import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
46 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 48 import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
49 49 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
50 50 import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg;
... ... @@ -423,12 +423,12 @@ public class JsonConverter {
423 423
424 424 private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
425 425 JsonObject result = new JsonObject();
426   - if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) {
  426 + if (payload.getStatus() == ResponseStatus.NOT_FOUND) {
427 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 430 result.addProperty("errorMsg", "Failed to provision device!");
431   - result.addProperty("status", ProvisionResponseStatus.FAILURE.name());
  431 + result.addProperty("status", ResponseStatus.FAILURE.name());
432 432 } else {
433 433 if (toGateway) {
434 434 result.addProperty("id", requestId);
... ... @@ -445,7 +445,7 @@ public class JsonConverter {
445 445 break;
446 446 }
447 447 result.addProperty("credentialsType", payload.getCredentialsType().name());
448   - result.addProperty("status", ProvisionResponseStatus.SUCCESS.name());
  448 + result.addProperty("status", ResponseStatus.SUCCESS.name());
449 449 }
450 450 return result;
451 451 }
... ...
... ... @@ -377,7 +377,6 @@ public class DefaultTransportService implements TransportService {
377 377 AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
378 378 }
379 379
380   -
381 380 @Override
382 381 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
383 382 if (log.isTraceEnabled()) {
... ... @@ -530,6 +529,19 @@ public class DefaultTransportService implements TransportService {
530 529 }
531 530
532 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 545 public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
534 546 return reportActivityInternal(sessionInfo);
535 547 }
... ... @@ -608,11 +620,11 @@ public class DefaultTransportService implements TransportService {
608 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 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 628 if (log.isTraceEnabled()) {
617 629 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
618 630 }
... ...
... ... @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
40 40 import org.thingsboard.server.common.data.DeviceProfileProvisionType;
41 41 import org.thingsboard.server.common.data.DeviceProfileType;
42 42 import org.thingsboard.server.common.data.DeviceTransportType;
  43 +import org.thingsboard.server.common.data.Firmware;
43 44 import org.thingsboard.server.common.data.Tenant;
44 45 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
45 46 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
... ... @@ -59,6 +60,7 @@ import org.thingsboard.server.common.data.page.PageData;
59 60 import org.thingsboard.server.common.data.page.PageLink;
60 61 import org.thingsboard.server.dao.entity.AbstractEntityService;
61 62 import org.thingsboard.server.dao.exception.DataValidationException;
  63 +import org.thingsboard.server.dao.firmware.FirmwareService;
62 64 import org.thingsboard.server.dao.service.DataValidator;
63 65 import org.thingsboard.server.dao.service.PaginatedRemover;
64 66 import org.thingsboard.server.dao.service.Validator;
... ... @@ -107,6 +109,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
107 109 @Autowired
108 110 private CacheManager cacheManager;
109 111
  112 + @Autowired
  113 + private FirmwareService firmwareService;
  114 +
110 115 private final Lock findOrCreateLock = new ReentrantLock();
111 116
112 117 @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}")
... ... @@ -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 408 @Override
... ...
... ... @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
39 39 import org.thingsboard.server.common.data.EntitySubtype;
40 40 import org.thingsboard.server.common.data.EntityType;
41 41 import org.thingsboard.server.common.data.EntityView;
  42 +import org.thingsboard.server.common.data.Firmware;
42 43 import org.thingsboard.server.common.data.Tenant;
43 44 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
44 45 import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
... ... @@ -68,6 +69,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
68 69 import org.thingsboard.server.dao.entityview.EntityViewService;
69 70 import org.thingsboard.server.dao.event.EventService;
70 71 import org.thingsboard.server.dao.exception.DataValidationException;
  72 +import org.thingsboard.server.dao.firmware.FirmwareService;
71 73 import org.thingsboard.server.dao.service.DataValidator;
72 74 import org.thingsboard.server.dao.service.PaginatedRemover;
73 75 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
... ... @@ -128,6 +130,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
128 130 @Lazy
129 131 private TbTenantProfileCache tenantProfileCache;
130 132
  133 + @Autowired
  134 + private FirmwareService firmwareService;
  135 +
131 136 @Override
132 137 public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
133 138 log.trace("Executing findDeviceInfoById [{}]", deviceId);
... ... @@ -598,6 +603,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
598 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 153 public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
154 154 public static final String DEVICE_DEVICE_PROFILE_ID_PROPERTY = "device_profile_id";
155 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 158 public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
158 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 177 public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
177 178 public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
178 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 183 * Cassandra entityView constants.
... ... @@ -468,6 +470,22 @@ public class ModelConstants {
468 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 489 * Cassandra attributes and timeseries constants.
472 490 */
473 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 27 import org.thingsboard.server.common.data.id.CustomerId;
28 28 import org.thingsboard.server.common.data.id.DeviceId;
29 29 import org.thingsboard.server.common.data.id.DeviceProfileId;
  30 +import org.thingsboard.server.common.data.id.FirmwareId;
30 31 import org.thingsboard.server.common.data.id.TenantId;
31 32 import org.thingsboard.server.dao.model.BaseSqlEntity;
32 33 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -73,6 +74,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
73 74 @Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_ID_PROPERTY, columnDefinition = "uuid")
74 75 private UUID deviceProfileId;
75 76
  77 + @Column(name = ModelConstants.DEVICE_FIRMWARE_ID_PROPERTY, columnDefinition = "uuid")
  78 + private UUID firmwareId;
  79 +
76 80 @Type(type = "jsonb")
77 81 @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb")
78 82 private JsonNode deviceData;
... ... @@ -95,6 +99,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
95 99 if (device.getDeviceProfileId() != null) {
96 100 this.deviceProfileId = device.getDeviceProfileId().getId();
97 101 }
  102 + if (device.getFirmwareId() != null) {
  103 + this.firmwareId = device.getFirmwareId().getId();
  104 + }
98 105 this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class);
99 106 this.name = device.getName();
100 107 this.type = device.getType();
... ... @@ -114,6 +121,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
114 121 this.label = deviceEntity.getLabel();
115 122 this.searchText = deviceEntity.getSearchText();
116 123 this.additionalInfo = deviceEntity.getAdditionalInfo();
  124 + this.firmwareId = deviceEntity.getFirmwareId();
117 125 }
118 126
119 127 @Override
... ... @@ -138,6 +146,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
138 146 if (deviceProfileId != null) {
139 147 device.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
140 148 }
  149 + if (firmwareId != null) {
  150 + device.setFirmwareId(new FirmwareId(firmwareId));
  151 + }
141 152 device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class));
142 153 device.setName(name);
143 154 device.setType(type);
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType;
27 27 import org.thingsboard.server.common.data.DeviceTransportType;
28 28 import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
29 29 import org.thingsboard.server.common.data.id.DeviceProfileId;
  30 +import org.thingsboard.server.common.data.id.FirmwareId;
30 31 import org.thingsboard.server.common.data.id.RuleChainId;
31 32 import org.thingsboard.server.common.data.id.TenantId;
32 33 import org.thingsboard.server.dao.model.BaseSqlEntity;
... ... @@ -89,6 +90,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
89 90 @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY)
90 91 private String provisionDeviceKey;
91 92
  93 + @Column(name=ModelConstants.DEVICE_PROFILE_FIRMWARE_ID_PROPERTY)
  94 + private UUID firmwareId;
  95 +
92 96 public DeviceProfileEntity() {
93 97 super();
94 98 }
... ... @@ -113,6 +117,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
113 117 }
114 118 this.defaultQueueName = deviceProfile.getDefaultQueueName();
115 119 this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
  120 + if (deviceProfile.getFirmwareId() != null) {
  121 + this.firmwareId = deviceProfile.getFirmwareId().getId();
  122 + }
116 123 }
117 124
118 125 @Override
... ... @@ -148,6 +155,11 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
148 155 }
149 156 deviceProfile.setDefaultQueueName(defaultQueueName);
150 157 deviceProfile.setProvisionDeviceKey(provisionDeviceKey);
  158 +
  159 + if (firmwareId != null) {
  160 + deviceProfile.setFirmwareId(new FirmwareId(firmwareId));
  161 + }
  162 +
151 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 48 @Param("searchText") String search,
49 49 Pageable pageable);
50 50
51   - void removeAllByTenantId(UUID tenantId);
52   -
53 51 @Query("SELECT tr FROM TbResourceEntity tr " +
54 52 "WHERE tr.resourceType = :resourceType " +
55 53 "AND LOWER(tr.searchText) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
... ...
... ... @@ -157,6 +157,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
157 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 176 CREATE TABLE IF NOT EXISTS device_profile (
161 177 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
162 178 created_time bigint NOT NULL,
... ... @@ -169,12 +185,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
169 185 search_text varchar(255),
170 186 is_default boolean,
171 187 tenant_id uuid,
  188 + firmware_id uuid,
172 189 default_rule_chain_id uuid,
173 190 default_queue_name varchar(255),
174 191 provision_device_key varchar,
175 192 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
176 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 198 CREATE TABLE IF NOT EXISTS device (
... ... @@ -189,8 +207,10 @@ CREATE TABLE IF NOT EXISTS device (
189 207 label varchar(255),
190 208 search_text varchar(255),
191 209 tenant_id uuid,
  210 + firmware_id uuid,
192 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 216 CREATE TABLE IF NOT EXISTS device_credentials (
... ...
... ... @@ -25,7 +25,7 @@ CREATE OR REPLACE PROCEDURE insert_tb_schema_settings()
25 25 $$
26 26 BEGIN
27 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 29 END IF;
30 30 END;
31 31 $$;
... ... @@ -175,6 +175,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
175 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 194 CREATE TABLE IF NOT EXISTS device_profile (
179 195 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
180 196 created_time bigint NOT NULL,
... ... @@ -187,12 +203,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
187 203 search_text varchar(255),
188 204 is_default boolean,
189 205 tenant_id uuid,
  206 + firmware_id uuid,
190 207 default_rule_chain_id uuid,
191 208 default_queue_name varchar(255),
192 209 provision_device_key varchar,
193 210 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
194 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 216 CREATE TABLE IF NOT EXISTS device (
... ... @@ -207,8 +225,10 @@ CREATE TABLE IF NOT EXISTS device (
207 225 label varchar(255),
208 226 search_text varchar(255),
209 227 tenant_id uuid,
  228 + firmware_id uuid,
210 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 234 CREATE TABLE IF NOT EXISTS device_credentials (
... ...
... ... @@ -51,6 +51,7 @@ import org.thingsboard.server.dao.device.DeviceService;
51 51 import org.thingsboard.server.dao.entity.EntityService;
52 52 import org.thingsboard.server.dao.entityview.EntityViewService;
53 53 import org.thingsboard.server.dao.event.EventService;
  54 +import org.thingsboard.server.dao.firmware.FirmwareService;
54 55 import org.thingsboard.server.dao.relation.RelationService;
55 56 import org.thingsboard.server.dao.resource.TbResourceService;
56 57 import org.thingsboard.server.dao.rule.RuleChainService;
... ... @@ -146,6 +147,9 @@ public abstract class AbstractServiceTest {
146 147 @Autowired
147 148 protected TbResourceService resourceService;
148 149
  150 + @Autowired
  151 + protected FirmwareService firmwareService;
  152 +
149 153 class IdComparator<D extends HasId> implements Comparator<D> {
150 154 @Override
151 155 public int compare(D o1, D o2) {
... ... @@ -192,7 +196,7 @@ public abstract class AbstractServiceTest {
192 196
193 197 @Bean
194 198 public AuditLogLevelFilter auditLogLevelFilter() {
195   - Map<String,String> mask = new HashMap<>();
  199 + Map<String, String> mask = new HashMap<>();
196 200 for (EntityType entityType : EntityType.values()) {
197 201 mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name());
198 202 }
... ...
... ... @@ -27,19 +27,19 @@ import org.junit.Test;
27 27 import org.thingsboard.server.common.data.Device;
28 28 import org.thingsboard.server.common.data.DeviceProfile;
29 29 import org.thingsboard.server.common.data.DeviceProfileInfo;
30   -import org.thingsboard.server.common.data.DeviceProfileType;
31 30 import org.thingsboard.server.common.data.DeviceTransportType;
  31 +import org.thingsboard.server.common.data.Firmware;
32 32 import org.thingsboard.server.common.data.Tenant;
33 33 import org.thingsboard.server.common.data.id.TenantId;
34 34 import org.thingsboard.server.common.data.page.PageData;
35 35 import org.thingsboard.server.common.data.page.PageLink;
36 36 import org.thingsboard.server.dao.exception.DataValidationException;
37 37
  38 +import java.nio.ByteBuffer;
38 39 import java.util.ArrayList;
39 40 import java.util.Collections;
40 41 import java.util.List;
41 42 import java.util.concurrent.ExecutionException;
42   -import java.util.concurrent.ExecutorService;
43 43 import java.util.concurrent.Executors;
44 44 import java.util.stream.Collectors;
45 45
... ... @@ -83,17 +83,48 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
83 83 }
84 84
85 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 117 public void testFindDeviceProfileById() {
87   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  118 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
88 119 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
89 120 DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
90 121 Assert.assertNotNull(foundDeviceProfile);
91 122 Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
92   - }
  123 + }
93 124
94 125 @Test
95 126 public void testFindDeviceProfileInfoById() {
96   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  127 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
97 128 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
98 129 DeviceProfileInfo foundDeviceProfileInfo = deviceProfileService.findDeviceProfileInfoById(tenantId, savedDeviceProfile.getId());
99 130 Assert.assertNotNull(foundDeviceProfileInfo);
... ... @@ -124,7 +155,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
124 155 ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100));
125 156 try {
126 157 List<ListenableFuture<DeviceProfile>> futures = new ArrayList<>();
127   - for (int i = 0; i < 50; i ++) {
  158 + for (int i = 0; i < 50; i++) {
128 159 futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1")));
129 160 futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2")));
130 161 }
... ... @@ -138,8 +169,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
138 169
139 170 @Test
140 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 175 DeviceProfile savedDeviceProfile1 = deviceProfileService.saveDeviceProfile(deviceProfile1);
145 176 DeviceProfile savedDeviceProfile2 = deviceProfileService.saveDeviceProfile(deviceProfile2);
... ... @@ -165,16 +196,16 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
165 196
166 197 @Test(expected = DataValidationException.class)
167 198 public void testSaveDeviceProfileWithSameName() {
168   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  199 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
169 200 deviceProfileService.saveDeviceProfile(deviceProfile);
170   - DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile");
  201 + DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile");
171 202 deviceProfileService.saveDeviceProfile(deviceProfile2);
172 203 }
173 204
174 205 @Ignore
175 206 @Test(expected = DataValidationException.class)
176 207 public void testChangeDeviceProfileTypeWithExistingDevices() {
177   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  208 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
178 209 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
179 210 Device device = new Device();
180 211 device.setTenantId(tenantId);
... ... @@ -189,7 +220,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
189 220
190 221 @Test(expected = DataValidationException.class)
191 222 public void testChangeDeviceProfileTransportTypeWithExistingDevices() {
192   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  223 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
193 224 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
194 225 Device device = new Device();
195 226 device.setTenantId(tenantId);
... ... @@ -203,7 +234,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
203 234
204 235 @Test(expected = DataValidationException.class)
205 236 public void testDeleteDeviceProfileWithExistingDevice() {
206   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  237 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
207 238 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
208 239 Device device = new Device();
209 240 device.setTenantId(tenantId);
... ... @@ -216,7 +247,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
216 247
217 248 @Test
218 249 public void testDeleteDeviceProfile() {
219   - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
  250 + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
220 251 DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
221 252 deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
222 253 DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
... ... @@ -233,8 +264,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
233 264 Assert.assertEquals(1, pageData.getTotalElements());
234 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 269 deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile));
239 270 }
240 271
... ... @@ -275,8 +306,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
275 306 Assert.assertEquals(1, deviceProfilePageData.getTotalElements());
276 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 311 deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile));
281 312 }
282 313
... ... @@ -297,7 +328,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
297 328
298 329 List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream()
299 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 333 Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);
303 334
... ... @@ -312,4 +343,5 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
312 343 Assert.assertFalse(pageData.hasNext());
313 344 Assert.assertEquals(1, pageData.getTotalElements());
314 345 }
  346 +
315 347 }
... ...
... ... @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
30 30 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
31 31 import org.thingsboard.server.dao.exception.DataValidationException;
32 32
  33 +import java.nio.ByteBuffer;
33 34 import java.util.ArrayList;
34 35 import java.util.Collections;
35 36 import java.util.List;
... ... @@ -88,6 +89,49 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
88 89
89 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 136 @Test(expected = DataValidationException.class)
93 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 36 caffeine.specs.deviceProfiles.timeToLiveInMinutes=1440
37 37 caffeine.specs.deviceProfiles.maxSize=100000
38 38
  39 +caffeine.specs.firmwares.timeToLiveInMinutes=1440
  40 +caffeine.specs.firmwares.maxSize=100000
  41 +
39 42 redis.connection.host=localhost
40 43 redis.connection.port=6379
41 44 redis.connection.db=0
... ...
... ... @@ -29,4 +29,5 @@ DROP TABLE IF EXISTS oauth2_client_registration_info;
29 29 DROP TABLE IF EXISTS oauth2_client_registration_template;
30 30 DROP TABLE IF EXISTS api_usage_state;
31 31 DROP TABLE IF EXISTS resource;
  32 +DROP TABLE IF EXISTS firmware;
32 33 DROP FUNCTION IF EXISTS to_uuid;
... ...
... ... @@ -30,3 +30,4 @@ DROP TABLE IF EXISTS oauth2_client_registration_info;
30 30 DROP TABLE IF EXISTS oauth2_client_registration_template;
31 31 DROP TABLE IF EXISTS api_usage_state;
32 32 DROP TABLE IF EXISTS resource;
  33 +DROP TABLE IF EXISTS firmware;
... ...
... ... @@ -28,4 +28,6 @@ DROP TABLE IF EXISTS tb_schema_settings;
28 28 DROP TABLE IF EXISTS oauth2_client_registration;
29 29 DROP TABLE IF EXISTS oauth2_client_registration_info;
30 30 DROP TABLE IF EXISTS oauth2_client_registration_template;
31   -DROP TABLE IF EXISTS api_usage_state;
\ No newline at end of file
  31 +DROP TABLE IF EXISTS api_usage_state;
  32 +DROP TABLE IF EXISTS resource;
  33 +DROP TABLE IF EXISTS firmware;
\ No newline at end of file
... ...
... ... @@ -74,6 +74,7 @@ import {
74 74 StringOperation
75 75 } from '@shared/models/query/query.models';
76 76 import { alarmFields } from '@shared/models/alarm.models';
  77 +import { FirmwareService } from '@core/http/firmware.service';
77 78
78 79 @Injectable({
79 80 providedIn: 'root'
... ... @@ -93,6 +94,7 @@ export class EntityService {
93 94 private dashboardService: DashboardService,
94 95 private entityRelationService: EntityRelationService,
95 96 private attributeService: AttributeService,
  97 + private firmwareService: FirmwareService,
96 98 private utils: UtilsService
97 99 ) { }
98 100
... ... @@ -128,6 +130,9 @@ export class EntityService {
128 130 case EntityType.ALARM:
129 131 console.error('Get Alarm Entity is not implemented!');
130 132 break;
  133 + case EntityType.FIRMWARE:
  134 + observable = this.firmwareService.getFirmwareInfo(entityId, config);
  135 + break;
131 136 }
132 137 return observable;
133 138 }
... ... @@ -326,6 +331,10 @@ export class EntityService {
326 331 case EntityType.ALARM:
327 332 console.error('Get Alarm Entities is not implemented!');
328 333 break;
  334 + case EntityType.FIRMWARE:
  335 + pageLink.sortOrder.property = 'title';
  336 + entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config);
  337 + break;
329 338 }
330 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 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 18 import { HttpClient } from '@angular/common/http';
19 19 import { PageLink } from '@shared/models/page/page-link';
20 20 import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
21   -import { Observable } from 'rxjs';
  21 +import { forkJoin, Observable, of } from 'rxjs';
22 22 import { PageData } from '@shared/models/page/page-data';
23 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 26 @Injectable({
27 27 providedIn: 'root'
... ... @@ -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 92 public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> {
74 93 return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config));
75 94 }
... ...
... ... @@ -281,6 +281,13 @@ export class MenuService {
281 281 },
282 282 {
283 283 id: guid(),
  284 + name: 'firmware.firmware',
  285 + type: 'link',
  286 + path: '/firmwares',
  287 + icon: 'memory'
  288 + },
  289 + {
  290 + id: guid(),
284 291 name: 'entity-view.entity-views',
285 292 type: 'link',
286 293 path: '/entityViews',
... ... @@ -379,6 +386,11 @@ export class MenuService {
379 386 icon: 'mdi:alpha-d-box',
380 387 isMdiIcon: true,
381 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 407 }
408 408
409 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 411 return obj;
412 412 }
413 413 return Object.keys(obj).reduce((acc, curr) => {
... ...