Commit c350fb7accc7c57f39d37a7683cf4c1c3e170e93

Authored by Andrew Shvayka
Committed by GitHub
2 parents 18f27b18 820fc4a9

Merge pull request #4465 from thingsboard/feature/firmware

Feature/firmware
Showing 93 changed files with 3134 additions and 145 deletions

Too many changes to show.

To preserve performance only 93 of 138 files are displayed.

... ... @@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS edge (
31 31 tenant_id uuid,
32 32 CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name),
33 33 CONSTRAINT edge_routing_key_unq_key UNIQUE (routing_key)
34   -);
  34 + );
35 35
36 36 CREATE TABLE IF NOT EXISTS edge_event (
37 37 id uuid NOT NULL CONSTRAINT edge_event_pkey PRIMARY KEY,
... ... @@ -44,4 +44,57 @@ CREATE TABLE IF NOT EXISTS edge_event (
44 44 body varchar(10000000),
45 45 tenant_id uuid,
46 46 ts bigint NOT NULL
  47 + );
  48 +
  49 +CREATE TABLE IF NOT EXISTS resource (
  50 + id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY,
  51 + created_time bigint NOT NULL,
  52 + tenant_id uuid NOT NULL,
  53 + title varchar(255) NOT NULL,
  54 + resource_type varchar(32) NOT NULL,
  55 + resource_key varchar(255) NOT NULL,
  56 + search_text varchar(255),
  57 + file_name varchar(255) NOT NULL,
  58 + data varchar,
  59 + CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
  60 +);
  61 +
  62 +CREATE TABLE IF NOT EXISTS firmware (
  63 + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
  64 + created_time bigint NOT NULL,
  65 + tenant_id uuid NOT NULL,
  66 + title varchar(255) NOT NULL,
  67 + version varchar(255) NOT NULL,
  68 + file_name varchar(255),
  69 + content_type varchar(255),
  70 + checksum_algorithm varchar(32),
  71 + checksum varchar(1020),
  72 + data bytea,
  73 + data_size bigint,
  74 + additional_info varchar,
  75 + search_text varchar(255),
  76 + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
47 77 );
  78 +
  79 +ALTER TABLE device_profile
  80 + ADD COLUMN IF NOT EXISTS firmware_id uuid;
  81 +
  82 +ALTER TABLE device
  83 + ADD COLUMN IF NOT EXISTS firmware_id uuid;
  84 +
  85 +DO $$
  86 + BEGIN
  87 + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
  88 + ALTER TABLE device_profile
  89 + ADD CONSTRAINT fk_firmware_device_profile
  90 + FOREIGN KEY (firmware_id) REFERENCES firmware(id);
  91 + END IF;
  92 +
  93 + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
  94 + ALTER TABLE device
  95 + ADD CONSTRAINT fk_firmware_device
  96 + FOREIGN KEY (firmware_id) REFERENCES firmware(id);
  97 + END IF;
  98 + END;
  99 +$$;
  100 +
... ...
... ... @@ -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;
... ... @@ -38,6 +39,8 @@ import org.thingsboard.server.common.data.EdgeUtils;
38 39 import org.thingsboard.server.common.data.EntityType;
39 40 import org.thingsboard.server.common.data.EntityView;
40 41 import org.thingsboard.server.common.data.EntityViewInfo;
  42 +import org.thingsboard.server.common.data.Firmware;
  43 +import org.thingsboard.server.common.data.FirmwareInfo;
41 44 import org.thingsboard.server.common.data.HasName;
42 45 import org.thingsboard.server.common.data.HasTenantId;
43 46 import org.thingsboard.server.common.data.TbResourceInfo;
... ... @@ -67,6 +70,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
67 70 import org.thingsboard.server.common.data.id.EntityId;
68 71 import org.thingsboard.server.common.data.id.EntityIdFactory;
69 72 import org.thingsboard.server.common.data.id.EntityViewId;
  73 +import org.thingsboard.server.common.data.id.FirmwareId;
70 74 import org.thingsboard.server.common.data.id.TbResourceId;
71 75 import org.thingsboard.server.common.data.id.RuleChainId;
72 76 import org.thingsboard.server.common.data.id.RuleNodeId;
... ... @@ -106,6 +110,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
106 110 import org.thingsboard.server.dao.entityview.EntityViewService;
107 111 import org.thingsboard.server.dao.exception.DataValidationException;
108 112 import org.thingsboard.server.dao.exception.IncorrectParameterException;
  113 +import org.thingsboard.server.dao.firmware.FirmwareService;
109 114 import org.thingsboard.server.dao.model.ModelConstants;
110 115 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
111 116 import org.thingsboard.server.dao.oauth2.OAuth2Service;
... ... @@ -123,6 +128,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
123 128 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
124 129 import org.thingsboard.server.queue.util.TbCoreComponent;
125 130 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  131 +import org.thingsboard.server.service.firmware.FirmwareStateService;
126 132 import org.thingsboard.server.service.edge.EdgeNotificationService;
127 133 import org.thingsboard.server.service.edge.rpc.EdgeGrpcService;
128 134 import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
... ... @@ -246,6 +252,12 @@ public abstract class BaseController {
246 252 protected TbResourceService resourceService;
247 253
248 254 @Autowired
  255 + protected FirmwareService firmwareService;
  256 +
  257 + @Autowired
  258 + protected FirmwareStateService firmwareStateService;
  259 +
  260 + @Autowired
249 261 protected TbQueueProducerProvider producerProvider;
250 262
251 263 @Autowired
... ... @@ -501,6 +513,9 @@ public abstract class BaseController {
501 513 case TB_RESOURCE:
502 514 checkResourceId(new TbResourceId(entityId.getId()), operation);
503 515 return;
  516 + case FIRMWARE:
  517 + checkFirmwareId(new FirmwareId(entityId.getId()), operation);
  518 + return;
504 519 default:
505 520 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
506 521 }
... ... @@ -756,6 +771,30 @@ public abstract class BaseController {
756 771 }
757 772 }
758 773
  774 + Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException {
  775 + try {
  776 + validateId(firmwareId, "Incorrect firmwareId " + firmwareId);
  777 + Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId);
  778 + checkNotNull(firmware);
  779 + accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware);
  780 + return firmware;
  781 + } catch (Exception e) {
  782 + throw handleException(e, false);
  783 + }
  784 + }
  785 +
  786 + FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException {
  787 + try {
  788 + validateId(firmwareId, "Incorrect firmwareId " + firmwareId);
  789 + FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId);
  790 + checkNotNull(firmwareInfo);
  791 + accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo);
  792 + return firmwareInfo;
  793 + } catch (Exception e) {
  794 + throw handleException(e, false);
  795 + }
  796 + }
  797 +
759 798 @SuppressWarnings("unchecked")
760 799 protected <I extends EntityId> I emptyId(EntityType entityType) {
761 800 return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
... ... @@ -1082,4 +1121,11 @@ public abstract class BaseController {
1082 1121 }
1083 1122 }
1084 1123
  1124 + protected MediaType parseMediaType(String contentType) {
  1125 + try {
  1126 + return MediaType.parseMediaType(contentType);
  1127 + } catch (Exception e) {
  1128 + return MediaType.APPLICATION_OCTET_STREAM;
  1129 + }
  1130 + }
1085 1131 }
... ...
... ... @@ -75,6 +75,7 @@ import javax.annotation.Nullable;
75 75 import java.io.IOException;
76 76 import java.util.ArrayList;
77 77 import java.util.List;
  78 +import java.util.Objects;
78 79 import java.util.stream.Collectors;
79 80
80 81 import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
... ... @@ -124,15 +125,22 @@ public class DeviceController extends BaseController {
124 125
125 126 checkEntity(device.getId(), device, Resource.DEVICE);
126 127
  128 + boolean created = device.getId() == null;
  129 + Device oldDevice;
  130 + if (!created) {
  131 + oldDevice = deviceService.findDeviceById(getTenantId(), device.getId());
  132 + } else {
  133 + oldDevice = null;
  134 + }
  135 +
127 136 Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
128 137
129 138 tbClusterService.onDeviceChange(savedDevice, null);
130 139 tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
131 140 savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
132   - tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),
133   - device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  141 + tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
134 142
135   - if (device.getId() != null) {
  143 + if (!created) {
136 144 sendEntityNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), EdgeEventActionType.UPDATED);
137 145 }
138 146
... ... @@ -145,12 +153,17 @@ public class DeviceController extends BaseController {
145 153 } else {
146 154 deviceStateService.onDeviceUpdated(savedDevice);
147 155 }
  156 +
  157 + firmwareStateService.update(savedDevice, oldDevice);
  158 +
148 159 return savedDevice;
149   - } catch (Exception e) {
  160 + } catch (
  161 + Exception e) {
150 162 logEntityAction(emptyId(EntityType.DEVICE), device,
151 163 null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
152 164 throw handleException(e);
153 165 }
  166 +
154 167 }
155 168
156 169 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
... ...
... ... @@ -44,6 +44,7 @@ import org.thingsboard.server.service.security.permission.Operation;
44 44 import org.thingsboard.server.service.security.permission.Resource;
45 45
46 46 import java.util.List;
  47 +import java.util.Objects;
47 48 import java.util.UUID;
48 49
49 50 @RestController
... ... @@ -144,6 +145,15 @@ public class DeviceProfileController extends BaseController {
144 145
145 146 checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE);
146 147
  148 + boolean isFirmwareChanged = false;
  149 +
  150 + if (!created) {
  151 + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId());
  152 + if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) {
  153 + isFirmwareChanged = true;
  154 + }
  155 + }
  156 +
147 157 DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
148 158
149 159 tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
... ... @@ -154,9 +164,11 @@ public class DeviceProfileController extends BaseController {
154 164 null,
155 165 created ? ActionType.ADDED : ActionType.UPDATED, null);
156 166
  167 + if (isFirmwareChanged) {
  168 + firmwareStateService.update(savedDeviceProfile);
  169 + }
157 170 sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),
158 171 deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
159   -
160 172 return savedDeviceProfile;
161 173 } catch (Exception e) {
162 174 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 + byte[] data = file.getBytes();
  133 + if (StringUtils.isEmpty(checksumAlgorithm)) {
  134 + checksumAlgorithm = "sha256";
  135 + checksum = Hashing.sha256().hashBytes(data).toString();
  136 + }
  137 +
  138 + firmware.setChecksumAlgorithm(checksumAlgorithm);
  139 + firmware.setChecksum(checksum);
  140 + firmware.setFileName(file.getOriginalFilename());
  141 + firmware.setContentType(file.getContentType());
  142 + firmware.setData(ByteBuffer.wrap(data));
  143 + firmware.setDataSize((long) data.length);
  144 + return firmwareService.saveFirmware(firmware);
  145 + } catch (Exception e) {
  146 + throw handleException(e);
  147 + }
  148 + }
  149 +
  150 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  151 + @RequestMapping(value = "/firmwares", method = RequestMethod.GET)
  152 + @ResponseBody
  153 + public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,
  154 + @RequestParam int page,
  155 + @RequestParam(required = false) String textSearch,
  156 + @RequestParam(required = false) String sortProperty,
  157 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  158 + try {
  159 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  160 + return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink));
  161 + } catch (Exception e) {
  162 + throw handleException(e);
  163 + }
  164 + }
  165 +
  166 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  167 + @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET)
  168 + @ResponseBody
  169 + public PageData<FirmwareInfo> getFirmwares(@PathVariable("hasData") boolean hasData,
  170 + @RequestParam int pageSize,
  171 + @RequestParam int page,
  172 + @RequestParam(required = false) String textSearch,
  173 + @RequestParam(required = false) String sortProperty,
  174 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  175 + try {
  176 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  177 + return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndHasData(getTenantId(), hasData, pageLink));
  178 + } catch (Exception e) {
  179 + throw handleException(e);
  180 + }
  181 + }
  182 +
  183 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  184 + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE)
  185 + @ResponseBody
  186 + public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {
  187 + checkParameter(FIRMWARE_ID, strFirmwareId);
  188 + try {
  189 + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
  190 + checkFirmwareInfoId(firmwareId, Operation.DELETE);
  191 + firmwareService.deleteFirmware(getTenantId(), firmwareId);
  192 + } catch (Exception e) {
  193 + throw handleException(e);
  194 + }
  195 + }
  196 +
  197 +}
... ...
... ... @@ -166,7 +166,7 @@ public class TbResourceController extends BaseController {
166 166 @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE)
167 167 @ResponseBody
168 168 public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException {
169   - checkParameter("resourceId", strResourceId);
  169 + checkParameter(RESOURCE_ID, strResourceId);
170 170 try {
171 171 TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
172 172 TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);
... ...
... ... @@ -232,7 +232,6 @@ public class ThingsboardInstallService {
232 232 systemDataLoaderService.createAdminSettings();
233 233 systemDataLoaderService.loadSystemWidgets();
234 234 systemDataLoaderService.createOAuth2Templates();
235   - systemDataLoaderService.loadSystemLwm2mResources();
236 235 // systemDataLoaderService.loadSystemPlugins();
237 236 // systemDataLoaderService.loadSystemRules();
238 237
... ...
  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.FirmwareInfo;
  27 +import org.thingsboard.server.common.data.id.DeviceId;
  28 +import org.thingsboard.server.common.data.id.FirmwareId;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  31 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  32 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  33 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  34 +import org.thingsboard.server.common.data.kv.StringDataEntry;
  35 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  36 +import org.thingsboard.server.common.data.page.PageData;
  37 +import org.thingsboard.server.common.data.page.PageLink;
  38 +import org.thingsboard.server.common.msg.queue.TbCallback;
  39 +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
  40 +import org.thingsboard.server.dao.device.DeviceProfileService;
  41 +import org.thingsboard.server.dao.device.DeviceService;
  42 +import org.thingsboard.server.dao.firmware.FirmwareService;
  43 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
  44 +import org.thingsboard.server.queue.TbQueueProducer;
  45 +import org.thingsboard.server.queue.common.TbProtoQueueMsg;
  46 +import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
  47 +import org.thingsboard.server.queue.util.TbCoreComponent;
  48 +
  49 +import javax.annotation.Nullable;
  50 +import java.util.ArrayList;
  51 +import java.util.Arrays;
  52 +import java.util.Collections;
  53 +import java.util.List;
  54 +import java.util.UUID;
  55 +import java.util.function.Consumer;
  56 +
  57 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM;
  58 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM_ALGORITHM;
  59 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_SIZE;
  60 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_TITLE;
  61 +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_VERSION;
  62 +
  63 +@Slf4j
  64 +@Service
  65 +@TbCoreComponent
  66 +public class DefaultFirmwareStateService implements FirmwareStateService {
  67 +
  68 + private final FirmwareService firmwareService;
  69 + private final DeviceService deviceService;
  70 + private final DeviceProfileService deviceProfileService;
  71 + private final RuleEngineTelemetryService telemetryService;
  72 + private final TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> fwStateMsgProducer;
  73 +
  74 + public DefaultFirmwareStateService(FirmwareService firmwareService,
  75 + DeviceService deviceService,
  76 + DeviceProfileService deviceProfileService,
  77 + RuleEngineTelemetryService telemetryService,
  78 + TbCoreQueueFactory coreQueueFactory) {
  79 + this.firmwareService = firmwareService;
  80 + this.deviceService = deviceService;
  81 + this.deviceProfileService = deviceProfileService;
  82 + this.telemetryService = telemetryService;
  83 + this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer();
  84 + }
  85 +
  86 + @Override
  87 + public void update(Device device, Device oldDevice) {
  88 + FirmwareId newFirmwareId = device.getFirmwareId();
  89 + if (newFirmwareId == null) {
  90 + DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
  91 + newFirmwareId = newDeviceProfile.getFirmwareId();
  92 + }
  93 + if (oldDevice != null) {
  94 + if (newFirmwareId != null) {
  95 + FirmwareId oldFirmwareId = oldDevice.getFirmwareId();
  96 + if (oldFirmwareId == null) {
  97 + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
  98 + oldFirmwareId = oldDeviceProfile.getFirmwareId();
  99 + }
  100 + if (!newFirmwareId.equals(oldFirmwareId)) {
  101 + // Device was updated and new firmware is different from previous firmware.
  102 + send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis());
  103 + }
  104 + } else {
  105 + // Device was updated and new firmware is not set.
  106 + remove(device);
  107 + }
  108 + } else if (newFirmwareId != null) {
  109 + // Device was created and firmware is defined.
  110 + send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis());
  111 + }
  112 + }
  113 +
  114 + @Override
  115 + public void update(DeviceProfile deviceProfile) {
  116 + TenantId tenantId = deviceProfile.getTenantId();
  117 +
  118 + Consumer<Device> updateConsumer;
  119 + if (deviceProfile.getFirmwareId() != null) {
  120 + long ts = System.currentTimeMillis();
  121 + updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts);
  122 + } else {
  123 + updateConsumer = this::remove;
  124 + }
  125 +
  126 + PageLink pageLink = new PageLink(100);
  127 + PageData<Device> pageData;
  128 + do {
  129 + pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pageLink);
  130 +
  131 + pageData.getData().forEach(updateConsumer);
  132 +
  133 + if (pageData.hasNext()) {
  134 + pageLink = pageLink.nextPageLink();
  135 + }
  136 + } while (pageData.hasNext());
  137 + }
  138 +
  139 + @Override
  140 + public boolean process(ToFirmwareStateServiceMsg msg) {
  141 + boolean isSuccess = false;
  142 + FirmwareId targetFirmwareId = new FirmwareId(new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB()));
  143 + DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
  144 + TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
  145 + long ts = msg.getTs();
  146 +
  147 + Device device = deviceService.findDeviceById(tenantId, deviceId);
  148 + if (device == null) {
  149 + log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId);
  150 + } else {
  151 + FirmwareId currentFirmwareId = device.getFirmwareId();
  152 +
  153 + if (currentFirmwareId == null) {
  154 + currentFirmwareId = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()).getFirmwareId();
  155 + }
  156 +
  157 + if (targetFirmwareId.equals(currentFirmwareId)) {
  158 + update(device, firmwareService.findFirmwareById(device.getTenantId(), targetFirmwareId), ts);
  159 + isSuccess = true;
  160 + } else {
  161 + log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId);
  162 + }
  163 + }
  164 + return isSuccess;
  165 + }
  166 +
  167 + private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts) {
  168 + ToFirmwareStateServiceMsg msg = ToFirmwareStateServiceMsg.newBuilder()
  169 + .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
  170 + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
  171 + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
  172 + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
  173 + .setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits())
  174 + .setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits())
  175 + .setTs(ts)
  176 + .build();
  177 +
  178 + FirmwareInfo firmware = firmwareService.findFirmwareInfoById(tenantId, firmwareId);
  179 + if (firmware == null) {
  180 + log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId);
  181 + return;
  182 + }
  183 +
  184 + TopicPartitionInfo tpi = new TopicPartitionInfo(fwStateMsgProducer.getDefaultTopic(), null, null, false);
  185 + fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null);
  186 +
  187 + List<TsKvEntry> telemetry = new ArrayList<>();
  188 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle())));
  189 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion())));
  190 + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name())));
  191 +
  192 + telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
  193 + @Override
  194 + public void onSuccess(@Nullable Void tmp) {
  195 + log.trace("[{}] Success save firmware status!", deviceId);
  196 + }
  197 +
  198 + @Override
  199 + public void onFailure(Throwable t) {
  200 + log.error("[{}] Failed to save firmware status!", deviceId, t);
  201 + }
  202 + });
  203 + }
  204 +
  205 +
  206 + private void update(Device device, FirmwareInfo firmware, long ts) {
  207 + TenantId tenantId = device.getTenantId();
  208 + DeviceId deviceId = device.getId();
  209 +
  210 + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.INITIATED.name()));
  211 +
  212 + telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
  213 + @Override
  214 + public void onSuccess(@Nullable Void tmp) {
  215 + log.trace("[{}] Success save telemetry with target firmware for device!", deviceId);
  216 + }
  217 +
  218 + @Override
  219 + public void onFailure(Throwable t) {
  220 + log.error("[{}] Failed to save telemetry with target firmware for device!", deviceId, t);
  221 + }
  222 + });
  223 +
  224 + List<AttributeKvEntry> attributes = new ArrayList<>();
  225 +
  226 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle())));
  227 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion())));
  228 +
  229 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, firmware.getDataSize())));
  230 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm())));
  231 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum())));
  232 + telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
  233 + @Override
  234 + public void onSuccess(@Nullable Void tmp) {
  235 + log.trace("[{}] Success save attributes with target firmware!", deviceId);
  236 + }
  237 +
  238 + @Override
  239 + public void onFailure(Throwable t) {
  240 + log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
  241 + }
  242 + });
  243 + }
  244 +
  245 + private void remove(Device device) {
  246 + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE,
  247 + Arrays.asList(FIRMWARE_TITLE, FIRMWARE_VERSION, FIRMWARE_SIZE, FIRMWARE_CHECKSUM_ALGORITHM, FIRMWARE_CHECKSUM),
  248 + new FutureCallback<>() {
  249 + @Override
  250 + public void onSuccess(@Nullable Void tmp) {
  251 + log.trace("[{}] Success remove target firmware attributes!", device.getId());
  252 + }
  253 +
  254 + @Override
  255 + public void onFailure(Throwable t) {
  256 + log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t);
  257 + }
  258 + });
  259 + }
  260 +}
... ...
  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 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
  21 +
  22 +public interface FirmwareStateService {
  23 +
  24 + void update(Device device, Device oldDevice);
  25 +
  26 + void update(DeviceProfile deviceProfile);
  27 +
  28 + boolean process(ToFirmwareStateServiceMsg msg);
  29 +
  30 +}
... ...
  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 +public enum FirmwareUpdateStatus {
  19 + QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
  20 +}
... ...
... ... @@ -444,11 +444,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
444 444 installScripts.loadSystemWidgets();
445 445 }
446 446
447   - @Override
448   - public void loadSystemLwm2mResources() throws Exception {
449   - installScripts.loadSystemLwm2mResources();
450   - }
451   -
452 447 private User createUser(Authority authority,
453 448 TenantId tenantId,
454 449 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.ResourceService;
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;
... ... @@ -210,29 +206,6 @@ public class InstallScripts {
210 206 }
211 207 }
212 208
213   - public void loadSystemLwm2mResources() throws Exception {
214   - Path modelsDir = Paths.get(getDataDir(), MODELS_DIR);
215   - if (Files.isDirectory(modelsDir)) {
216   - try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) {
217   - dirStream.forEach(
218   - path -> {
219   - try {
220   - byte[] fileBytes = Files.readAllBytes(path);
221   - TbResource resource = new TbResource();
222   - resource.setFileName(path.getFileName().toString());
223   - resource.setTenantId(TenantId.SYS_TENANT_ID);
224   - resource.setResourceType(ResourceType.LWM2M_MODEL);
225   - resource.setData(Base64.getEncoder().encodeToString(fileBytes));
226   - resourceService.saveResource(resource);
227   - } catch (Exception e) {
228   - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", path.toString()));
229   - }
230   - }
231   - );
232   - }
233   - }
234   - }
235   -
236 209 public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
237 210 Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
238 211 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
... ...
... ... @@ -450,37 +450,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
450 450 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
451 451 log.info("Updating schema ...");
452 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   -
469   - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL);
470   - loadSql(schemaUpdateFile, conn);
471   - try {
472   - conn.createStatement().execute("ALTER TABLE rule_chain ADD COLUMN type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
473   - } catch (Exception ignored) {}
474   -
475   - log.info("Load Edge TTL functions ...");
476   - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", "schema_update_ttl.sql");
477   - loadSql(schemaUpdateFile, conn);
478   - log.info("Edge TTL functions successfully loaded!");
479   -
480   - } catch (Exception e) {
481   - log.error("Failed updating schema!!!", e);
  453 + conn.createStatement().execute("ALTER TABLE rule_chain ADD COLUMN type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  454 + } catch (Exception ignored) {
482 455 }
  456 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL);
  457 + loadSql(schemaUpdateFile, conn);
  458 + log.info("Load Edge TTL functions ...");
  459 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", "schema_update_ttl.sql");
  460 + loadSql(schemaUpdateFile, conn);
  461 + log.info("Edge TTL functions successfully loaded!");
  462 + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
483 463 log.info("Schema updated.");
  464 + } catch (Exception e) {
  465 + log.error("Failed updating schema!!!", e);
484 466 }
485 467 break;
486 468 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 }
... ...
... ... @@ -24,6 +24,7 @@ import org.springframework.context.event.EventListener;
24 24 import org.springframework.core.annotation.Order;
25 25 import org.springframework.scheduling.annotation.Scheduled;
26 26 import org.springframework.stereotype.Service;
  27 +import org.thingsboard.common.util.JacksonUtil;
27 28 import org.thingsboard.common.util.ThingsBoardThreadFactory;
28 29 import org.thingsboard.rule.engine.api.RpcError;
29 30 import org.thingsboard.server.actors.ActorSystemContext;
... ... @@ -35,7 +36,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
35 36 import org.thingsboard.server.common.msg.queue.TbCallback;
36 37 import org.thingsboard.server.common.stats.StatsFactory;
37 38 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
38   -import org.thingsboard.common.util.JacksonUtil;
  39 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
39 40 import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
40 41 import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
41 42 import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
... ... @@ -49,6 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP
49 50 import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
50 51 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
51 52 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  53 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
52 54 import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
53 55 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
54 56 import org.thingsboard.server.queue.TbQueueConsumer;
... ... @@ -58,8 +60,8 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
58 60 import org.thingsboard.server.queue.util.TbCoreComponent;
59 61 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
60 62 import org.thingsboard.server.service.edge.EdgeNotificationService;
  63 +import org.thingsboard.server.service.firmware.FirmwareStateService;
61 64 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
62   -import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
63 65 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
64 66 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
65 67 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
... ... @@ -97,6 +99,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
97 99 @Value("${queue.core.stats.enabled:false}")
98 100 private boolean statsEnabled;
99 101
  102 + @Value("${queue.core.firmware.pack-interval-ms:60000}")
  103 + private long firmwarePackInterval;
  104 + @Value("${queue.core.firmware.pack-size:100}")
  105 + private int firmwarePackSize;
  106 +
100 107 private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer;
101 108 private final DeviceStateService stateService;
102 109 private final TbApiUsageStateService statsService;
... ... @@ -104,11 +111,15 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
104 111 private final SubscriptionManagerService subscriptionManagerService;
105 112 private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
106 113 private final EdgeNotificationService edgeNotificationService;
  114 + private final FirmwareStateService firmwareStateService;
107 115 private final TbCoreConsumerStats stats;
108 116 protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
  117 + private final TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> firmwareStatesConsumer;
109 118
110 119 protected volatile ExecutorService usageStatsExecutor;
111 120
  121 + private volatile ExecutorService firmwareStatesExecutor;
  122 +
112 123 public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory,
113 124 ActorSystemContext actorContext,
114 125 DeviceStateService stateService,
... ... @@ -121,10 +132,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
121 132 TbApiUsageStateService statsService,
122 133 TbTenantProfileCache tenantProfileCache,
123 134 TbApiUsageStateService apiUsageStateService,
124   - EdgeNotificationService edgeNotificationService) {
  135 + EdgeNotificationService edgeNotificationService,
  136 + FirmwareStateService firmwareStateService) {
125 137 super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, apiUsageStateService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
126 138 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
127 139 this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
  140 + this.firmwareStatesConsumer = tbCoreQueueFactory.createToFirmwareStateServiceMsgConsumer();
128 141 this.stateService = stateService;
129 142 this.localSubscriptionService = localSubscriptionService;
130 143 this.subscriptionManagerService = subscriptionManagerService;
... ... @@ -132,12 +145,14 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
132 145 this.edgeNotificationService = edgeNotificationService;
133 146 this.stats = new TbCoreConsumerStats(statsFactory);
134 147 this.statsService = statsService;
  148 + this.firmwareStateService = firmwareStateService;
135 149 }
136 150
137 151 @PostConstruct
138 152 public void init() {
139 153 super.init("tb-core-consumer", "tb-core-notifications-consumer");
140   - this.usageStatsExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("tb-core-usage-stats-consumer"));
  154 + this.usageStatsExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-core-usage-stats-consumer"));
  155 + this.firmwareStatesExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-core-firmware-notifications-consumer"));
141 156 }
142 157
143 158 @PreDestroy
... ... @@ -146,6 +161,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
146 161 if (usageStatsExecutor != null) {
147 162 usageStatsExecutor.shutdownNow();
148 163 }
  164 + if (firmwareStatesExecutor != null) {
  165 + firmwareStatesExecutor.shutdownNow();
  166 + }
149 167 }
150 168
151 169 @EventListener(ApplicationReadyEvent.class)
... ... @@ -153,6 +171,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
153 171 public void onApplicationEvent(ApplicationReadyEvent event) {
154 172 super.onApplicationEvent(event);
155 173 launchUsageStatsConsumer();
  174 + launchFirmwareUpdateNotificationConsumer();
156 175 }
157 176
158 177 @Override
... ... @@ -167,6 +186,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
167 186 .map(tpi -> tpi.newByTopic(usageStatsConsumer.getTopic()))
168 187 .collect(Collectors.toSet()));
169 188 }
  189 + this.firmwareStatesConsumer.subscribe();
170 190 }
171 191
172 192 @Override
... ... @@ -336,10 +356,59 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
336 356 });
337 357 }
338 358
  359 + private void launchFirmwareUpdateNotificationConsumer() {
  360 + long maxProcessingTimeoutPerRecord = firmwarePackInterval / firmwarePackSize;
  361 + firmwareStatesExecutor.submit(() -> {
  362 + while (!stopped) {
  363 + try {
  364 + List<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration());
  365 + if (msgs.isEmpty()) {
  366 + continue;
  367 + }
  368 + long timeToSleep = maxProcessingTimeoutPerRecord;
  369 + for (TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg : msgs) {
  370 + try {
  371 + long startTime = System.currentTimeMillis();
  372 + boolean isSuccessUpdate = handleFirmwareUpdates(msg);
  373 + long endTime = System.currentTimeMillis();
  374 + long spentTime = endTime - startTime;
  375 + timeToSleep = timeToSleep - spentTime;
  376 + if (isSuccessUpdate) {
  377 + if (timeToSleep > 0) {
  378 + log.debug("Spent time per record is: [{}]!", spentTime);
  379 + Thread.sleep(timeToSleep);
  380 + timeToSleep = 0;
  381 + }
  382 + timeToSleep += maxProcessingTimeoutPerRecord;
  383 + }
  384 + } catch (Throwable e) {
  385 + log.warn("Failed to process firmware update msg: {}", msg, e);
  386 + }
  387 + }
  388 + firmwareStatesConsumer.commit();
  389 + } catch (Exception e) {
  390 + if (!stopped) {
  391 + log.warn("Failed to obtain usage stats from queue.", e);
  392 + try {
  393 + Thread.sleep(getNotificationPollDuration());
  394 + } catch (InterruptedException e2) {
  395 + log.trace("Failed to wait until the server has capacity to handle new firmware updates", e2);
  396 + }
  397 + }
  398 + }
  399 + }
  400 + log.info("TB Firmware States Consumer stopped.");
  401 + });
  402 + }
  403 +
339 404 private void handleUsageStats(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
340 405 statsService.process(msg, callback);
341 406 }
342 407
  408 + private boolean handleFirmwareUpdates(TbProtoQueueMsg<ToFirmwareStateServiceMsg> msg) {
  409 + return firmwareStateService.process(msg.getValue());
  410 + }
  411 +
343 412 private void forwardToCoreRpcService(FromDeviceRPCResponseProto proto, TbCallback callback) {
344 413 RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null;
345 414 FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB())
... ... @@ -448,6 +517,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
448 517 if (usageStatsConsumer != null) {
449 518 usageStatsConsumer.unsubscribe();
450 519 }
  520 + if (firmwareStatesConsumer != null) {
  521 + firmwareStatesConsumer.unsubscribe();
  522 + }
451 523 }
452 524
453 525 }
... ...
... ... @@ -31,7 +31,7 @@ public interface TbResourceService {
31 31
32 32 TbResource saveResource(TbResource resource) throws ThingsboardException;
33 33
34   - TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
  34 + TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey);
35 35
36 36 TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
37 37
... ...
... ... @@ -38,6 +38,7 @@ public enum Resource {
38 38 DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
39 39 API_USAGE_STATE(EntityType.API_USAGE_STATE),
40 40 TB_RESOURCE(EntityType.TB_RESOURCE),
  41 + FIRMWARE(EntityType.FIRMWARE),
41 42 EDGE(EntityType.EDGE);
42 43
43 44 private final EntityType entityType;
... ...
... ... @@ -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 put(Resource.EDGE, tenantEntityPermissionChecker);
46 47 }
47 48
... ...
... ... @@ -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.ResourceService;
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;
... ... @@ -79,6 +83,7 @@ import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
79 83 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
80 84 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
81 85 import org.thingsboard.server.service.queue.TbClusterService;
  86 +import org.thingsboard.server.service.resource.TbResourceService;
82 87 import org.thingsboard.server.service.state.DeviceStateService;
83 88
84 89 import java.util.Optional;
... ... @@ -109,7 +114,9 @@ public class DefaultTransportApiService implements TransportApiService {
109 114 private final TbClusterService tbClusterService;
110 115 private final DataDecodingEncodingService dataDecodingEncodingService;
111 116 private final DeviceProvisionService deviceProvisionService;
112   - private final ResourceService resourceService;
  117 + private final TbResourceService resourceService;
  118 + private final FirmwareService firmwareService;
  119 + private final FirmwareCacheWriter firmwareCacheWriter;
113 120
114 121 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
115 122
... ... @@ -118,7 +125,7 @@ public class DefaultTransportApiService implements TransportApiService {
118 125 RelationService relationService, DeviceCredentialsService deviceCredentialsService,
119 126 DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
120 127 TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
121   - DeviceProvisionService deviceProvisionService, ResourceService resourceService) {
  128 + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareCacheWriter firmwareCacheWriter) {
122 129 this.deviceProfileCache = deviceProfileCache;
123 130 this.tenantProfileCache = tenantProfileCache;
124 131 this.apiUsageStateService = apiUsageStateService;
... ... @@ -131,6 +138,8 @@ public class DefaultTransportApiService implements TransportApiService {
131 138 this.dataDecodingEncodingService = dataDecodingEncodingService;
132 139 this.deviceProvisionService = deviceProvisionService;
133 140 this.resourceService = resourceService;
  141 + this.firmwareService = firmwareService;
  142 + this.firmwareCacheWriter = firmwareCacheWriter;
134 143 }
135 144
136 145 @Override
... ... @@ -167,6 +176,9 @@ public class DefaultTransportApiService implements TransportApiService {
167 176 } else if (transportApiRequestMsg.hasResourceRequestMsg()) {
168 177 return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
169 178 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  179 + } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) {
  180 + return Futures.transform(handle(transportApiRequestMsg.getFirmwareRequestMsg()),
  181 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
170 182 }
171 183 return Futures.transform(getEmptyTransportApiResponseFuture(),
172 184 value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
... ... @@ -314,14 +326,14 @@ public class DefaultTransportApiService implements TransportApiService {
314 326 } catch (ProvisionFailedException e) {
315 327 return Futures.immediateFuture(getTransportApiResponseMsg(
316 328 new DeviceCredentials(),
317   - TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage())));
  329 + TransportProtos.ResponseStatus.valueOf(e.getMessage())));
318 330 }
319   - return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS),
  331 + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ResponseStatus.SUCCESS),
320 332 dbCallbackExecutorService);
321 333 }
322 334
323   - private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) {
324   - if (!status.equals(ProvisionResponseStatus.SUCCESS)) {
  335 + private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ResponseStatus status) {
  336 + if (!status.equals(TransportProtos.ResponseStatus.SUCCESS)) {
325 337 return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build();
326 338 }
327 339 TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
... ... @@ -441,6 +453,48 @@ public class DefaultTransportApiService implements TransportApiService {
441 453 }
442 454 }
443 455
  456 + private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetFirmwareRequestMsg requestMsg) {
  457 + TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
  458 + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
  459 + Device device = deviceService.findDeviceById(tenantId, deviceId);
  460 +
  461 + if (device == null) {
  462 + return getEmptyTransportApiResponseFuture();
  463 + }
  464 +
  465 + FirmwareId firmwareId = device.getFirmwareId();
  466 +
  467 + if (firmwareId == null) {
  468 + firmwareId = deviceProfileCache.find(device.getDeviceProfileId()).getFirmwareId();
  469 + }
  470 +
  471 + TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder();
  472 +
  473 + if (firmwareId == null) {
  474 + builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
  475 + } else {
  476 + Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId);
  477 +
  478 + if (firmware == null) {
  479 + builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
  480 + } else {
  481 + builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
  482 + builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits());
  483 + builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits());
  484 + builder.setTitle(firmware.getTitle());
  485 + builder.setVersion(firmware.getVersion());
  486 + builder.setFileName(firmware.getFileName());
  487 + builder.setContentType(firmware.getContentType());
  488 + firmwareCacheWriter.put(firmwareId.toString(), firmware.getData().array());
  489 + }
  490 + }
  491 +
  492 + return Futures.immediateFuture(
  493 + TransportApiResponseMsg.newBuilder()
  494 + .setFirmwareResponseMsg(builder.build())
  495 + .build());
  496 + }
  497 +
444 498 private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) {
445 499 TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId()));
446 500 String deviceName = msg.getEndpoint();
... ...
... ... @@ -371,6 +371,9 @@ caffeine:
371 371 tokensOutdatageTime:
372 372 timeToLiveInMinutes: 20000
373 373 maxSize: 10000
  374 + firmwares:
  375 + timeToLiveInMinutes: 1440
  376 + maxSize: 100
374 377 edges:
375 378 timeToLiveInMinutes: 1440
376 379 maxSize: 0
... ... @@ -446,6 +449,9 @@ spring.resources.chain:
446 449 content:
447 450 enabled: "true"
448 451
  452 +spring.servlet.multipart.max-file-size: "50MB"
  453 +spring.servlet.multipart.max-request-size: "50MB"
  454 +
449 455 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
450 456 spring.jpa.properties.hibernate.order_by.default_null_ordering: "last"
451 457
... ... @@ -738,6 +744,10 @@ queue:
738 744 sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}"
739 745 sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}"
740 746 security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
  747 + consumer-properties-per-topic:
  748 + tb_firmware:
  749 + - key: max.poll.records
  750 + value: 10
741 751 other:
742 752 topic-properties:
743 753 rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
... ... @@ -745,6 +755,7 @@ queue:
745 755 transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
746 756 notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
747 757 js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}"
  758 + fw-updates: "${TB_QUEUE_KAFKA_FW_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
748 759 consumer-stats:
749 760 enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
750 761 print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
... ... @@ -815,6 +826,10 @@ queue:
815 826 poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
816 827 partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
817 828 pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}"
  829 + firmware:
  830 + topic: "${TB_QUEUE_CORE_FW_TOPIC:tb_firmware}"
  831 + pack-interval-ms: "${TB_QUEUE_CORE_FW_PACK_INTERVAL_MS:60000}"
  832 + pack-size: "${TB_QUEUE_CORE_FW_PACK_SIZE:100}"
818 833 usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}"
819 834 stats:
820 835 enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
... ...
  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}'=='null')")
  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' || '${service.type:null}'=='tb-core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')")
  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' || '${service.type:null}'=='tb-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 +}
... ...
... ... @@ -61,6 +61,8 @@ public interface DeviceService {
61 61
62 62 PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
63 63
  64 + PageData<Device> findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink);
  65 +
64 66 PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
65 67
66 68 PageData<DeviceInfo> findDeviceInfosByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink);
... ...
  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 +}
... ...
... ... @@ -29,4 +29,5 @@ public class CacheConstants {
29 29 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
30 30 public static final String ATTRIBUTES_CACHE = "attributes";
31 31 public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
  32 + public static final String FIRMWARE_CACHE = "firmwares";
32 33 }
... ...
... ... @@ -93,6 +93,21 @@ public class DataConstants {
93 93 public static final String USERNAME = "username";
94 94 public static final String PASSWORD = "password";
95 95
  96 + //firmware
  97 + //telemetry
  98 + public static final String CURRENT_FIRMWARE_TITLE = "cur_fw_title";
  99 + public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version";
  100 + public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
  101 + public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
  102 + public static final String FIRMWARE_STATE = "fw_state";
  103 +
  104 + //attributes
  105 + //telemetry
  106 + public static final String FIRMWARE_TITLE = "fw_title";
  107 + public static final String FIRMWARE_VERSION = "fw_version";
  108 + public static final String FIRMWARE_SIZE = "fw_size";
  109 + public static final String FIRMWARE_CHECKSUM = "fw_checksum";
  110 + public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm";
96 111 public static final String EDGE_MSG_SOURCE = "edge";
97 112 public static final String MSG_SOURCE_KEY = "source";
98 113 }
... ...
... ... @@ -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, EDGE;
  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, EDGE;
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 transient ByteBuffer data;
  31 +
  32 + public Firmware() {
  33 + super();
  34 + }
  35 +
  36 + public Firmware(FirmwareId id) {
  37 + super(id);
  38 + }
  39 +
  40 + public Firmware(Firmware firmware) {
  41 + super(firmware);
  42 + this.data = firmware.getData();
  43 + }
  44 +}
... ...
  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 + private String fileName;
  36 + private String contentType;
  37 + private String checksumAlgorithm;
  38 + private String checksum;
  39 + private Long dataSize;
  40 +
  41 +
  42 + public FirmwareInfo() {
  43 + super();
  44 + }
  45 +
  46 + public FirmwareInfo(FirmwareId id) {
  47 + super(id);
  48 + }
  49 +
  50 + public FirmwareInfo(FirmwareInfo firmwareInfo) {
  51 + super(firmwareInfo);
  52 + this.tenantId = firmwareInfo.getTenantId();
  53 + this.title = firmwareInfo.getTitle();
  54 + this.version = firmwareInfo.getVersion();
  55 + this.hasData = firmwareInfo.isHasData();
  56 + this.fileName = firmwareInfo.getFileName();
  57 + this.contentType = firmwareInfo.getContentType();
  58 + this.checksumAlgorithm = firmwareInfo.getChecksumAlgorithm();
  59 + this.checksum = firmwareInfo.getChecksum();
  60 + this.dataSize = firmwareInfo.getDataSize();
  61 + }
  62 +
  63 + @Override
  64 + public String getSearchText() {
  65 + return title;
  66 + }
  67 +}
... ...
... ... @@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.validation.NoXss;
27 27 @EqualsAndHashCode(callSuper = true)
28 28 public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
29 29
  30 + private static final long serialVersionUID = 7282664529021651736L;
  31 +
30 32 private TenantId tenantId;
31 33 @NoXss
32 34 private String title;
... ...
... ... @@ -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 }
... ...
... ... @@ -71,6 +71,8 @@ public class EntityIdFactory {
71 71 return new ApiUsageStateId(uuid);
72 72 case TB_RESOURCE:
73 73 return new TbResourceId(uuid);
  74 + case FIRMWARE:
  75 + return new FirmwareId(uuid);
74 76 case EDGE:
75 77 return new EdgeId(uuid);
76 78 }
... ...
  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 +}
... ...
... ... @@ -69,7 +69,7 @@ public class TbKafkaConsumerStatsService {
69 69 this.adminClient = AdminClient.create(kafkaSettings.toAdminProps());
70 70 this.statsPrintScheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("kafka-consumer-stats"));
71 71
72   - Properties consumerProps = kafkaSettings.toConsumerProps();
  72 + Properties consumerProps = kafkaSettings.toConsumerProps(null);
73 73 consumerProps.put(ConsumerConfig.CLIENT_ID_CONFIG, "consumer-stats-loader-client");
74 74 consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer-stats-loader-client-group");
75 75 this.consumer = new KafkaConsumer<>(consumerProps);
... ...
... ... @@ -50,7 +50,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
50 50 String clientId, String groupId, String topic,
51 51 TbQueueAdmin admin, TbKafkaConsumerStatsService statsService) {
52 52 super(topic);
53   - Properties props = settings.toConsumerProps();
  53 + Properties props = settings.toConsumerProps(topic);
54 54 props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
55 55 if (groupId != null) {
56 56 props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
... ...
... ... @@ -31,7 +31,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
31 31 import org.springframework.boot.context.properties.ConfigurationProperties;
32 32 import org.springframework.stereotype.Component;
33 33
  34 +import java.util.Collections;
34 35 import java.util.List;
  36 +import java.util.Map;
35 37 import java.util.Properties;
36 38
37 39 /**
... ... @@ -95,6 +97,9 @@ public class TbKafkaSettings {
95 97 @Setter
96 98 private List<TbKafkaProperty> other;
97 99
  100 + @Setter
  101 + private Map<String, List<TbKafkaProperty>> consumerPropertiesPerTopic = Collections.emptyMap();
  102 +
98 103 public Properties toAdminProps() {
99 104 Properties props = toProps();
100 105 props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
... ... @@ -103,7 +108,7 @@ public class TbKafkaSettings {
103 108 return props;
104 109 }
105 110
106   - public Properties toConsumerProps() {
  111 + public Properties toConsumerProps(String topic) {
107 112 Properties props = toProps();
108 113 props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
109 114 props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
... ... @@ -113,6 +118,10 @@ public class TbKafkaSettings {
113 118
114 119 props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
115 120 props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
  121 +
  122 + consumerPropertiesPerTopic
  123 + .getOrDefault(topic, Collections.emptyList())
  124 + .forEach(kv -> props.put(kv.getKey(), kv.getValue()));
116 125 return props;
117 126 }
118 127
... ...
... ... @@ -19,6 +19,7 @@ import lombok.Getter;
19 19 import org.springframework.beans.factory.annotation.Value;
20 20 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 21 import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.StringUtils;
22 23
23 24 import javax.annotation.PostConstruct;
24 25 import java.util.HashMap;
... ... @@ -37,6 +38,8 @@ public class TbKafkaTopicConfigs {
37 38 private String notificationsProperties;
38 39 @Value("${queue.kafka.topic-properties.js-executor}")
39 40 private String jsExecutorProperties;
  41 + @Value("${queue.kafka.topic-properties.fw-updates:}")
  42 + private String fwUpdatesProperties;
40 43
41 44 @Getter
42 45 private Map<String, String> coreConfigs;
... ... @@ -48,6 +51,8 @@ public class TbKafkaTopicConfigs {
48 51 private Map<String, String> notificationsConfigs;
49 52 @Getter
50 53 private Map<String, String> jsExecutorConfigs;
  54 + @Getter
  55 + private Map<String, String> fwUpdatesConfigs;
51 56
52 57 @PostConstruct
53 58 private void init() {
... ... @@ -56,15 +61,18 @@ public class TbKafkaTopicConfigs {
56 61 transportApiConfigs = getConfigs(transportApiProperties);
57 62 notificationsConfigs = getConfigs(notificationsProperties);
58 63 jsExecutorConfigs = getConfigs(jsExecutorProperties);
  64 + fwUpdatesConfigs = getConfigs(fwUpdatesProperties);
59 65 }
60 66
61 67 private Map<String, String> getConfigs(String properties) {
62 68 Map<String, String> configs = new HashMap<>();
63   - for (String property : properties.split(";")) {
64   - int delimiterPosition = property.indexOf(":");
65   - String key = property.substring(0, delimiterPosition);
66   - String value = property.substring(delimiterPosition + 1);
67   - configs.put(key, value);
  69 + if (StringUtils.isNotEmpty(properties)) {
  70 + for (String property : properties.split(";")) {
  71 + int delimiterPosition = property.indexOf(":");
  72 + String key = property.substring(0, delimiterPosition);
  73 + String value = property.substring(delimiterPosition + 1);
  74 + configs.put(key, value);
  75 + }
68 76 }
69 77 return configs;
70 78 }
... ...
... ... @@ -186,6 +186,17 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
186 186 msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
187 187 }
188 188
  189 + @Override
  190 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  191 + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getFirmwareTopic(),
  192 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  193 + }
  194 +
  195 + @Override
  196 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  197 + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getFirmwareTopic());
  198 + }
  199 +
189 200 @PreDestroy
190 201 private void destroy() {
191 202 if (coreAdmin != null) {
... ...
... ... @@ -21,12 +21,13 @@ import org.springframework.context.annotation.Bean;
21 21 import org.springframework.stereotype.Component;
22 22 import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24   -import org.thingsboard.server.gen.transport.TransportProtos;
25 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
26 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
27 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
28 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
29 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
  30 +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
30 31 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
31 32 import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
32 33 import org.thingsboard.server.queue.TbQueueAdmin;
... ... @@ -165,14 +166,25 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory {
165 166 }
166 167
167 168 @Override
168   - public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
  169 + public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
169 170 return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic());
170 171 }
171 172
172 173 @Override
173   - public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
  174 + public TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer() {
174 175 return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(),
175   - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  176 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  177 + }
  178 +
  179 + @Override
  180 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  181 + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getFirmwareTopic(),
  182 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  183 + }
  184 +
  185 + @Override
  186 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  187 + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getFirmwareTopic());
176 188 }
177 189
178 190 @PreDestroy
... ...
... ... @@ -131,6 +131,16 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
131 131 }
132 132
133 133 @Override
  134 + public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  135 + return new InMemoryTbQueueConsumer<>(coreSettings.getFirmwareTopic());
  136 + }
  137 +
  138 + @Override
  139 + public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  140 + return new InMemoryTbQueueProducer<>(coreSettings.getFirmwareTopic());
  141 + }
  142 +
  143 + @Override
134 144 public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
135 145 return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic());
136 146 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -73,6 +74,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
73 74 private final TbQueueAdmin jsExecutorAdmin;
74 75 private final TbQueueAdmin transportApiAdmin;
75 76 private final TbQueueAdmin notificationAdmin;
  77 + private final TbQueueAdmin fwUpdatesAdmin;
76 78
77 79 public KafkaMonolithQueueFactory(PartitionService partitionService, TbKafkaSettings kafkaSettings,
78 80 TbServiceInfoProvider serviceInfoProvider,
... ... @@ -98,6 +100,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
98 100 this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs());
99 101 this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs());
100 102 this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs());
  103 + this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs());
101 104 }
102 105
103 106 @Override
... ... @@ -274,6 +277,29 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
274 277 }
275 278
276 279 @Override
  280 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  281 + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
  282 + consumerBuilder.settings(kafkaSettings);
  283 + consumerBuilder.topic(coreSettings.getFirmwareTopic());
  284 + consumerBuilder.clientId("monolith-fw-consumer-" + serviceInfoProvider.getServiceId());
  285 + consumerBuilder.groupId("monolith-fw-consumer");
  286 + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  287 + consumerBuilder.admin(fwUpdatesAdmin);
  288 + consumerBuilder.statsService(consumerStatsService);
  289 + return consumerBuilder.build();
  290 + }
  291 +
  292 + @Override
  293 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  294 + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
  295 + requestBuilder.settings(kafkaSettings);
  296 + requestBuilder.clientId("monolith-fw-producer-" + serviceInfoProvider.getServiceId());
  297 + requestBuilder.defaultTopic(coreSettings.getFirmwareTopic());
  298 + requestBuilder.admin(fwUpdatesAdmin);
  299 + return requestBuilder.build();
  300 + }
  301 +
  302 + @Override
277 303 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
278 304 TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToUsageStatsServiceMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
279 305 requestBuilder.settings(kafkaSettings);
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -70,6 +71,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
70 71 private final TbQueueAdmin jsExecutorAdmin;
71 72 private final TbQueueAdmin transportApiAdmin;
72 73 private final TbQueueAdmin notificationAdmin;
  74 + private final TbQueueAdmin fwUpdatesAdmin;
73 75
74 76 public KafkaTbCoreQueueFactory(PartitionService partitionService, TbKafkaSettings kafkaSettings,
75 77 TbServiceInfoProvider serviceInfoProvider,
... ... @@ -93,6 +95,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
93 95 this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs());
94 96 this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs());
95 97 this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs());
  98 + this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs());
96 99 }
97 100
98 101 @Override
... ... @@ -242,6 +245,29 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
242 245 }
243 246
244 247 @Override
  248 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  249 + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
  250 + consumerBuilder.settings(kafkaSettings);
  251 + consumerBuilder.topic(coreSettings.getFirmwareTopic());
  252 + consumerBuilder.clientId("tb-core-fw-consumer-" + serviceInfoProvider.getServiceId());
  253 + consumerBuilder.groupId("tb-core-fw-consumer");
  254 + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  255 + consumerBuilder.admin(fwUpdatesAdmin);
  256 + consumerBuilder.statsService(consumerStatsService);
  257 + return consumerBuilder.build();
  258 + }
  259 +
  260 + @Override
  261 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  262 + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
  263 + requestBuilder.settings(kafkaSettings);
  264 + requestBuilder.clientId("tb-core-fw-producer-" + serviceInfoProvider.getServiceId());
  265 + requestBuilder.defaultTopic(coreSettings.getFirmwareTopic());
  266 + requestBuilder.admin(fwUpdatesAdmin);
  267 + return requestBuilder.build();
  268 + }
  269 +
  270 + @Override
245 271 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
246 272 TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToUsageStatsServiceMsg>> requestBuilder = TbKafkaProducerTemplate.builder();
247 273 requestBuilder.settings(kafkaSettings);
... ...
... ... @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component;
22 22 import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest;
24 24 import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse;
  25 +import org.thingsboard.server.gen.transport.TransportProtos.*;
25 26 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
... ... @@ -191,6 +192,17 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng
191 192 }
192 193
193 194 @Override
  195 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  196 + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic(),
  197 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  198 + }
  199 +
  200 + @Override
  201 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  202 + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic());
  203 + }
  204 +
  205 + @Override
194 206 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
195 207 return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic());
196 208 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -165,6 +166,17 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory {
165 166 }
166 167
167 168 @Override
  169 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  170 + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic(),
  171 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  172 + }
  173 +
  174 + @Override
  175 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  176 + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic());
  177 + }
  178 +
  179 + @Override
168 180 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
169 181 return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic());
170 182 }
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest;
24 24 import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
26 26 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  27 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
29 30 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -189,6 +190,17 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
189 190 }
190 191
191 192 @Override
  193 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  194 + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic(),
  195 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  196 + }
  197 +
  198 + @Override
  199 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  200 + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic());
  201 + }
  202 +
  203 + @Override
192 204 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
193 205 return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic());
194 206 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -171,6 +172,17 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory {
171 172 }
172 173
173 174 @Override
  175 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  176 + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic(),
  177 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  178 + }
  179 +
  180 + @Override
  181 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  182 + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic());
  183 + }
  184 +
  185 + @Override
174 186 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
175 187 return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic());
176 188 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -188,6 +189,17 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul
188 189 }
189 190
190 191 @Override
  192 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  193 + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic(),
  194 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  195 + }
  196 +
  197 + @Override
  198 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  199 + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic());
  200 + }
  201 +
  202 + @Override
191 203 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
192 204 return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic());
193 205 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
23 23 import org.thingsboard.server.gen.js.JsInvokeProtos;
24 24 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
25 25 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
  26 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
26 27 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
27 28 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
28 29 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
... ... @@ -171,6 +172,17 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory {
171 172 }
172 173
173 174 @Override
  175 + public TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer() {
  176 + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic(),
  177 + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders()));
  178 + }
  179 +
  180 + @Override
  181 + public TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer() {
  182 + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic());
  183 + }
  184 +
  185 + @Override
174 186 public TbQueueProducer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgProducer() {
175 187 return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic());
176 188 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.queue.provider;
17 17
18 18 import org.thingsboard.server.gen.js.JsInvokeProtos;
  19 +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg;
19 20 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
20 21 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
21 22 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
... ... @@ -86,6 +87,20 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory {
86 87 TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> createToUsageStatsServiceMsgConsumer();
87 88
88 89 /**
  90 + * Used to consume messages about firmware update notifications by TB Core Service
  91 + *
  92 + * @return
  93 + */
  94 + TbQueueConsumer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgConsumer();
  95 +
  96 + /**
  97 + * Used to consume messages about firmware update notifications by TB Core Service
  98 + *
  99 + * @return
  100 + */
  101 + TbQueueProducer<TbProtoQueueMsg<ToFirmwareStateServiceMsg>> createToFirmwareStateServiceMsgProducer();
  102 +
  103 + /**
89 104 * Used to consume high priority messages by TB Core Service
90 105 *
91 106 * @return
... ...
... ... @@ -26,6 +26,9 @@ public class TbQueueCoreSettings {
26 26 @Value("${queue.core.topic}")
27 27 private String topic;
28 28
  29 + @Value("${queue.core.firmware.topic:tb_firmware}")
  30 + private String firmwareTopic;
  31 +
29 32 @Value("${queue.core.usage-stats-topic:tb_usage_stats}")
30 33 private String usageStatsTopic;
31 34
... ...
... ... @@ -340,17 +340,35 @@ message ProvisionDeviceCredentialsMsg {
340 340 }
341 341
342 342 message ProvisionDeviceResponseMsg {
343   - ProvisionResponseStatus status = 1;
  343 + ResponseStatus status = 1;
344 344 CredentialsType credentialsType = 2;
345 345 string credentialsValue = 3;
346 346 }
347 347
348   -enum ProvisionResponseStatus {
  348 +enum ResponseStatus {
349 349 UNKNOWN = 0;
350 350 SUCCESS = 1;
351 351 NOT_FOUND = 2;
352 352 FAILURE = 3;
353 353 }
  354 +
  355 +message GetFirmwareRequestMsg {
  356 + int64 deviceIdMSB = 1;
  357 + int64 deviceIdLSB = 2;
  358 + int64 tenantIdMSB = 3;
  359 + int64 tenantIdLSB = 4;
  360 +}
  361 +
  362 +message GetFirmwareResponseMsg {
  363 + ResponseStatus responseStatus = 1;
  364 + int64 firmwareIdMSB = 2;
  365 + int64 firmwareIdLSB = 3;
  366 + string title = 4;
  367 + string version = 5;
  368 + string contentType = 6;
  369 + string fileName = 7;
  370 +}
  371 +
354 372 //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
355 373 message SubscriptionInfoProto {
356 374 int64 lastActivityTime = 1;
... ... @@ -571,6 +589,7 @@ message TransportApiRequestMsg {
571 589 ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
572 590 ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
573 591 GetResourceRequestMsg resourceRequestMsg = 9;
  592 + GetFirmwareRequestMsg firmwareRequestMsg = 10;
574 593 }
575 594
576 595 /* Response from ThingsBoard Core Service to Transport Service */
... ... @@ -581,6 +600,7 @@ message TransportApiResponseMsg {
581 600 ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
582 601 LwM2MResponseMsg lwM2MResponseMsg = 6;
583 602 GetResourceResponseMsg resourceResponseMsg = 7;
  603 + GetFirmwareResponseMsg firmwareResponseMsg = 8;
584 604 }
585 605
586 606 /* Messages that are handled by ThingsBoard Core Service */
... ... @@ -645,3 +665,13 @@ message ToUsageStatsServiceMsg {
645 665 int64 customerIdMSB = 6;
646 666 int64 customerIdLSB = 7;
647 667 }
  668 +
  669 +message ToFirmwareStateServiceMsg {
  670 + int64 ts = 1;
  671 + int64 tenantIdMSB = 2;
  672 + int64 tenantIdLSB = 3;
  673 + int64 deviceIdMSB = 4;
  674 + int64 deviceIdLSB = 5;
  675 + int64 firmwareIdMSB = 6;
  676 + int64 firmwareIdLSB = 7;
  677 +}
... ...
... ... @@ -407,7 +407,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
407 407 @Override
408 408 public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) {
409 409 CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED;
410   - if (!msg.getStatus().equals(TransportProtos.ProvisionResponseStatus.SUCCESS)) {
  410 + if (!msg.getStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
411 411 responseCode = CoAP.ResponseCode.BAD_REQUEST;
412 412 }
413 413 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,25 @@ 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 = "title") String title,
  211 + @RequestParam(value = "version") String version,
  212 + @RequestParam(value = "chunkSize", required = false, defaultValue = "0") int chunkSize,
  213 + @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
  214 + DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
  215 + transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
  216 + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
  217 + TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
  218 + .setTenantIdMSB(sessionInfo.getTenantIdMSB())
  219 + .setTenantIdLSB(sessionInfo.getTenantIdLSB())
  220 + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
  221 + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build();
  222 + transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, title, version, chunkSize, chunk));
  223 + }));
  224 + return responseWriter;
  225 + }
  226 +
207 227 @RequestMapping(value = "/provision", method = RequestMethod.POST)
208 228 public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) {
209 229 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
... ... @@ -258,6 +278,47 @@ public class DeviceApiController {
258 278 }
259 279 }
260 280
  281 + private class GetFirmwareCallback implements TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> {
  282 + private final DeferredResult<ResponseEntity> responseWriter;
  283 + private final String title;
  284 + private final String version;
  285 + private final int chuckSize;
  286 + private final int chuck;
  287 +
  288 + GetFirmwareCallback(DeferredResult<ResponseEntity> responseWriter, String title, String version, int chuckSize, int chuck) {
  289 + this.responseWriter = responseWriter;
  290 + this.title = title;
  291 + this.version = version;
  292 + this.chuckSize = chuckSize;
  293 + this.chuck = chuck;
  294 + }
  295 +
  296 + @Override
  297 + public void onSuccess(TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg) {
  298 + if (!TransportProtos.ResponseStatus.SUCCESS.equals(firmwareResponseMsg.getResponseStatus())) {
  299 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND));
  300 + } else if (title.equals(firmwareResponseMsg.getTitle()) && version.equals(firmwareResponseMsg.getVersion())) {
  301 + String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString();
  302 + ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareCacheReader().get(firmwareId, chuckSize, chuck));
  303 + ResponseEntity<ByteArrayResource> response = ResponseEntity.ok()
  304 + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmwareResponseMsg.getFileName())
  305 + .header("x-filename", firmwareResponseMsg.getFileName())
  306 + .contentLength(resource.contentLength())
  307 + .contentType(parseMediaType(firmwareResponseMsg.getContentType()))
  308 + .body(resource);
  309 + responseWriter.setResult(response);
  310 + } else {
  311 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
  312 + }
  313 + }
  314 +
  315 + @Override
  316 + public void onError(Throwable e) {
  317 + log.warn("Failed to process request", e);
  318 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
  319 + }
  320 + }
  321 +
261 322 private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
262 323 private final TransportService transportService;
263 324 private final SessionInfoProto sessionInfo;
... ... @@ -338,4 +399,12 @@ public class DeviceApiController {
338 399 .build(), TransportServiceCallback.EMPTY);
339 400 }
340 401
  402 + private static MediaType parseMediaType(String contentType) {
  403 + try {
  404 + return MediaType.parseMediaType(contentType);
  405 + } catch (Exception e) {
  406 + return MediaType.APPLICATION_OCTET_STREAM;
  407 + }
  408 + }
  409 +
341 410 }
... ...
... ... @@ -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 }
... ...
... ... @@ -22,13 +22,8 @@ import com.google.protobuf.DynamicMessage;
22 22 import com.google.protobuf.InvalidProtocolBufferException;
23 23 import com.google.protobuf.util.JsonFormat;
24 24 import io.netty.buffer.ByteBuf;
25   -import io.netty.buffer.ByteBufAllocator;
26   -import io.netty.buffer.UnpooledByteBufAllocator;
27   -import io.netty.handler.codec.mqtt.MqttFixedHeader;
28 25 import io.netty.handler.codec.mqtt.MqttMessage;
29   -import io.netty.handler.codec.mqtt.MqttMessageType;
30 26 import io.netty.handler.codec.mqtt.MqttPublishMessage;
31   -import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
32 27 import lombok.extern.slf4j.Slf4j;
33 28 import org.springframework.stereotype.Component;
34 29 import org.springframework.util.StringUtils;
... ... @@ -47,8 +42,6 @@ import java.util.Optional;
47 42 @Slf4j
48 43 public class ProtoMqttAdaptor implements MqttTransportAdaptor {
49 44
50   - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
51   -
52 45 @Override
53 46 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
54 47 DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
... ... @@ -149,7 +142,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
149 142 }
150 143 }
151 144
152   -
153 145 @Override
154 146 public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException {
155 147 DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
... ... @@ -173,6 +165,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
173 165 }
174 166
175 167 @Override
  168 + public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException {
  169 + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk));
  170 + }
  171 +
  172 + @Override
176 173 public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
177 174 if (!StringUtils.isEmpty(responseMsg.getError())) {
178 175 throw new AdaptorException(responseMsg.getError());
... ... @@ -210,15 +207,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
210 207 return bytes;
211 208 }
212 209
213   - private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadBytes) {
214   - MqttFixedHeader mqttFixedHeader =
215   - new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
216   - MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
217   - ByteBuf payload = ALLOCATOR.buffer();
218   - payload.writeBytes(payloadBytes);
219   - return new MqttPublishMessage(mqttFixedHeader, header, payload);
220   - }
221   -
222 210 private int getRequestId(String topicName, String topic) {
223 211 return Integer.parseInt(topicName.substring(topic.length()));
224 212 }
... ...
... ... @@ -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;
... ... @@ -427,12 +427,12 @@ public class JsonConverter {
427 427
428 428 private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
429 429 JsonObject result = new JsonObject();
430   - if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) {
  430 + if (payload.getStatus() == ResponseStatus.NOT_FOUND) {
431 431 result.addProperty("errorMsg", "Provision data was not found!");
432   - result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name());
433   - } else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) {
  432 + result.addProperty("status", ResponseStatus.NOT_FOUND.name());
  433 + } else if (payload.getStatus() == TransportProtos.ResponseStatus.FAILURE) {
434 434 result.addProperty("errorMsg", "Failed to provision device!");
435   - result.addProperty("status", ProvisionResponseStatus.FAILURE.name());
  435 + result.addProperty("status", ResponseStatus.FAILURE.name());
436 436 } else {
437 437 if (toGateway) {
438 438 result.addProperty("id", requestId);
... ... @@ -449,7 +449,7 @@ public class JsonConverter {
449 449 break;
450 450 }
451 451 result.addProperty("credentialsType", payload.getCredentialsType().name());
452   - result.addProperty("status", ProvisionResponseStatus.SUCCESS.name());
  452 + result.addProperty("status", ResponseStatus.SUCCESS.name());
453 453 }
454 454 return result;
455 455 }
... ...
... ... @@ -380,7 +380,6 @@ public class DefaultTransportService implements TransportService {
380 380 AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
381 381 }
382 382
383   -
384 383 @Override
385 384 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
386 385 if (log.isTraceEnabled()) {
... ... @@ -534,6 +533,19 @@ public class DefaultTransportService implements TransportService {
534 533 }
535 534
536 535 @Override
  536 + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetFirmwareRequestMsg msg, TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> callback) {
  537 + if (checkLimits(sessionInfo, msg, callback)) {
  538 + TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg =
  539 + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setFirmwareRequestMsg(msg).build());
  540 +
  541 + AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), response -> {
  542 + TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg = response.getValue().getFirmwareResponseMsg();
  543 + callback.onSuccess(firmwareResponseMsg);
  544 + }, callback::onError, transportCallbackExecutor);
  545 + }
  546 + }
  547 +
  548 + @Override
537 549 public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
538 550 return reportActivityInternal(sessionInfo);
539 551 }
... ... @@ -612,11 +624,11 @@ public class DefaultTransportService implements TransportService {
612 624 sessions.remove(toSessionId(sessionInfo));
613 625 }
614 626
615   - private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
  627 + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback) {
616 628 return checkLimits(sessionInfo, msg, callback, 0);
617 629 }
618 630
619   - private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints) {
  631 + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback, int dataPoints) {
620 632 if (log.isTraceEnabled()) {
621 633 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
622 634 }
... ...
... ... @@ -81,6 +81,8 @@ public interface DeviceDao extends Dao<Device>, TenantEntityDao {
81 81 */
82 82 PageData<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink);
83 83
  84 + PageData<Device> findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink);
  85 +
84 86 /**
85 87 * Find device infos by tenantId, type and page link.
86 88 *
... ...
... ... @@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
42 42 import org.thingsboard.server.common.data.DeviceProfileProvisionType;
43 43 import org.thingsboard.server.common.data.DeviceProfileType;
44 44 import org.thingsboard.server.common.data.DeviceTransportType;
  45 +import org.thingsboard.server.common.data.Firmware;
45 46 import org.thingsboard.server.common.data.Tenant;
46 47 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
47 48 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
... ... @@ -61,6 +62,7 @@ import org.thingsboard.server.common.data.page.PageData;
61 62 import org.thingsboard.server.common.data.page.PageLink;
62 63 import org.thingsboard.server.dao.entity.AbstractEntityService;
63 64 import org.thingsboard.server.dao.exception.DataValidationException;
  65 +import org.thingsboard.server.dao.firmware.FirmwareService;
64 66 import org.thingsboard.server.dao.service.DataValidator;
65 67 import org.thingsboard.server.dao.service.PaginatedRemover;
66 68 import org.thingsboard.server.dao.service.Validator;
... ... @@ -111,6 +113,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
111 113 @Autowired
112 114 private CacheManager cacheManager;
113 115
  116 + @Autowired
  117 + private FirmwareService firmwareService;
  118 +
114 119 private final Lock findOrCreateLock = new ReentrantLock();
115 120
116 121 @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}")
... ... @@ -395,6 +400,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
395 400 }
396 401 }
397 402
  403 + if (deviceProfile.getFirmwareId() != null) {
  404 + Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId());
  405 + if (firmware == null) {
  406 + throw new DataValidationException("Can't assign non-existent firmware!");
  407 + }
  408 + if (firmware.getData() == null) {
  409 + throw new DataValidationException("Can't assign firmware with empty data!");
  410 + }
  411 + }
398 412 }
399 413
400 414 @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.asset.Asset;
44 45 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
... ... @@ -72,6 +73,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
72 73 import org.thingsboard.server.dao.entity.AbstractEntityService;
73 74 import org.thingsboard.server.dao.event.EventService;
74 75 import org.thingsboard.server.dao.exception.DataValidationException;
  76 +import org.thingsboard.server.dao.firmware.FirmwareService;
75 77 import org.thingsboard.server.dao.service.DataValidator;
76 78 import org.thingsboard.server.dao.service.PaginatedRemover;
77 79 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
... ... @@ -131,6 +133,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
131 133 @Lazy
132 134 private TbTenantProfileCache tenantProfileCache;
133 135
  136 + @Autowired
  137 + private FirmwareService firmwareService;
  138 +
134 139 @Override
135 140 public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
136 141 log.trace("Executing findDeviceInfoById [{}]", deviceId);
... ... @@ -350,6 +355,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
350 355 }
351 356
352 357 @Override
  358 + public PageData<Device> findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink) {
  359 + log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);
  360 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  361 + validateString(type, "Incorrect type " + type);
  362 + validatePageLink(pageLink);
  363 + return deviceDao.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId.getId(), type, pageLink);
  364 + }
  365 +
  366 + @Override
353 367 public PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) {
354 368 log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);
355 369 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
... ... @@ -663,6 +677,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
663 677 throw new DataValidationException("Can't assign device to customer from different tenant!");
664 678 }
665 679 }
  680 +
  681 + if (device.getFirmwareId() != null) {
  682 + Firmware firmware = firmwareService.findFirmwareById(tenantId, device.getFirmwareId());
  683 + if (firmware == null) {
  684 + throw new DataValidationException("Can't assign non-existent firmware!");
  685 + }
  686 + if (firmware.getData() == null) {
  687 + throw new DataValidationException("Can't assign firmware with empty data!");
  688 + }
  689 + }
666 690 }
667 691 };
668 692
... ...
  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 + BaseFirmwareService.validateUpdate(firmware, firmwareOld);
  188 + }
  189 + };
  190 +
  191 + private DataValidator<Firmware> firmwareValidator = new DataValidator<>() {
  192 +
  193 + @Override
  194 + protected void validateDataImpl(TenantId tenantId, Firmware firmware) {
  195 + if (firmware.getTenantId() == null) {
  196 + throw new DataValidationException("Firmware should be assigned to tenant!");
  197 + } else {
  198 + Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId());
  199 + if (tenant == null) {
  200 + throw new DataValidationException("Firmware is referencing to non-existent tenant!");
  201 + }
  202 + }
  203 +
  204 + if (StringUtils.isEmpty(firmware.getTitle())) {
  205 + throw new DataValidationException("Firmware title should be specified!");
  206 + }
  207 +
  208 + if (StringUtils.isEmpty(firmware.getVersion())) {
  209 + throw new DataValidationException("Firmware version should be specified!");
  210 + }
  211 +
  212 + if (StringUtils.isEmpty(firmware.getFileName())) {
  213 + throw new DataValidationException("Firmware file name should be specified!");
  214 + }
  215 +
  216 + if (StringUtils.isEmpty(firmware.getContentType())) {
  217 + throw new DataValidationException("Firmware content type should be specified!");
  218 + }
  219 +
  220 + ByteBuffer data = firmware.getData();
  221 + if (data == null || !data.hasArray() || data.array().length == 0) {
  222 + throw new DataValidationException("Firmware data should be specified!");
  223 + }
  224 +
  225 + if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) {
  226 + throw new DataValidationException("Firmware checksum algorithm should be specified!");
  227 + }
  228 + if (StringUtils.isEmpty(firmware.getChecksum())) {
  229 + throw new DataValidationException("Firmware checksum should be specified!");
  230 + }
  231 +
  232 + HashFunction hashFunction;
  233 + switch (firmware.getChecksumAlgorithm()) {
  234 + case "sha256":
  235 + hashFunction = Hashing.sha256();
  236 + break;
  237 + case "md5":
  238 + hashFunction = Hashing.md5();
  239 + break;
  240 + case "crc32":
  241 + hashFunction = Hashing.crc32();
  242 + break;
  243 + default:
  244 + throw new DataValidationException("Unknown checksum algorithm!");
  245 + }
  246 +
  247 + String currentChecksum = hashFunction.hashBytes(data.array()).toString();
  248 +
  249 + if (!currentChecksum.equals(firmware.getChecksum())) {
  250 + throw new DataValidationException("Wrong firmware file!");
  251 + }
  252 + }
  253 +
  254 + @Override
  255 + protected void validateUpdate(TenantId tenantId, Firmware firmware) {
  256 + Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId());
  257 +
  258 + BaseFirmwareService.validateUpdate(firmware, firmwareOld);
  259 +
  260 + if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) {
  261 + throw new DataValidationException("Updating firmware data is prohibited!");
  262 + }
  263 + }
  264 + };
  265 +
  266 + private static void validateUpdate(FirmwareInfo firmware, FirmwareInfo firmwareOld) {
  267 + if (!firmwareOld.getTitle().equals(firmware.getTitle())) {
  268 + throw new DataValidationException("Updating firmware title is prohibited!");
  269 + }
  270 +
  271 + if (!firmwareOld.getVersion().equals(firmware.getVersion())) {
  272 + throw new DataValidationException("Updating firmware version is prohibited!");
  273 + }
  274 +
  275 + if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) {
  276 + throw new DataValidationException("Updating firmware file name is prohibited!");
  277 + }
  278 +
  279 + if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) {
  280 + throw new DataValidationException("Updating firmware content type is prohibited!");
  281 + }
  282 +
  283 + if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) {
  284 + throw new DataValidationException("Updating firmware content type is prohibited!");
  285 + }
  286 +
  287 + if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) {
  288 + throw new DataValidationException("Updating firmware content type is prohibited!");
  289 + }
  290 +
  291 + if (firmwareOld.getDataSize() != null && !firmwareOld.getDataSize().equals(firmware.getDataSize())) {
  292 + throw new DataValidationException("Updating firmware data size is prohibited!");
  293 + }
  294 + }
  295 +
  296 + private PaginatedRemover<TenantId, FirmwareInfo> tenantFirmwareRemover =
  297 + new PaginatedRemover<>() {
  298 +
  299 + @Override
  300 + protected PageData<FirmwareInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
  301 + return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink);
  302 + }
  303 +
  304 + @Override
  305 + protected void removeEntity(TenantId tenantId, FirmwareInfo entity) {
  306 + deleteFirmware(tenantId, entity.getId());
  307 + }
  308 + };
  309 +
  310 + protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) {
  311 + if (t instanceof ConstraintViolationException) {
  312 + return Optional.of((ConstraintViolationException) t);
  313 + } else if (t.getCause() instanceof ConstraintViolationException) {
  314 + return Optional.of((ConstraintViolationException) (t.getCause()));
  315 + } else {
  316 + return Optional.empty();
  317 + }
  318 + }
  319 +}
... ...
  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.
... ... @@ -470,6 +472,22 @@ public class ModelConstants {
470 472 public static final String RESOURCE_DATA_COLUMN = "data";
471 473
472 474 /**
  475 + * Firmware constants.
  476 + */
  477 + public static final String FIRMWARE_TABLE_NAME = "firmware";
  478 + public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN;
  479 + public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY;
  480 + public static final String FIRMWARE_VERSION_COLUMN = "version";
  481 + public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name";
  482 + public static final String FIRMWARE_CONTENT_TYPE_COLUMN = "content_type";
  483 + public static final String FIRMWARE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm";
  484 + public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum";
  485 + public static final String FIRMWARE_DATA_COLUMN = "data";
  486 + public static final String FIRMWARE_DATA_SIZE_COLUMN = "data_size";
  487 + public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY;
  488 + public static final String FIRMWARE_HAS_DATA_PROPERTY = "has_data";
  489 +
  490 + /**
473 491 * Edge constants.
474 492 */
475 493 public static final String EDGE_COLUMN_FAMILY_NAME = "edge";
... ...
... ... @@ -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_DATA_SIZE_COLUMN;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN;
  47 +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
  48 +
  49 +@Data
  50 +@EqualsAndHashCode(callSuper = true)
  51 +@Entity
  52 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  53 +@Table(name = FIRMWARE_TABLE_NAME)
  54 +public class FirmwareEntity extends BaseSqlEntity<Firmware> implements SearchTextEntity<Firmware> {
  55 +
  56 + @Column(name = FIRMWARE_TENANT_ID_COLUMN)
  57 + private UUID tenantId;
  58 +
  59 + @Column(name = FIRMWARE_TITLE_COLUMN)
  60 + private String title;
  61 +
  62 + @Column(name = FIRMWARE_VERSION_COLUMN)
  63 + private String version;
  64 +
  65 + @Column(name = FIRMWARE_FILE_NAME_COLUMN)
  66 + private String fileName;
  67 +
  68 + @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN)
  69 + private String contentType;
  70 +
  71 + @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN)
  72 + private String checksumAlgorithm;
  73 +
  74 + @Column(name = FIRMWARE_CHECKSUM_COLUMN)
  75 + private String checksum;
  76 +
  77 + @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY")
  78 + private byte[] data;
  79 +
  80 + @Column(name = FIRMWARE_DATA_SIZE_COLUMN)
  81 + private Long dataSize;
  82 +
  83 + @Type(type = "json")
  84 + @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN)
  85 + private JsonNode additionalInfo;
  86 +
  87 + @Column(name = SEARCH_TEXT_PROPERTY)
  88 + private String searchText;
  89 +
  90 + public FirmwareEntity() {
  91 + super();
  92 + }
  93 +
  94 + public FirmwareEntity(Firmware firmware) {
  95 + this.createdTime = firmware.getCreatedTime();
  96 + this.setUuid(firmware.getUuidId());
  97 + this.tenantId = firmware.getTenantId().getId();
  98 + this.title = firmware.getTitle();
  99 + this.version = firmware.getVersion();
  100 + this.fileName = firmware.getFileName();
  101 + this.contentType = firmware.getContentType();
  102 + this.checksumAlgorithm = firmware.getChecksumAlgorithm();
  103 + this.checksum = firmware.getChecksum();
  104 + this.data = firmware.getData().array();
  105 + this.dataSize = firmware.getDataSize();
  106 + this.additionalInfo = firmware.getAdditionalInfo();
  107 + }
  108 +
  109 + @Override
  110 + public String getSearchTextSource() {
  111 + return title;
  112 + }
  113 +
  114 + @Override
  115 + public void setSearchText(String searchText) {
  116 + this.searchText = searchText;
  117 + }
  118 +
  119 + @Override
  120 + public Firmware toData() {
  121 + Firmware firmware = new Firmware(new FirmwareId(id));
  122 + firmware.setCreatedTime(createdTime);
  123 + firmware.setTenantId(new TenantId(tenantId));
  124 + firmware.setTitle(title);
  125 + firmware.setVersion(version);
  126 + firmware.setFileName(fileName);
  127 + firmware.setContentType(contentType);
  128 + firmware.setChecksumAlgorithm(checksumAlgorithm);
  129 + firmware.setChecksum(checksum);
  130 + firmware.setDataSize(dataSize);
  131 + if (data != null) {
  132 + firmware.setData(ByteBuffer.wrap(data));
  133 + firmware.setHasData(true);
  134 + }
  135 + firmware.setAdditionalInfo(additionalInfo);
  136 + return firmware;
  137 + }
  138 +}
... ...
  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_CHECKSUM_ALGORITHM_COLUMN;
  39 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_HAS_DATA_PROPERTY;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN;
  47 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN;
  48 +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN;
  49 +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
  50 +
  51 +@Data
  52 +@EqualsAndHashCode(callSuper = true)
  53 +@Entity
  54 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  55 +@Table(name = FIRMWARE_TABLE_NAME)
  56 +public class FirmwareInfoEntity extends BaseSqlEntity<FirmwareInfo> implements SearchTextEntity<FirmwareInfo> {
  57 +
  58 + @Column(name = FIRMWARE_TENANT_ID_COLUMN)
  59 + private UUID tenantId;
  60 +
  61 + @Column(name = FIRMWARE_TITLE_COLUMN)
  62 + private String title;
  63 +
  64 + @Column(name = FIRMWARE_VERSION_COLUMN)
  65 + private String version;
  66 +
  67 + @Column(name = FIRMWARE_FILE_NAME_COLUMN)
  68 + private String fileName;
  69 +
  70 + @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN)
  71 + private String contentType;
  72 +
  73 + @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN)
  74 + private String checksumAlgorithm;
  75 +
  76 + @Column(name = FIRMWARE_CHECKSUM_COLUMN)
  77 + private String checksum;
  78 +
  79 + @Column(name = FIRMWARE_DATA_SIZE_COLUMN)
  80 + private Long dataSize;
  81 +
  82 + @Type(type = "json")
  83 + @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN)
  84 + private JsonNode additionalInfo;
  85 +
  86 + @Column(name = SEARCH_TEXT_PROPERTY)
  87 + private String searchText;
  88 +
  89 + @Transient
  90 + private boolean hasData;
  91 +
  92 + public FirmwareInfoEntity() {
  93 + super();
  94 + }
  95 +
  96 + public FirmwareInfoEntity(FirmwareInfo firmware) {
  97 + this.createdTime = firmware.getCreatedTime();
  98 + this.setUuid(firmware.getUuidId());
  99 + this.tenantId = firmware.getTenantId().getId();
  100 + this.title = firmware.getTitle();
  101 + this.version = firmware.getVersion();
  102 + this.fileName = firmware.getFileName();
  103 + this.contentType = firmware.getContentType();
  104 + this.checksumAlgorithm = firmware.getChecksumAlgorithm();
  105 + this.checksum = firmware.getChecksum();
  106 + this.dataSize = firmware.getDataSize();
  107 + this.additionalInfo = firmware.getAdditionalInfo();
  108 + }
  109 +
  110 + public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version,
  111 + String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize,
  112 + Object additionalInfo, boolean hasData) {
  113 + this.id = id;
  114 + this.createdTime = createdTime;
  115 + this.tenantId = tenantId;
  116 + this.title = title;
  117 + this.version = version;
  118 + this.fileName = fileName;
  119 + this.contentType = contentType;
  120 + this.checksumAlgorithm = checksumAlgorithm;
  121 + this.checksum = checksum;
  122 + this.dataSize = dataSize;
  123 + this.hasData = hasData;
  124 + this.additionalInfo = JacksonUtil.convertValue(additionalInfo, JsonNode.class);
  125 + }
  126 +
  127 + @Override
  128 + public String getSearchTextSource() {
  129 + return title;
  130 + }
  131 +
  132 + @Override
  133 + public void setSearchText(String searchText) {
  134 + this.searchText = searchText;
  135 + }
  136 +
  137 + @Override
  138 + public FirmwareInfo toData() {
  139 + FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id));
  140 + firmware.setCreatedTime(createdTime);
  141 + firmware.setTenantId(new TenantId(tenantId));
  142 + firmware.setTitle(title);
  143 + firmware.setVersion(version);
  144 + firmware.setFileName(fileName);
  145 + firmware.setContentType(contentType);
  146 + firmware.setChecksumAlgorithm(checksumAlgorithm);
  147 + firmware.setChecksum(checksum);
  148 + firmware.setDataSize(dataSize);
  149 + firmware.setAdditionalInfo(additionalInfo);
  150 + firmware.setHasData(hasData);
  151 + return firmware;
  152 + }
  153 +}
... ...
... ... @@ -94,6 +94,15 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit
94 94 @Param("textSearch") String textSearch,
95 95 Pageable pageable);
96 96
  97 + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
  98 + "AND d.type = :type " +
  99 + "AND d.firmwareId = null " +
  100 + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
  101 + Page<DeviceEntity> findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId,
  102 + @Param("type") String type,
  103 + @Param("textSearch") String textSearch,
  104 + Pageable pageable);
  105 +
97 106 @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " +
98 107 "FROM DeviceEntity d " +
99 108 "LEFT JOIN CustomerEntity c on c.id = d.customerId " +
... ...
... ... @@ -150,6 +150,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
150 150 }
151 151
152 152 @Override
  153 + public PageData<Device> findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink) {
  154 + return DaoUtil.toPageData(
  155 + deviceRepository.findByTenantIdAndTypeAndFirmwareIdIsNull(
  156 + tenantId,
  157 + type,
  158 + Objects.toString(pageLink.getTextSearch(), ""),
  159 + DaoUtil.toPageable(pageLink)));
  160 + }
  161 +
  162 + @Override
153 163 public PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) {
154 164 return DaoUtil.toPageData(
155 165 deviceRepository.findDeviceInfosByTenantIdAndType(
... ...
  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.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, 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.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, 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.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, 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, '%')) " +
... ...
... ... @@ -158,6 +158,23 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
158 158 CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
159 159 );
160 160
  161 +CREATE TABLE IF NOT EXISTS firmware (
  162 + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
  163 + created_time bigint NOT NULL,
  164 + tenant_id uuid NOT NULL,
  165 + title varchar(255) NOT NULL,
  166 + version varchar(255) NOT NULL,
  167 + file_name varchar(255),
  168 + content_type varchar(255),
  169 + checksum_algorithm varchar(32),
  170 + checksum varchar(1020),
  171 + data binary,
  172 + data_size bigint,
  173 + additional_info varchar,
  174 + search_text varchar(255),
  175 + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
  176 +);
  177 +
161 178 CREATE TABLE IF NOT EXISTS device_profile (
162 179 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
163 180 created_time bigint NOT NULL,
... ... @@ -170,12 +187,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
170 187 search_text varchar(255),
171 188 is_default boolean,
172 189 tenant_id uuid,
  190 + firmware_id uuid,
173 191 default_rule_chain_id uuid,
174 192 default_queue_name varchar(255),
175 193 provision_device_key varchar,
176 194 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
177 195 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
178   - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
  196 + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
  197 + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id)
179 198 );
180 199
181 200 CREATE TABLE IF NOT EXISTS device (
... ... @@ -190,8 +209,10 @@ CREATE TABLE IF NOT EXISTS device (
190 209 label varchar(255),
191 210 search_text varchar(255),
192 211 tenant_id uuid,
  212 + firmware_id uuid,
193 213 CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
194   - CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id)
  214 + CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
  215 + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id)
195 216 );
196 217
197 218 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 $$;
... ... @@ -176,6 +176,23 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
176 176 CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
177 177 );
178 178
  179 +CREATE TABLE IF NOT EXISTS firmware (
  180 + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
  181 + created_time bigint NOT NULL,
  182 + tenant_id uuid NOT NULL,
  183 + title varchar(255) NOT NULL,
  184 + version varchar(255) NOT NULL,
  185 + file_name varchar(255),
  186 + content_type varchar(255),
  187 + checksum_algorithm varchar(32),
  188 + checksum varchar(1020),
  189 + data bytea,
  190 + data_size bigint,
  191 + additional_info varchar,
  192 + search_text varchar(255),
  193 + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
  194 +);
  195 +
179 196 CREATE TABLE IF NOT EXISTS device_profile (
180 197 id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
181 198 created_time bigint NOT NULL,
... ... @@ -188,12 +205,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
188 205 search_text varchar(255),
189 206 is_default boolean,
190 207 tenant_id uuid,
  208 + firmware_id uuid,
191 209 default_rule_chain_id uuid,
192 210 default_queue_name varchar(255),
193 211 provision_device_key varchar,
194 212 CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
195 213 CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
196   - CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
  214 + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
  215 + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id)
197 216 );
198 217
199 218 CREATE TABLE IF NOT EXISTS device (
... ... @@ -208,8 +227,10 @@ CREATE TABLE IF NOT EXISTS device (
208 227 label varchar(255),
209 228 search_text varchar(255),
210 229 tenant_id uuid,
  230 + firmware_id uuid,
211 231 CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
212   - CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id)
  232 + CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
  233 + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id)
213 234 );
214 235
215 236 CREATE TABLE IF NOT EXISTS device_credentials (
... ...
... ... @@ -28,6 +28,7 @@ import java.util.Arrays;
28 28 "org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
29 29 "org.thingsboard.server.dao.service.event.sql.*SqlTest",
30 30 "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
  31 +
31 32 })
32 33 public class SqlDaoServiceTestSuite {
33 34
... ...
... ... @@ -54,6 +54,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
54 54 import org.thingsboard.server.dao.entity.EntityService;
55 55 import org.thingsboard.server.dao.entityview.EntityViewService;
56 56 import org.thingsboard.server.dao.event.EventService;
  57 +import org.thingsboard.server.dao.firmware.FirmwareService;
57 58 import org.thingsboard.server.dao.relation.RelationService;
58 59 import org.thingsboard.server.dao.resource.ResourceService;
59 60 import org.thingsboard.server.dao.rule.RuleChainService;
... ... @@ -157,6 +158,10 @@ public abstract class AbstractServiceTest {
157 158 @Autowired
158 159 protected ResourceService resourceService;
159 160
  161 +
  162 + @Autowired
  163 + protected FirmwareService firmwareService;
  164 +
160 165 public class IdComparator<D extends HasId> implements Comparator<D> {
161 166 @Override
162 167 public int compare(D o1, D o2) {
... ... @@ -203,7 +208,7 @@ public abstract class AbstractServiceTest {
203 208
204 209 @Bean
205 210 public AuditLogLevelFilter auditLogLevelFilter() {
206   - Map<String,String> mask = new HashMap<>();
  211 + Map<String, String> mask = new HashMap<>();
207 212 for (EntityType entityType : EntityType.values()) {
208 213 mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name());
209 214 }
... ...