Browse Source

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

[WIP] feature firmware
pull/4475/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
07aefe533a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  2. 46
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 26
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 14
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  5. 195
      application/src/main/java/org/thingsboard/server/controller/FirmwareController.java
  6. 8
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  7. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  8. 171
      application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java
  9. 27
      application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java
  10. 8
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  11. 27
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  12. 24
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  15. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  16. 65
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  17. 8
      application/src/main/resources/thingsboard.yml
  18. 302
      application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java
  19. 23
      application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java
  20. 33
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java
  21. 60
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java
  22. 38
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java
  23. 22
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java
  24. 20
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java
  25. 49
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java
  26. 38
      common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java
  27. 42
      common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java
  28. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java
  29. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  30. 15
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  31. 14
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  32. 3
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  34. 56
      common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java
  35. 56
      common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java
  36. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  37. 10
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java
  38. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  39. 44
      common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java
  40. 22
      common/queue/src/main/proto/queue.proto
  41. 2
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  42. 65
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  43. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java
  44. 112
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  45. 10
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  46. 18
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java
  47. 22
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  48. 8
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java
  49. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  50. 12
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  51. 18
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  52. 14
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  53. 15
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  54. 317
      dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java
  55. 23
      dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java
  56. 32
      dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java
  57. 18
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  58. 11
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java
  59. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java
  60. 132
      dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java
  61. 116
      dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java
  62. 46
      dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java
  63. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java
  64. 46
      dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java
  65. 84
      dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java
  66. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java
  67. 24
      dao/src/main/resources/sql/schema-entities-hsql.sql
  68. 26
      dao/src/main/resources/sql/schema-entities.sql
  69. 6
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  70. 70
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
  71. 44
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java
  72. 481
      dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java
  73. 23
      dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java
  74. 3
      dao/src/test/resources/application-test.properties
  75. 1
      dao/src/test/resources/sql/hsql/drop-all-tables.sql
  76. 1
      dao/src/test/resources/sql/psql/drop-all-tables.sql
  77. 4
      dao/src/test/resources/sql/timescale/drop-all-tables.sql
  78. 9
      ui-ngx/src/app/core/http/entity.service.ts
  79. 122
      ui-ngx/src/app/core/http/firmware.service.ts
  80. 8
      ui-ngx/src/app/core/http/http-utils.ts
  81. 23
      ui-ngx/src/app/core/http/resource.service.ts
  82. 12
      ui-ngx/src/app/core/services/menu.service.ts
  83. 2
      ui-ngx/src/app/core/utils.ts
  84. 54
      ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html
  85. 236
      ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts
  86. 7
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  87. 4
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html
  88. 8
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts
  89. 4
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.html
  90. 6
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts
  91. 4
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html
  92. 4
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts
  93. 2
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  94. 4
      ui-ngx/src/app/modules/home/pages/device/device.component.html
  95. 2
      ui-ngx/src/app/modules/home/pages/device/device.component.ts
  96. 48
      ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts
  97. 99
      ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts
  98. 35
      ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts
  99. 97
      ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html
  100. 124
      ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts

66
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -0,0 +1,66 @@
--
-- Copyright © 2016-2021 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS resource (
id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
title varchar(255) NOT NULL,
resource_type varchar(32) NOT NULL,
resource_key varchar(255) NOT NULL,
search_text varchar(255),
file_name varchar(255) NOT NULL,
data varchar,
CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
);
CREATE TABLE IF NOT EXISTS firmware (
id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),
checksum varchar(1020),
data bytea,
additional_info varchar,
search_text varchar(255),
CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
);
ALTER TABLE device_profile
ADD COLUMN IF NOT EXISTS firmware_id uuid;
ALTER TABLE device
ADD COLUMN IF NOT EXISTS firmware_id uuid;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
ALTER TABLE device_profile
ADD CONSTRAINT fk_firmware_device_profile
FOREIGN KEY (firmware_id) REFERENCES firmware(id);
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
ALTER TABLE device
ADD CONSTRAINT fk_firmware_device
FOREIGN KEY (firmware_id) REFERENCES firmware(id);
END IF;
END;
$$;

46
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ -37,6 +38,8 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.TbResourceInfo;
@ -61,6 +64,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
@ -97,6 +101,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
@ -114,6 +119,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.firmware.FirmwareStateService;
import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
@ -232,6 +238,12 @@ public abstract class BaseController {
@Autowired
protected TbResourceService resourceService;
@Autowired
protected FirmwareService firmwareService;
@Autowired
protected FirmwareStateService firmwareStateService;
@Autowired
protected TbQueueProducerProvider producerProvider;
@ -470,6 +482,9 @@ public abstract class BaseController {
case TB_RESOURCE:
checkResourceId(new TbResourceId(entityId.getId()), operation);
return;
case FIRMWARE:
checkFirmwareId(new FirmwareId(entityId.getId()), operation);
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
}
@ -701,6 +716,30 @@ public abstract class BaseController {
}
}
Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException {
try {
validateId(firmwareId, "Incorrect firmwareId " + firmwareId);
Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId);
checkNotNull(firmware);
accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware);
return firmware;
} catch (Exception e) {
throw handleException(e, false);
}
}
FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException {
try {
validateId(firmwareId, "Incorrect firmwareId " + firmwareId);
FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId);
checkNotNull(firmwareInfo);
accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo);
return firmwareInfo;
} catch (Exception e) {
throw handleException(e, false);
}
}
@SuppressWarnings("unchecked")
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
@ -924,4 +963,11 @@ public abstract class BaseController {
}
}
protected MediaType parseMediaType(String contentType) {
try {
return MediaType.parseMediaType(contentType);
} catch (Exception e) {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}

26
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -70,6 +70,7 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@RestController
@ -117,13 +118,25 @@ public class DeviceController extends BaseController {
checkEntity(device.getId(), device, Resource.DEVICE);
boolean created = device.getId() == null;
boolean isFirmwareChanged = false;
if (created) {
isFirmwareChanged = true;
} else {
Device oldDevice = deviceService.findDeviceById(getTenantId(), device.getId());
if (!Objects.equals(device.getFirmwareId(), oldDevice.getFirmwareId()) || !oldDevice.getDeviceProfileId().equals(device.getDeviceProfileId())) {
isFirmwareChanged = true;
}
}
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
tbClusterService.onDeviceChange(savedDevice, null);
tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),
device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedDevice.getId(), savedDevice,
savedDevice.getCustomerId(),
@ -134,12 +147,19 @@ public class DeviceController extends BaseController {
} else {
deviceStateService.onDeviceUpdated(savedDevice);
}
if (isFirmwareChanged) {
firmwareStateService.update(savedDevice, created);
}
return savedDevice;
} catch (Exception e) {
} catch (
Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), device,
null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")

14
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -43,6 +43,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@RestController
@ -143,6 +144,15 @@ public class DeviceProfileController extends BaseController {
checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE);
boolean isFirmwareChanged = false;
if (!created) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId());
if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) {
isFirmwareChanged = true;
}
}
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
@ -153,6 +163,10 @@ public class DeviceProfileController extends BaseController {
null,
created ? ActionType.ADDED : ActionType.UPDATED, null);
if (isFirmwareChanged) {
firmwareStateService.update(savedDeviceProfile);
}
return savedDeviceProfile;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile,

195
application/src/main/java/org/thingsboard/server/controller/FirmwareController.java

@ -0,0 +1,195 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.nio.ByteBuffer;
@Slf4j
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class FirmwareController extends BaseController {
public static final String FIRMWARE_ID = "firmwareId";
@PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')")
@RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadFirmware(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
checkParameter(FIRMWARE_ID, strFirmwareId);
try {
FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
Firmware firmware = checkFirmwareId(firmwareId, Operation.READ);
ByteArrayResource resource = new ByteArrayResource(firmware.getData().array());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmware.getFileName())
.header("x-filename", firmware.getFileName())
.contentLength(resource.contentLength())
.contentType(parseMediaType(firmware.getContentType()))
.body(resource);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET)
@ResponseBody
public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
checkParameter(FIRMWARE_ID, strFirmwareId);
try {
FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
return checkFirmwareInfoId(firmwareId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET)
@ResponseBody
public Firmware getFirmwareById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException {
checkParameter(FIRMWARE_ID, strFirmwareId);
try {
FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
return checkFirmwareId(firmwareId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmware", method = RequestMethod.POST)
@ResponseBody
public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException {
firmwareInfo.setTenantId(getTenantId());
checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE);
try {
return firmwareService.saveFirmwareInfo(firmwareInfo);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST)
@ResponseBody
public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId,
@RequestParam(required = false) String checksum,
@RequestParam(required = false) String checksumAlgorithm,
@RequestBody MultipartFile file) throws ThingsboardException {
checkParameter(FIRMWARE_ID, strFirmwareId);
try {
FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ);
Firmware firmware = new Firmware(firmwareId);
firmware.setCreatedTime(info.getCreatedTime());
firmware.setTenantId(getTenantId());
firmware.setTitle(info.getTitle());
firmware.setVersion(info.getVersion());
firmware.setAdditionalInfo(info.getAdditionalInfo());
if (StringUtils.isEmpty(checksumAlgorithm)) {
checksumAlgorithm = "sha256";
checksum = Hashing.sha256().hashBytes(file.getBytes()).toString();
}
firmware.setChecksumAlgorithm(checksumAlgorithm);
firmware.setChecksum(checksum);
firmware.setFileName(file.getOriginalFilename());
firmware.setContentType(file.getContentType());
firmware.setData(ByteBuffer.wrap(file.getBytes()));
return firmwareService.saveFirmware(firmware);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmwares", method = RequestMethod.GET)
@ResponseBody
public PageData<FirmwareInfo> getFirmwares(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET)
@ResponseBody
public PageData<FirmwareInfo> getFirmwares(@PathVariable("hasData") boolean hasData,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndHasData(getTenantId(), hasData, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException {
checkParameter(FIRMWARE_ID, strFirmwareId);
try {
FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId));
checkFirmwareInfoId(firmwareId, Operation.DELETE);
firmwareService.deleteFirmware(getTenantId(), firmwareId);
} catch (Exception e) {
throw handleException(e);
}
}
}

8
application/src/main/java/org/thingsboard/server/controller/TbResourceController.java

@ -55,12 +55,6 @@ public class TbResourceController extends BaseController {
public static final String RESOURCE_ID = "resourceId";
private final TbResourceService resourceService;
public TbResourceController(TbResourceService resourceService) {
this.resourceService = resourceService;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
@ResponseBody
@ -187,7 +181,7 @@ public class TbResourceController extends BaseController {
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException {
checkParameter("resourceId", strResourceId);
checkParameter(RESOURCE_ID, strResourceId);
try {
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
TbResource tbResource = checkResourceId(resourceId, Operation.DELETE);

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -230,7 +230,6 @@ public class ThingsboardInstallService {
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
systemDataLoaderService.loadSystemLwm2mResources();
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();

171
application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java

@ -0,0 +1,171 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.firmware;
import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM;
import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM_ALGORITHM;
import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_SIZE;
import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_TITLE;
import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_VERSION;
@Slf4j
@Service
@TbCoreComponent
public class DefaultFirmwareStateService implements FirmwareStateService {
private final FirmwareService firmwareService;
private final DeviceService deviceService;
private final DeviceProfileService deviceProfileService;
private final RuleEngineTelemetryService telemetryService;
public DefaultFirmwareStateService(FirmwareService firmwareService, DeviceService deviceService, DeviceProfileService deviceProfileService, RuleEngineTelemetryService telemetryService) {
this.firmwareService = firmwareService;
this.deviceService = deviceService;
this.deviceProfileService = deviceProfileService;
this.telemetryService = telemetryService;
}
@Override
public void update(Device device, boolean created) {
FirmwareId firmwareId = device.getFirmwareId();
if (firmwareId == null) {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
firmwareId = deviceProfile.getFirmwareId();
}
if (firmwareId == null) {
if (!created) {
remove(device);
}
} else {
update(device, firmwareService.findFirmwareById(device.getTenantId(), firmwareId), System.currentTimeMillis());
}
}
@Override
public void update(DeviceProfile deviceProfile) {
TenantId tenantId = deviceProfile.getTenantId();
Consumer<Device> updateConsumer;
if (deviceProfile.getFirmwareId() != null) {
Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId());
long ts = System.currentTimeMillis();
updateConsumer = d -> update(d, firmware, ts);
} else {
updateConsumer = this::remove;
}
PageLink pageLink = new PageLink(100);
PageData<Device> pageData;
do {
//TODO: create a query which will return devices without firmware
pageData = deviceService.findDevicesByTenantIdAndType(tenantId, deviceProfile.getName(), pageLink);
pageData.getData().stream().filter(d -> d.getFirmwareId() == null).forEach(updateConsumer);
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
}
private void update(Device device, Firmware firmware, long ts) {
TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId();
List<TsKvEntry> telemetry = new ArrayList<>();
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle())));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion())));
telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save telemetry with target firmware for device!", deviceId);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Failed to save telemetry with target firmware for device!", deviceId, t);
}
});
List<AttributeKvEntry> attributes = new ArrayList<>();
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion())));
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, (long) firmware.getData().array().length)));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum())));
telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save attributes with target firmware!", deviceId);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
}
});
}
private void remove(Device device) {
telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE,
Arrays.asList(FIRMWARE_TITLE, FIRMWARE_VERSION, FIRMWARE_SIZE, FIRMWARE_CHECKSUM_ALGORITHM, FIRMWARE_CHECKSUM),
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target firmware attributes!", device.getId());
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t);
}
});
}
}

27
application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.firmware;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
public interface FirmwareStateService {
void update(Device device, boolean created);
void update(DeviceProfile deviceProfile);
}

8
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -58,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
@ -445,11 +442,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
installScripts.loadSystemWidgets();
}
@Override
public void loadSystemLwm2mResources() throws Exception {
installScripts.loadSystemLwm2mResources();
}
private User createUser(Authority authority,
TenantId tenantId,
CustomerId customerId,

27
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -22,8 +22,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -45,7 +42,6 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Optional;
import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
@ -196,29 +192,6 @@ public class InstallScripts {
}
}
public void loadSystemLwm2mResources() throws Exception {
Path modelsDir = Paths.get(getDataDir(), MODELS_DIR);
if (Files.isDirectory(modelsDir)) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) {
dirStream.forEach(
path -> {
try {
byte[] fileBytes = Files.readAllBytes(path);
TbResource resource = new TbResource();
resource.setFileName(path.getFileName().toString());
resource.setTenantId(TenantId.SYS_TENANT_ID);
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setData(Base64.getEncoder().encodeToString(fileBytes));
resourceService.saveResource(resource);
} catch (Exception e) {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", path.toString()));
}
}
);
}
}
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {

24
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -449,26 +449,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
case "3.2.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource ( " +
"id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY, " +
"created_time bigint NOT NULL, " +
"tenant_id uuid NOT NULL, " +
"title varchar(255) NOT NULL, " +
"resource_type varchar(32) NOT NULL, " +
"resource_key varchar(255) NOT NULL, " +
"search_text varchar(255), " +
"file_name varchar(255) NOT NULL, " +
"data varchar, " +
"CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)" +
");");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
installScripts.loadSystemLwm2mResources();
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:

2
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -33,6 +33,4 @@ public interface SystemDataLoaderService {
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
void loadSystemLwm2mResources() throws Exception;
}

3
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -37,7 +37,8 @@ public enum Resource {
TENANT_PROFILE(EntityType.TENANT_PROFILE),
DEVICE_PROFILE(EntityType.DEVICE_PROFILE),
API_USAGE_STATE(EntityType.API_USAGE_STATE),
TB_RESOURCE(EntityType.TB_RESOURCE);
TB_RESOURCE(EntityType.TB_RESOURCE),
FIRMWARE(EntityType.FIRMWARE);
private final EntityType entityType;

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker);
put(Resource.TB_RESOURCE, tbResourcePermissionChecker);
put(Resource.FIRMWARE, tenantEntityPermissionChecker);
}
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {

65
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -23,16 +23,19 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.firmware.FirmwareCacheWriter;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
@ -40,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileC
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials;
@ -55,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -66,7 +71,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
@ -109,6 +113,8 @@ public class DefaultTransportApiService implements TransportApiService {
private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService;
private final TbResourceService resourceService;
private final FirmwareService firmwareService;
private final FirmwareCacheWriter firmwareCacheWriter;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
@ -117,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService {
RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService, TbResourceService resourceService) {
DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, CacheManager cacheManager, FirmwareCacheWriter firmwareCacheWriter) {
this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache;
this.apiUsageStateService = apiUsageStateService;
@ -130,6 +136,8 @@ public class DefaultTransportApiService implements TransportApiService {
this.dataDecodingEncodingService = dataDecodingEncodingService;
this.deviceProvisionService = deviceProvisionService;
this.resourceService = resourceService;
this.firmwareService = firmwareService;
this.firmwareCacheWriter = firmwareCacheWriter;
}
@Override
@ -166,6 +174,9 @@ public class DefaultTransportApiService implements TransportApiService {
} else if (transportApiRequestMsg.hasResourceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasFirmwareRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getFirmwareRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
}
return Futures.transform(getEmptyTransportApiResponseFuture(),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
@ -313,14 +324,14 @@ public class DefaultTransportApiService implements TransportApiService {
} catch (ProvisionFailedException e) {
return Futures.immediateFuture(getTransportApiResponseMsg(
new DeviceCredentials(),
TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage())));
TransportProtos.ResponseStatus.valueOf(e.getMessage())));
}
return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS),
return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ResponseStatus.SUCCESS),
dbCallbackExecutorService);
}
private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) {
if (!status.equals(ProvisionResponseStatus.SUCCESS)) {
private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ResponseStatus status) {
if (!status.equals(TransportProtos.ResponseStatus.SUCCESS)) {
return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build();
}
TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
@ -438,6 +449,46 @@ public class DefaultTransportApiService implements TransportApiService {
}
}
private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetFirmwareRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
Device device = deviceService.findDeviceById(tenantId, deviceId);
if (device == null) {
return getEmptyTransportApiResponseFuture();
}
FirmwareId firmwareId = device.getFirmwareId();
if (firmwareId == null) {
firmwareId = deviceProfileCache.find(device.getDeviceProfileId()).getFirmwareId();
}
TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder();
if (firmwareId == null) {
builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
} else {
Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId);
if (firmware == null) {
builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
} else {
builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits());
builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits());
builder.setFileName(firmware.getFileName());
builder.setContentType(firmware.getContentType());
firmwareCacheWriter.put(firmwareId.toString(), firmware.getData().array());
}
}
return Futures.immediateFuture(
TransportApiResponseMsg.newBuilder()
.setFirmwareResponseMsg(builder.build())
.build());
}
private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) {
TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId()));
String deviceName = msg.getEndpoint();

8
application/src/main/resources/thingsboard.yml

@ -281,7 +281,7 @@ actors:
tenant:
create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}"
session:
max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:1}"
max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:2}"
sync:
# Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds
timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}"
@ -365,6 +365,9 @@ caffeine:
tokensOutdatageTime:
timeToLiveInMinutes: 20000
maxSize: 10000
firmwares:
timeToLiveInMinutes: 1440
maxSize: 100
redis:
# standalone or cluster
@ -437,6 +440,9 @@ spring.resources.chain:
content:
enabled: "true"
spring.servlet.multipart.max-file-size: "50MB"
spring.servlet.multipart.max-request-size: "50MB"
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
spring.jpa.properties.hibernate.order_by.default_null_ordering: "last"

302
application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java

@ -0,0 +1,302 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseFirmwareControllerTest extends AbstractControllerTest {
private IdComparator<FirmwareInfo> idComparator = new IdComparator<>();
public static final String TITLE = "My firmware";
private static final String FILE_NAME = "filename.txt";
private static final String VERSION = "v1.0";
private static final String CONTENT_TYPE = "text/plain";
private static final String CHECKSUM_ALGORITHM = "sha256";
private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveFirmware() throws Exception {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
Assert.assertNotNull(savedFirmwareInfo);
Assert.assertNotNull(savedFirmwareInfo.getId());
Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedFirmwareInfo.getTenantId());
Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion());
savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
save(savedFirmwareInfo);
FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class);
Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
}
@Test
public void testSaveFirmwareData() throws Exception {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
Assert.assertNotNull(savedFirmwareInfo);
Assert.assertNotNull(savedFirmwareInfo.getId());
Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedFirmwareInfo.getTenantId());
Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion());
savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
save(savedFirmwareInfo);
FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class);
Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
Assert.assertEquals(FILE_NAME, savedFirmware.getFileName());
Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType());
}
@Test
public void testUpdateFirmwareFromDifferentTenant() throws Exception {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
loginDifferentTenant();
doPost("/api/firmware", savedFirmwareInfo, FirmwareInfo.class, status().isForbidden());
deleteDifferentTenant();
}
@Test
public void testFindFirmwareInfoById() throws Exception {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
FirmwareInfo foundFirmware = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class);
Assert.assertNotNull(foundFirmware);
Assert.assertEquals(savedFirmwareInfo, foundFirmware);
}
@Test
public void testFindFirmwareById() throws Exception {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
Firmware foundFirmware = doGet("/api/firmware/" + savedFirmwareInfo.getId().getId().toString(), Firmware.class);
Assert.assertNotNull(foundFirmware);
Assert.assertEquals(savedFirmware, foundFirmware);
}
@Test
public void testDeleteFirmware() throws Exception {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
doDelete("/api/firmware/" + savedFirmwareInfo.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testFindTenantFirmwares() throws Exception {
List<FirmwareInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION + i);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
if (i > 100) {
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
firmwares.add(new FirmwareInfo(savedFirmware));
} else {
firmwares.add(savedFirmwareInfo);
}
}
List<FirmwareInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<FirmwareInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/firmwares?",
new TypeReference<>() {
}, pageLink);
loadedFirmwares.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
}
@Test
public void testFindTenantFirmwaresByHasData() throws Exception {
List<FirmwareInfo> firmwaresWithData = new ArrayList<>();
List<FirmwareInfo> firmwaresWithoutData = new ArrayList<>();
for (int i = 0; i < 165; i++) {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION + i);
FirmwareInfo savedFirmwareInfo = save(firmwareInfo);
if (i > 100) {
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
firmwaresWithData.add(new FirmwareInfo(savedFirmware));
} else {
firmwaresWithoutData.add(savedFirmwareInfo);
}
}
List<FirmwareInfo> loadedFirmwaresWithData = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<FirmwareInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/firmwares/true?",
new TypeReference<>() {
}, pageLink);
loadedFirmwaresWithData.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
List<FirmwareInfo> loadedFirmwaresWithoutData = new ArrayList<>();
pageLink = new PageLink(24);
do {
pageData = doGetTypedWithPageLink("/api/firmwares/false?",
new TypeReference<>() {
}, pageLink);
loadedFirmwaresWithoutData.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(firmwaresWithData, idComparator);
Collections.sort(firmwaresWithoutData, idComparator);
Collections.sort(loadedFirmwaresWithData, idComparator);
Collections.sort(loadedFirmwaresWithoutData, idComparator);
Assert.assertEquals(firmwaresWithData, loadedFirmwaresWithData);
Assert.assertEquals(firmwaresWithoutData, loadedFirmwaresWithoutData);
}
private FirmwareInfo save(FirmwareInfo firmwareInfo) throws Exception {
return doPost("/api/firmware", firmwareInfo, FirmwareInfo.class);
}
protected Firmware savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
postRequest.file(content);
setJwtToken(postRequest);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), Firmware.class);
}
}

23
application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseFirmwareControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class FirmwareControllerSqlTest extends BaseFirmwareControllerTest {
}

33
common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
public abstract class AbstractRedisFirmwareCache {
protected final RedisConnectionFactory redisConnectionFactory;
protected AbstractRedisFirmwareCache(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
protected byte[] toFirmwareCacheKey(String key) {
return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes();
}
}

60
common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
@Service
@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')")
public class CaffeineFirmwareCacheReader implements FirmwareCacheReader {
private final CacheManager cacheManager;
public CaffeineFirmwareCacheReader(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public byte[] get(String key) {
return get(key, 0, 0);
}
@Override
public byte[] get(String key, int chunkSize, int chunk) {
byte[] data = cacheManager.getCache(FIRMWARE_CACHE).get(key, byte[].class);
if (chunkSize < 1) {
return data;
}
if (data != null && data.length > 0) {
int startIndex = chunkSize * chunk;
int size = Math.min(data.length - startIndex, chunkSize);
if (startIndex < data.length && size > 0) {
byte[] result = new byte[size];
System.arraycopy(data, startIndex, result, 0, size);
return result;
}
}
return new byte[0];
}
}

38
common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
@Service
@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')")
public class CaffeineFirmwareCacheWriter implements FirmwareCacheWriter {
private final CacheManager cacheManager;
public CaffeineFirmwareCacheWriter(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public void put(String key, byte[] value) {
cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value);
}
}

22
common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
public interface FirmwareCacheReader {
byte[] get(String key);
byte[] get(String key, int chunkSize, int chunk);
}

20
common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
public interface FirmwareCacheWriter {
void put(String key, byte[] value);
}

49
common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
@Service
@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && '${cache.type:null}'=='redis'")
public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache implements FirmwareCacheReader {
public RedisFirmwareCacheReader(RedisConnectionFactory redisConnectionFactory) {
super(redisConnectionFactory);
}
@Override
public byte[] get(String key) {
return get(key, 0, 0);
}
@Override
public byte[] get(String key, int chunkSize, int chunk) {
try (RedisConnection connection = redisConnectionFactory.getConnection()) {
if (chunkSize == 0) {
return connection.get(toFirmwareCacheKey(key));
}
int startIndex = chunkSize * chunk;
int endIndex = startIndex + chunkSize - 1;
return connection.getRange(toFirmwareCacheKey(key), startIndex, endIndex);
}
}
}

38
common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.cache.firmware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
@Service
@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && '${cache.type:null}'=='redis'")
public class RedisFirmwareCacheWriter extends AbstractRedisFirmwareCache implements FirmwareCacheWriter {
public RedisFirmwareCacheWriter(RedisConnectionFactory redisConnectionFactory) {
super(redisConnectionFactory);
}
@Override
public void put(String key, byte[] value) {
try (RedisConnection connection = redisConnectionFactory.getConnection()) {
connection.set(toFirmwareCacheKey(key), value);
}
}
}

42
common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.firmware;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
public interface FirmwareService {
FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo);
Firmware saveFirmware(Firmware firmware);
Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId);
FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId);
PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink);
PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink);
void deleteFirmware(TenantId tenantId, FirmwareId firmwareId);
void deleteFirmwaresByTenantId(TenantId tenantId);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java

@ -32,7 +32,7 @@ import java.util.List;
public interface TbResourceService {
TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException;
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -28,4 +28,5 @@ public class CacheConstants {
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";
public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
public static final String FIRMWARE_CACHE = "firmwares";
}

15
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -91,4 +91,19 @@ public class DataConstants {
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
//firmware
//telemetry
public static final String CURRENT_FIRMWARE_TITLE = "cur_fw_title";
public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version";
public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
public static final String CURRENT_FIRMWARE_STATE = "cur_fw_state";
//attributes
//telemetry
public static final String FIRMWARE_TITLE = "fw_title";
public static final String FIRMWARE_VERSION = "fw_version";
public static final String FIRMWARE_SIZE = "fw_size";
public static final String FIRMWARE_CHECKSUM = "fw_checksum";
public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm";
}

14
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@ -48,6 +49,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
@JsonIgnore
private byte[] deviceDataBytes;
private FirmwareId firmwareId;
public Device() {
super();
}
@ -65,6 +68,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.label = device.getLabel();
this.deviceProfileId = device.getDeviceProfileId();
this.setDeviceData(device.getDeviceData());
this.firmwareId = device.getFirmwareId();
}
public Device updateDevice(Device device) {
@ -159,6 +163,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
return getName();
}
public FirmwareId getFirmwareId() {
return firmwareId;
}
public void setFirmwareId(FirmwareId firmwareId) {
this.firmwareId = firmwareId;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@ -175,6 +187,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
builder.append(", deviceProfileId=");
builder.append(deviceProfileId);
builder.append(", deviceData=");
builder.append(firmwareId);
builder.append(", firmwareId=");
builder.append(deviceData);
builder.append(", additionalInfo=");
builder.append(getAdditionalInfo());

3
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@ -56,6 +57,8 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
@NoXss
private String provisionDeviceKey;
private FirmwareId firmwareId;
public DeviceProfile() {
super();
}

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
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;
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;
}

56
common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.FirmwareId;
import java.nio.ByteBuffer;
@Data
@EqualsAndHashCode(callSuper = true)
public class Firmware extends FirmwareInfo {
private static final long serialVersionUID = 3091601761339422546L;
private String fileName;
private String contentType;
private String checksumAlgorithm;
private String checksum;
private transient ByteBuffer data;
public Firmware() {
super();
}
public Firmware(FirmwareId id) {
super(id);
}
public Firmware(Firmware firmware) {
super(firmware);
this.fileName = firmware.getFileName();
this.contentType = firmware.getContentType();
this.data = firmware.getData();
this.checksumAlgorithm = firmware.getChecksumAlgorithm();
this.checksum = firmware.getChecksum();
}
}

56
common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo<FirmwareId> implements HasTenantId {
private static final long serialVersionUID = 3168391583570815419L;
private TenantId tenantId;
private String title;
private String version;
private boolean hasData;
public FirmwareInfo() {
super();
}
public FirmwareInfo(FirmwareId id) {
super(id);
}
public FirmwareInfo(FirmwareInfo firmwareInfo) {
super(firmwareInfo);
this.tenantId = firmwareInfo.getTenantId();
this.title = firmwareInfo.getTitle();
this.version = firmwareInfo.getVersion();
this.hasData = firmwareInfo.isHasData();
}
@Override
public String getSearchText() {
return title;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.id.TenantId;
@EqualsAndHashCode(callSuper = true)
public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
private static final long serialVersionUID = 7282664529021651736L;
private TenantId tenantId;
private String title;
private ResourceType resourceType;

10
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java

@ -30,6 +30,9 @@ public class MqttTopics {
private static final String CLAIM = "/claim";
private static final String SUB_TOPIC = "+";
private static final String PROVISION = "/provision";
private static final String FIRMWARE = "/fw";
private static final String CHUNK = "/chunk/";
private static final String ERROR = "/error";
private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE;
private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST;
@ -69,6 +72,13 @@ public class MqttTopics {
public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST;
public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE;
// v2 topics
public static final String BASE_DEVICE_API_TOPIC_V2 = "v2";
public static final String DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/";
public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + SUB_TOPIC + CHUNK + SUB_TOPIC;
public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR;
private MqttTopics() {
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -70,6 +70,8 @@ public class EntityIdFactory {
return new ApiUsageStateId(uuid);
case TB_RESOURCE:
return new TbResourceId(uuid);
case FIRMWARE:
return new FirmwareId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}

44
common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java

@ -0,0 +1,44 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class FirmwareId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public FirmwareId(@JsonProperty("id") UUID id) {
super(id);
}
public static FirmwareId fromString(String firmwareId) {
return new FirmwareId(UUID.fromString(firmwareId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.FIRMWARE;
}
}

22
common/queue/src/main/proto/queue.proto

@ -336,17 +336,33 @@ message ProvisionDeviceCredentialsMsg {
}
message ProvisionDeviceResponseMsg {
ProvisionResponseStatus status = 1;
ResponseStatus status = 1;
CredentialsType credentialsType = 2;
string credentialsValue = 3;
}
enum ProvisionResponseStatus {
enum ResponseStatus {
UNKNOWN = 0;
SUCCESS = 1;
NOT_FOUND = 2;
FAILURE = 3;
}
message GetFirmwareRequestMsg {
int64 deviceIdMSB = 1;
int64 deviceIdLSB = 2;
int64 tenantIdMSB = 3;
int64 tenantIdLSB = 4;
}
message GetFirmwareResponseMsg {
ResponseStatus responseStatus = 1;
int64 firmwareIdMSB = 2;
int64 firmwareIdLSB = 3;
string contentType = 4;
string fileName = 5;
}
//Used to report session state to tb-Service and persist this state in the cache on the tb-Service level.
message SubscriptionInfoProto {
int64 lastActivityTime = 1;
@ -552,6 +568,7 @@ message TransportApiRequestMsg {
ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
GetResourceRequestMsg resourceRequestMsg = 9;
GetFirmwareRequestMsg firmwareRequestMsg = 10;
}
/* Response from ThingsBoard Core Service to Transport Service */
@ -562,6 +579,7 @@ message TransportApiResponseMsg {
ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
LwM2MResponseMsg lwM2MResponseMsg = 6;
GetResourceResponseMsg resourceResponseMsg = 7;
GetFirmwareResponseMsg firmwareResponseMsg = 8;
}
/* Messages that are handled by ThingsBoard Core Service */

2
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -398,7 +398,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
@Override
public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) {
CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED;
if (!msg.getStatus().equals(TransportProtos.ProvisionResponseStatus.SUCCESS)) {
if (!msg.getStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) {
responseCode = CoAP.ResponseCode.BAD_REQUEST;
}
if (payloadType.equals(TransportPayloadType.JSON)) {

65
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -20,7 +20,10 @@ import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
@ -41,7 +44,6 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
@ -53,7 +55,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs
import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import javax.servlet.http.HttpServletRequest;
@ -204,6 +205,23 @@ public class DeviceApiController {
return responseWriter;
}
@RequestMapping(value = "/{deviceToken}/firmware", method = RequestMethod.GET)
public DeferredResult<ResponseEntity> getFirmware(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "chunkSize", required = false, defaultValue = "0") int chunkSize,
@RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build();
transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, chunkSize, chunk));
}));
return responseWriter;
}
@RequestMapping(value = "/provision", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
@ -258,6 +276,41 @@ public class DeviceApiController {
}
}
private class GetFirmwareCallback implements TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> {
private final DeferredResult<ResponseEntity> responseWriter;
private final int chuckSize;
private final int chuck;
GetFirmwareCallback(DeferredResult<ResponseEntity> responseWriter, int chuckSize, int chuck) {
this.responseWriter = responseWriter;
this.chuckSize = chuckSize;
this.chuck = chuck;
}
@Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg) {
if (!TransportProtos.ResponseStatus.SUCCESS.equals(firmwareResponseMsg.getResponseStatus())) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND));
} else {
String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString();
ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareCacheReader().get(firmwareId, chuckSize, chuck));
ResponseEntity<ByteArrayResource> response = ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmwareResponseMsg.getFileName())
.header("x-filename", firmwareResponseMsg.getFileName())
.contentLength(resource.contentLength())
.contentType(parseMediaType(firmwareResponseMsg.getContentType()))
.body(resource);
responseWriter.setResult(response);
}
}
@Override
public void onError(Throwable e) {
log.warn("Failed to process request", e);
responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
}
private static class SessionCloseOnErrorCallback implements TransportServiceCallback<Void> {
private final TransportService transportService;
private final SessionInfoProto sessionInfo;
@ -338,4 +391,12 @@ public class DeviceApiController {
.build(), TransportServiceCallback.EMPTY);
}
private static MediaType parseMediaType(String contentType) {
try {
return MediaType.parseMediaType(contentType);
} catch (Exception e) {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java

@ -29,7 +29,6 @@ import redis.clients.jedis.ScanResult;
import java.util.Collection;
import java.util.LinkedList;
@Service
public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
private static final String SEC_EP = "SEC#EP#";

112
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -40,12 +40,14 @@ import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.common.transport.SessionMsgListener;
@ -69,10 +71,10 @@ import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher;
import org.thingsboard.server.common.transport.util.SslUtil;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -80,7 +82,10 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.amazonaws.util.StringUtils.UTF8;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED;
import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK;
@ -99,6 +104,10 @@ import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE;
@Slf4j
public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>>, SessionMsgListener {
private static final Pattern FW_PATTERN = Pattern.compile("v2/fw/request/(?<requestId>\\d+)/chunk/(?<chunk>\\d+)");
private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE";
private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE;
private final UUID sessionId;
@ -112,6 +121,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private volatile InetSocketAddress address;
private volatile GatewaySessionHandler gatewaySessionHandler;
private final ConcurrentHashMap<String, String> fwSessions;
private final ConcurrentHashMap<String, Integer> fwChunkSizes;
MqttTransportHandler(MqttTransportContext context, SslHandler sslHandler) {
this.sessionId = UUID.randomUUID();
this.context = context;
@ -120,6 +132,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
this.sslHandler = sslHandler;
this.mqttQoSMap = new ConcurrentHashMap<>();
this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context);
this.fwSessions = new ConcurrentHashMap<>();
this.fwChunkSizes = new ConcurrentHashMap<>();
}
@Override
@ -280,6 +294,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) {
try {
Matcher fwMatcher;
MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
@ -299,6 +314,38 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) {
TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg));
} else if ((fwMatcher = FW_PATTERN.matcher(topicName)).find()) {
String payload = mqttMsg.content().toString(UTF8);
int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0;
String requestId = fwMatcher.group("requestId");
int chunk = Integer.parseInt(fwMatcher.group("chunk"));
if (chunkSize > 0) {
this.fwChunkSizes.put(requestId, chunkSize);
} else {
chunkSize = fwChunkSizes.getOrDefault(requestId, 0);
}
if (chunkSize > context.getMaxPayloadSize()) {
sendFirmwareError(ctx, PAYLOAD_TOO_LARGE);
return;
}
String firmwareId = fwSessions.get(requestId);
if (firmwareId != null) {
sendFirmware(ctx, mqttMsg.variableHeader().packetId(), firmwareId, requestId, chunkSize, chunk);
} else {
TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo();
TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.build();
transportService.process(deviceSessionCtx.getSessionInfo(), getFirmwareRequestMsg,
new FirmwareCallback(ctx, mqttMsg.variableHeader().packetId(), getFirmwareRequestMsg, requestId, chunkSize, chunk));
}
} else {
transportService.reportActivity(deviceSessionCtx.getSessionInfo());
ack(ctx, msgId);
@ -366,6 +413,65 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
}
private class FirmwareCallback implements TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> {
private final ChannelHandlerContext ctx;
private final int msgId;
private final TransportProtos.GetFirmwareRequestMsg msg;
private final String requestId;
private final int chunkSize;
private final int chunk;
FirmwareCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetFirmwareRequestMsg msg, String requestId, int chunkSize, int chunk) {
this.ctx = ctx;
this.msgId = msgId;
this.msg = msg;
this.requestId = requestId;
this.chunkSize = chunkSize;
this.chunk = chunk;
}
@Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
FirmwareId firmwareId = new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB()));
fwSessions.put(requestId, firmwareId.toString());
sendFirmware(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk);
} else {
sendFirmwareError(ctx, response.getResponseStatus().toString());
}
}
@Override
public void onError(Throwable e) {
log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
processDisconnect(ctx);
}
}
private void sendFirmware(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk) {
log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
ack(ctx, msgId);
try {
byte[] firmwareChunk = context.getFirmwareCacheReader().get(firmwareId, chunkSize, chunk);
deviceSessionCtx.getPayloadAdaptor()
.convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk)
.ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
if (firmwareChunk != null && chunkSize != firmwareChunk.length) {
scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.trace("[{}] Failed to send firmware response!", sessionId, e);
}
}
private void sendFirmwareError(ChannelHandlerContext ctx, String error) {
log.warn("[{}] {}", sessionId, error);
deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx
.getPayloadAdaptor()
.createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes()));
processDisconnect(ctx);
}
private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) {
if (!checkConnected(ctx, mqttMsg)) {
return;
@ -396,6 +502,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
case MqttTopics.GATEWAY_RPC_TOPIC:
case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC:
case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC:
case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC:
registerSubQoS(topic, grantedQoSList, reqQoS);
break;
default:

10
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -21,8 +21,6 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageType;
@ -31,10 +29,10 @@ import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext;
import java.nio.charset.Charset;
@ -55,7 +53,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
protected static final Charset UTF8 = StandardCharsets.UTF_8;
private static final Gson GSON = new Gson();
private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
@Override
public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
@ -153,6 +150,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse)));
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/chunk/" + chunk, firmwareChunk));
}
public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
String payload = validatePayload(sessionId, payloadData, false);
try {

18
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java

@ -15,8 +15,14 @@
*/
package org.thingsboard.server.transport.mqtt.adaptors;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageType;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
@ -39,6 +45,8 @@ import java.util.Optional;
*/
public interface MqttTransportAdaptor {
ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException;
@ -69,4 +77,14 @@ public interface MqttTransportAdaptor {
Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException;
Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException;
default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
ByteBuf payload = ALLOCATOR.buffer();
payload.writeBytes(payloadInBytes);
return new MqttPublishMessage(mqttFixedHeader, header, payload);
}
}

22
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java

@ -21,13 +21,8 @@ import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageType;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@ -46,8 +41,6 @@ import java.util.Optional;
@Slf4j
public class ProtoMqttAdaptor implements MqttTransportAdaptor {
private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false);
@Override
public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
@ -143,7 +136,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
}
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest)));
@ -164,6 +156,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray()));
}
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk));
}
@Override
public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException {
if (!StringUtils.isEmpty(responseMsg.getError())) {
@ -202,15 +199,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
return bytes;
}
private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadBytes) {
MqttFixedHeader mqttFixedHeader =
new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
ByteBuf payload = ALLOCATOR.buffer();
payload.writeBytes(payloadBytes);
return new MqttPublishMessage(mqttFixedHeader, header, payload);
}
private int getRequestId(String topicName, String topic) {
return Integer.parseInt(topicName.substring(topic.length()));
}

8
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java

@ -20,10 +20,9 @@ import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.firmware.FirmwareCacheReader;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.util.TbTransportComponent;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -51,6 +50,11 @@ public abstract class TransportContext {
@Getter
private ExecutorService executor;
@Getter
@Autowired
private FirmwareCacheReader firmwareCacheReader;
@PostConstruct
public void init() {
executor = Executors.newWorkStealingPool(50);

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetResourceResponseMsg;
@ -98,6 +100,8 @@ public interface TransportService {
void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback<GetFirmwareResponseMsg> callback);
SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);

12
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -44,7 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;
import org.thingsboard.server.gen.transport.TransportProtos.ResponseStatus;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg;
@ -423,12 +423,12 @@ public class JsonConverter {
private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) {
JsonObject result = new JsonObject();
if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) {
if (payload.getStatus() == ResponseStatus.NOT_FOUND) {
result.addProperty("errorMsg", "Provision data was not found!");
result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name());
} else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) {
result.addProperty("status", ResponseStatus.NOT_FOUND.name());
} else if (payload.getStatus() == TransportProtos.ResponseStatus.FAILURE) {
result.addProperty("errorMsg", "Failed to provision device!");
result.addProperty("status", ProvisionResponseStatus.FAILURE.name());
result.addProperty("status", ResponseStatus.FAILURE.name());
} else {
if (toGateway) {
result.addProperty("id", requestId);
@ -445,7 +445,7 @@ public class JsonConverter {
break;
}
result.addProperty("credentialsType", payload.getCredentialsType().name());
result.addProperty("status", ProvisionResponseStatus.SUCCESS.name());
result.addProperty("status", ResponseStatus.SUCCESS.name());
}
return result;
}

18
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -377,7 +377,6 @@ public class DefaultTransportService implements TransportService {
AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor);
}
@Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
if (log.isTraceEnabled()) {
@ -529,6 +528,19 @@ public class DefaultTransportService implements TransportService {
}
}
@Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetFirmwareRequestMsg msg, TransportServiceCallback<TransportProtos.GetFirmwareResponseMsg> callback) {
if (checkLimits(sessionInfo, msg, callback)) {
TbProtoQueueMsg<TransportProtos.TransportApiRequestMsg> protoMsg =
new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setFirmwareRequestMsg(msg).build());
AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), response -> {
TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg = response.getValue().getFirmwareResponseMsg();
callback.onSuccess(firmwareResponseMsg);
}, callback::onError, transportCallbackExecutor);
}
}
@Override
public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
return reportActivityInternal(sessionInfo);
@ -608,11 +620,11 @@ public class DefaultTransportService implements TransportService {
sessions.remove(toSessionId(sessionInfo));
}
private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback) {
return checkLimits(sessionInfo, msg, callback, 0);
}
private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints) {
private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback, int dataPoints) {
if (log.isTraceEnabled()) {
log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
}

14
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
@ -59,6 +60,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
@ -107,6 +109,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
@Autowired
private CacheManager cacheManager;
@Autowired
private FirmwareService firmwareService;
private final Lock findOrCreateLock = new ReentrantLock();
@Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}")
@ -389,6 +394,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
}
}
if (deviceProfile.getFirmwareId() != null) {
Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId());
if (firmware == null) {
throw new DataValidationException("Can't assign non-existent firmware!");
}
if (firmware.getData() == null) {
throw new DataValidationException("Can't assign firmware with empty data!");
}
}
}
@Override

15
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
@ -68,6 +69,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -128,6 +130,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Autowired
private FirmwareService firmwareService;
@Override
public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
log.trace("Executing findDeviceInfoById [{}]", deviceId);
@ -598,6 +603,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
throw new DataValidationException("Can't assign device to customer from different tenant!");
}
}
if (device.getFirmwareId() != null) {
Firmware firmware = firmwareService.findFirmwareById(tenantId, device.getFirmwareId());
if (firmware == null) {
throw new DataValidationException("Can't assign non-existent firmware!");
}
if (firmware.getData() == null) {
throw new DataValidationException("Can't assign firmware with empty data!");
}
}
}
};

317
dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java

@ -0,0 +1,317 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.firmware;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Optional;
import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@Slf4j
public class BaseFirmwareService implements FirmwareService {
public static final String INCORRECT_FIRMWARE_ID = "Incorrect firmwareId ";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private final TenantDao tenantDao;
private final FirmwareDao firmwareDao;
private final FirmwareInfoDao firmwareInfoDao;
private final CacheManager cacheManager;
public BaseFirmwareService(TenantDao tenantDao, FirmwareDao firmwareDao, FirmwareInfoDao firmwareInfoDao, CacheManager cacheManager) {
this.tenantDao = tenantDao;
this.firmwareDao = firmwareDao;
this.firmwareInfoDao = firmwareInfoDao;
this.cacheManager = cacheManager;
}
@Override
public FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo) {
log.trace("Executing saveFirmwareInfo [{}]", firmwareInfo);
firmwareInfoValidator.validate(firmwareInfo, FirmwareInfo::getTenantId);
try {
FirmwareId firmwareId = firmwareInfo.getId();
if (firmwareId != null) {
cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString());
}
return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo);
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) {
throw new DataValidationException("Firmware with such title and version already exists!");
} else {
throw t;
}
}
}
@Override
public Firmware saveFirmware(Firmware firmware) {
log.trace("Executing saveFirmware [{}]", firmware);
firmwareValidator.validate(firmware, FirmwareInfo::getTenantId);
try {
FirmwareId firmwareId = firmware.getId();
if (firmwareId != null) {
cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString());
}
return firmwareDao.save(firmware.getTenantId(), firmware);
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) {
throw new DataValidationException("Firmware with such title and version already exists!");
} else {
throw t;
}
}
}
@Override
public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) {
log.trace("Executing findFirmwareById [{}]", firmwareId);
validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId);
return firmwareDao.findById(tenantId, firmwareId.getId());
}
@Override
public FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId) {
log.trace("Executing findFirmwareInfoById [{}]", firmwareId);
validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId);
return firmwareInfoDao.findById(tenantId, firmwareId.getId());
}
@Override
public PageData<FirmwareInfo> findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findTenantFirmwaresByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validatePageLink(pageLink);
return firmwareInfoDao.findFirmwareInfoByTenantId(tenantId, pageLink);
}
@Override
public PageData<FirmwareInfo> findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) {
log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validatePageLink(pageLink);
return firmwareInfoDao.findFirmwareInfoByTenantIdAndHasData(tenantId, hasData, pageLink);
}
@Override
public void deleteFirmware(TenantId tenantId, FirmwareId firmwareId) {
log.trace("Executing deleteFirmware [{}]", firmwareId);
validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId);
try {
Cache cache = cacheManager.getCache(FIRMWARE_CACHE);
cache.evict(Collections.singletonList(firmwareId));
firmwareDao.removeById(tenantId, firmwareId.getId());
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device")) {
throw new DataValidationException("The firmware referenced by the devices cannot be deleted!");
} else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device_profile")) {
throw new DataValidationException("The firmware referenced by the device profile cannot be deleted!");
} else {
throw t;
}
}
}
@Override
public void deleteFirmwaresByTenantId(TenantId tenantId) {
log.trace("Executing deleteFirmwaresByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
tenantFirmwareRemover.removeEntities(tenantId, tenantId);
}
private DataValidator<FirmwareInfo> firmwareInfoValidator = new DataValidator<>() {
@Override
protected void validateDataImpl(TenantId tenantId, FirmwareInfo firmware) {
if (firmware.getTenantId() == null) {
throw new DataValidationException("Firmware should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Firmware is referencing to non-existent tenant!");
}
}
if (StringUtils.isEmpty(firmware.getTitle())) {
throw new DataValidationException("Firmware title should be specified!");
}
if (StringUtils.isEmpty(firmware.getVersion())) {
throw new DataValidationException("Firmware version should be specified!");
}
}
@Override
protected void validateUpdate(TenantId tenantId, FirmwareInfo firmware) {
FirmwareInfo firmwareOld = firmwareInfoDao.findById(tenantId, firmware.getUuidId());
if (!firmwareOld.getTitle().equals(firmware.getTitle())) {
throw new DataValidationException("Updating firmware title is prohibited!");
}
if (!firmwareOld.getVersion().equals(firmware.getVersion())) {
throw new DataValidationException("Updating firmware version is prohibited!");
}
}
};
private DataValidator<Firmware> firmwareValidator = new DataValidator<>() {
@Override
protected void validateDataImpl(TenantId tenantId, Firmware firmware) {
if (firmware.getTenantId() == null) {
throw new DataValidationException("Firmware should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Firmware is referencing to non-existent tenant!");
}
}
if (StringUtils.isEmpty(firmware.getTitle())) {
throw new DataValidationException("Firmware title should be specified!");
}
if (StringUtils.isEmpty(firmware.getVersion())) {
throw new DataValidationException("Firmware version should be specified!");
}
if (StringUtils.isEmpty(firmware.getFileName())) {
throw new DataValidationException("Firmware file name should be specified!");
}
if (StringUtils.isEmpty(firmware.getContentType())) {
throw new DataValidationException("Firmware content type should be specified!");
}
ByteBuffer data = firmware.getData();
if (data == null || !data.hasArray() || data.array().length == 0) {
throw new DataValidationException("Firmware data should be specified!");
}
if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) {
throw new DataValidationException("Firmware checksum algorithm should be specified!");
}
if (StringUtils.isEmpty(firmware.getChecksum())) {
throw new DataValidationException("Firmware checksum should be specified!");
}
HashFunction hashFunction;
switch (firmware.getChecksumAlgorithm()) {
case "sha256":
hashFunction = Hashing.sha256();
break;
case "md5":
hashFunction = Hashing.md5();
break;
case "crc32":
hashFunction = Hashing.crc32();
break;
default:
throw new DataValidationException("Unknown checksum algorithm!");
}
String currentChecksum = hashFunction.hashBytes(data.array()).toString();
if (!currentChecksum.equals(firmware.getChecksum())) {
throw new DataValidationException("Wrong firmware file!");
}
}
@Override
protected void validateUpdate(TenantId tenantId, Firmware firmware) {
Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId());
if (!firmwareOld.getTitle().equals(firmware.getTitle())) {
throw new DataValidationException("Updating firmware title is prohibited!");
}
if (!firmwareOld.getVersion().equals(firmware.getVersion())) {
throw new DataValidationException("Updating firmware version is prohibited!");
}
if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) {
throw new DataValidationException("Updating firmware file name is prohibited!");
}
if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) {
throw new DataValidationException("Updating firmware content type is prohibited!");
}
if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) {
throw new DataValidationException("Updating firmware content type is prohibited!");
}
if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) {
throw new DataValidationException("Updating firmware content type is prohibited!");
}
if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) {
throw new DataValidationException("Updating firmware data is prohibited!");
}
}
};
private PaginatedRemover<TenantId, FirmwareInfo> tenantFirmwareRemover =
new PaginatedRemover<>() {
@Override
protected PageData<FirmwareInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, FirmwareInfo entity) {
deleteFirmware(tenantId, entity.getId());
}
};
protected Optional<ConstraintViolationException> extractConstraintViolationException(Exception t) {
if (t instanceof ConstraintViolationException) {
return Optional.of((ConstraintViolationException) t);
} else if (t.getCause() instanceof ConstraintViolationException) {
return Optional.of((ConstraintViolationException) (t.getCause()));
} else {
return Optional.empty();
}
}
}

23
dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.firmware;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.dao.Dao;
public interface FirmwareDao extends Dao<Firmware> {
}

32
dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.UUID;
public interface FirmwareInfoDao extends Dao<FirmwareInfo> {
PageData<FirmwareInfo> findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink);
PageData<FirmwareInfo> findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink);
}

18
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -153,6 +153,7 @@ public class ModelConstants {
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_DEVICE_PROFILE_ID_PROPERTY = "device_profile_id";
public static final String DEVICE_DEVICE_DATA_PROPERTY = "device_data";
public static final String DEVICE_FIRMWARE_ID_PROPERTY = "firmware_id";
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
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 {
public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key";
public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id";
/**
* Cassandra entityView constants.
@ -467,6 +469,22 @@ public class ModelConstants {
public static final String RESOURCE_FILE_NAME_COLUMN = "file_name";
public static final String RESOURCE_DATA_COLUMN = "data";
/**
* Firmware constants.
*/
public static final String FIRMWARE_TABLE_NAME = "firmware";
public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN;
public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY;
public static final String FIRMWARE_VERSION_COLUMN = "version";
public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name";
public static final String FIRMWARE_CONTENT_TYPE_COLUMN = "content_type";
public static final String FIRMWARE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm";
public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum";
public static final String FIRMWARE_DATA_COLUMN = "data";
public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY;
public static final String FIRMWARE_HAS_DATA_PROPERTY = "has_data";
/**
* Cassandra attributes and timeseries constants.
*/

11
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
@ -73,6 +74,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
@Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_ID_PROPERTY, columnDefinition = "uuid")
private UUID deviceProfileId;
@Column(name = ModelConstants.DEVICE_FIRMWARE_ID_PROPERTY, columnDefinition = "uuid")
private UUID firmwareId;
@Type(type = "jsonb")
@Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb")
private JsonNode deviceData;
@ -95,6 +99,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
if (device.getDeviceProfileId() != null) {
this.deviceProfileId = device.getDeviceProfileId().getId();
}
if (device.getFirmwareId() != null) {
this.firmwareId = device.getFirmwareId().getId();
}
this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class);
this.name = device.getName();
this.type = device.getType();
@ -114,6 +121,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
this.label = deviceEntity.getLabel();
this.searchText = deviceEntity.getSearchText();
this.additionalInfo = deviceEntity.getAdditionalInfo();
this.firmwareId = deviceEntity.getFirmwareId();
}
@Override
@ -138,6 +146,9 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
if (deviceProfileId != null) {
device.setDeviceProfileId(new DeviceProfileId(deviceProfileId));
}
if (firmwareId != null) {
device.setFirmwareId(new FirmwareId(firmwareId));
}
device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class));
device.setName(name);
device.setType(type);

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -89,6 +90,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
@Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY)
private String provisionDeviceKey;
@Column(name=ModelConstants.DEVICE_PROFILE_FIRMWARE_ID_PROPERTY)
private UUID firmwareId;
public DeviceProfileEntity() {
super();
}
@ -113,6 +117,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
}
this.defaultQueueName = deviceProfile.getDefaultQueueName();
this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();
if (deviceProfile.getFirmwareId() != null) {
this.firmwareId = deviceProfile.getFirmwareId().getId();
}
}
@Override
@ -148,6 +155,11 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
}
deviceProfile.setDefaultQueueName(defaultQueueName);
deviceProfile.setProvisionDeviceKey(provisionDeviceKey);
if (firmwareId != null) {
deviceProfile.setFirmwareId(new FirmwareId(firmwareId));
}
return deviceProfile;
}
}

132
dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java

@ -0,0 +1,132 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.nio.ByteBuffer;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ALGORITHM_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = FIRMWARE_TABLE_NAME)
public class FirmwareEntity extends BaseSqlEntity<Firmware> implements SearchTextEntity<Firmware> {
@Column(name = FIRMWARE_TENANT_ID_COLUMN)
private UUID tenantId;
@Column(name = FIRMWARE_TITLE_COLUMN)
private String title;
@Column(name = FIRMWARE_VERSION_COLUMN)
private String version;
@Column(name = FIRMWARE_FILE_NAME_COLUMN)
private String fileName;
@Column(name = FIRMWARE_CONTENT_TYPE_COLUMN)
private String contentType;
@Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN)
private String checksumAlgorithm;
@Column(name = FIRMWARE_CHECKSUM_COLUMN)
private String checksum;
@Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY")
private byte[] data;
@Type(type = "json")
@Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN)
private JsonNode additionalInfo;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
public FirmwareEntity() {
super();
}
public FirmwareEntity(Firmware firmware) {
this.createdTime = firmware.getCreatedTime();
this.setUuid(firmware.getUuidId());
this.tenantId = firmware.getTenantId().getId();
this.title = firmware.getTitle();
this.version = firmware.getVersion();
this.fileName = firmware.getFileName();
this.contentType = firmware.getContentType();
this.checksumAlgorithm = firmware.getChecksumAlgorithm();
this.checksum = firmware.getChecksum();
this.data = firmware.getData().array();
this.additionalInfo = firmware.getAdditionalInfo();
}
@Override
public String getSearchTextSource() {
return title;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
@Override
public Firmware toData() {
Firmware firmware = new Firmware(new FirmwareId(id));
firmware.setCreatedTime(createdTime);
firmware.setTenantId(new TenantId(tenantId));
firmware.setTitle(title);
firmware.setVersion(version);
firmware.setFileName(fileName);
firmware.setContentType(contentType);
firmware.setChecksumAlgorithm(checksumAlgorithm);
firmware.setChecksum(checksum);
if (data != null) {
firmware.setData(ByteBuffer.wrap(data));
firmware.setHasData(true);
}
firmware.setAdditionalInfo(additionalInfo);
return firmware;
}
}

116
dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java

@ -0,0 +1,116 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_HAS_DATA_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = FIRMWARE_TABLE_NAME)
public class FirmwareInfoEntity extends BaseSqlEntity<FirmwareInfo> implements SearchTextEntity<FirmwareInfo> {
@Column(name = FIRMWARE_TENANT_ID_COLUMN)
private UUID tenantId;
@Column(name = FIRMWARE_TITLE_COLUMN)
private String title;
@Column(name = FIRMWARE_VERSION_COLUMN)
private String version;
@Type(type = "json")
@Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN)
private JsonNode additionalInfo;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
// @Column(name = FIRMWARE_HAS_DATA_PROPERTY, insertable = false, updatable = false)
@Transient
private boolean hasData;
public FirmwareInfoEntity() {
super();
}
public FirmwareInfoEntity(FirmwareInfo firmware) {
this.createdTime = firmware.getCreatedTime();
this.setUuid(firmware.getUuidId());
this.tenantId = firmware.getTenantId().getId();
this.title = firmware.getTitle();
this.version = firmware.getVersion();
this.additionalInfo = firmware.getAdditionalInfo();
}
public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, Object additionalInfo, boolean hasData) {
this.id = id;
this.createdTime = createdTime;
this.tenantId = tenantId;
this.title = title;
this.version = version;
this.hasData = hasData;
this.additionalInfo = JacksonUtil.convertValue(additionalInfo, JsonNode.class);
}
@Override
public String getSearchTextSource() {
return title;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
@Override
public FirmwareInfo toData() {
FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id));
firmware.setCreatedTime(createdTime);
firmware.setTenantId(new TenantId(tenantId));
firmware.setTitle(title);
firmware.setVersion(version);
firmware.setAdditionalInfo(additionalInfo);
firmware.setHasData(hasData);
return firmware;
}
}

46
dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.firmware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity;
import java.util.UUID;
public interface FirmwareInfoRepository extends CrudRepository<FirmwareInfoEntity, UUID> {
@Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " +
"f.tenantId = :tenantId " +
"AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<FirmwareInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " +
"f.tenantId = :tenantId " +
"AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" +
"AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<FirmwareInfoEntity> findAllByTenantIdAndHasData(@Param("tenantId") UUID tenantId,
@Param("hasData") boolean hasData,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE f.id = :id")
FirmwareInfoEntity findFirmwareInfoById(@Param("id") UUID id);
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.firmware;
import org.springframework.data.repository.CrudRepository;
import org.thingsboard.server.dao.model.sql.FirmwareEntity;
import java.util.UUID;
public interface FirmwareRepository extends CrudRepository<FirmwareEntity, UUID> {
}

46
dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.firmware;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.dao.firmware.FirmwareDao;
import org.thingsboard.server.dao.model.sql.FirmwareEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.UUID;
@Slf4j
@Component
public class JpaFirmwareDao extends JpaAbstractSearchTextDao<FirmwareEntity, Firmware> implements FirmwareDao {
@Autowired
private FirmwareRepository firmwareRepository;
@Override
protected Class<FirmwareEntity> getEntityClass() {
return FirmwareEntity.class;
}
@Override
protected CrudRepository<FirmwareEntity, UUID> getCrudRepository() {
return firmwareRepository;
}
}

84
dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java

@ -0,0 +1,84 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.firmware;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.firmware.FirmwareInfoDao;
import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import java.util.Objects;
import java.util.UUID;
@Slf4j
@Component
public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao<FirmwareInfoEntity, FirmwareInfo> implements FirmwareInfoDao {
@Autowired
private FirmwareInfoRepository firmwareInfoRepository;
@Override
protected Class<FirmwareInfoEntity> getEntityClass() {
return FirmwareInfoEntity.class;
}
@Override
protected CrudRepository<FirmwareInfoEntity, UUID> getCrudRepository() {
return firmwareInfoRepository;
}
@Override
public FirmwareInfo findById(TenantId tenantId, UUID id) {
return DaoUtil.getData(firmwareInfoRepository.findFirmwareInfoById(id));
}
@Override
public FirmwareInfo save(TenantId tenantId, FirmwareInfo firmwareInfo) {
FirmwareInfo savedFirmware = super.save(tenantId, firmwareInfo);
if (firmwareInfo.getId() == null) {
return savedFirmware;
} else {
return findById(tenantId, savedFirmware.getId().getId());
}
}
@Override
public PageData<FirmwareInfo> findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(firmwareInfoRepository
.findAllByTenantId(
tenantId.getId(),
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<FirmwareInfo> findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) {
return DaoUtil.toPageData(firmwareInfoRepository
.findAllByTenantIdAndHasData(
tenantId.getId(),
hasData,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java

@ -48,8 +48,6 @@ public interface TbResourceRepository extends CrudRepository<TbResourceEntity, U
@Param("searchText") String search,
Pageable pageable);
void removeAllByTenantId(UUID tenantId);
@Query("SELECT tr FROM TbResourceEntity tr " +
"WHERE tr.resourceType = :resourceType " +
"AND LOWER(tr.searchText) LIKE LOWER(CONCAT('%', :searchText, '%')) " +

24
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -157,6 +157,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS firmware (
id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),
checksum varchar(1020),
data binary,
additional_info varchar,
search_text varchar(255),
CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
);
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
@ -169,12 +185,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
search_text varchar(255),
is_default boolean,
tenant_id uuid,
firmware_id uuid,
default_rule_chain_id uuid,
default_queue_name varchar(255),
provision_device_key varchar,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id)
);
CREATE TABLE IF NOT EXISTS device (
@ -189,8 +207,10 @@ CREATE TABLE IF NOT EXISTS device (
label varchar(255),
search_text varchar(255),
tenant_id uuid,
firmware_id uuid,
CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id)
CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id)
);
CREATE TABLE IF NOT EXISTS device_credentials (

26
dao/src/main/resources/sql/schema-entities.sql

@ -25,7 +25,7 @@ CREATE OR REPLACE PROCEDURE insert_tb_schema_settings()
$$
BEGIN
IF (SELECT COUNT(*) FROM tb_schema_settings) = 0 THEN
INSERT INTO tb_schema_settings (schema_version) VALUES (3002000);
INSERT INTO tb_schema_settings (schema_version) VALUES (3003000);
END IF;
END;
$$;
@ -175,6 +175,22 @@ CREATE TABLE IF NOT EXISTS rule_node_state (
CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS firmware (
id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),
checksum varchar(1020),
data bytea,
additional_info varchar,
search_text varchar(255),
CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
);
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
@ -187,12 +203,14 @@ CREATE TABLE IF NOT EXISTS device_profile (
search_text varchar(255),
is_default boolean,
tenant_id uuid,
firmware_id uuid,
default_rule_chain_id uuid,
default_queue_name varchar(255),
provision_device_key varchar,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id)
);
CREATE TABLE IF NOT EXISTS device (
@ -207,8 +225,10 @@ CREATE TABLE IF NOT EXISTS device (
label varchar(255),
search_text varchar(255),
tenant_id uuid,
firmware_id uuid,
CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id)
CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id)
);
CREATE TABLE IF NOT EXISTS device_credentials (

6
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -51,6 +51,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -146,6 +147,9 @@ public abstract class AbstractServiceTest {
@Autowired
protected TbResourceService resourceService;
@Autowired
protected FirmwareService firmwareService;
class IdComparator<D extends HasId> implements Comparator<D> {
@Override
public int compare(D o1, D o2) {
@ -192,7 +196,7 @@ public abstract class AbstractServiceTest {
@Bean
public AuditLogLevelFilter auditLogLevelFilter() {
Map<String,String> mask = new HashMap<>();
Map<String, String> mask = new HashMap<>();
for (EntityType entityType : EntityType.values()) {
mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name());
}

70
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java

@ -27,19 +27,19 @@ import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@ -82,18 +82,49 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName());
}
@Test
public void testSaveDeviceProfileWithFirmware() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
Assert.assertNotNull(savedDeviceProfile);
Assert.assertNotNull(savedDeviceProfile.getId());
Assert.assertTrue(savedDeviceProfile.getCreatedTime() > 0);
Assert.assertEquals(deviceProfile.getName(), savedDeviceProfile.getName());
Assert.assertEquals(deviceProfile.getDescription(), savedDeviceProfile.getDescription());
Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData());
Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault());
Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId());
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle("my firmware");
firmware.setVersion("v1.0");
firmware.setFileName("test.txt");
firmware.setContentType("text/plain");
firmware.setChecksumAlgorithm("sha256");
firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
firmware.setData(ByteBuffer.wrap(new byte[]{1}));
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
deviceProfile.setFirmwareId(savedFirmware.getId());
deviceProfileService.saveDeviceProfile(savedDeviceProfile);
DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName());
}
@Test
public void testFindDeviceProfileById() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
Assert.assertNotNull(foundDeviceProfile);
Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
}
}
@Test
public void testFindDeviceProfileInfoById() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
DeviceProfileInfo foundDeviceProfileInfo = deviceProfileService.findDeviceProfileInfoById(tenantId, savedDeviceProfile.getId());
Assert.assertNotNull(foundDeviceProfileInfo);
@ -124,7 +155,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100));
try {
List<ListenableFuture<DeviceProfile>> futures = new ArrayList<>();
for (int i = 0; i < 50; i ++) {
for (int i = 0; i < 50; i++) {
futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1")));
futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2")));
}
@ -138,8 +169,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@Test
public void testSetDefaultDeviceProfile() {
DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1");
DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile 2");
DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId, "Device Profile 1");
DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile 2");
DeviceProfile savedDeviceProfile1 = deviceProfileService.saveDeviceProfile(deviceProfile1);
DeviceProfile savedDeviceProfile2 = deviceProfileService.saveDeviceProfile(deviceProfile2);
@ -165,16 +196,16 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class)
public void testSaveDeviceProfileWithSameName() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
deviceProfileService.saveDeviceProfile(deviceProfile);
DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile");
deviceProfileService.saveDeviceProfile(deviceProfile2);
}
@Ignore
@Test(expected = DataValidationException.class)
public void testChangeDeviceProfileTypeWithExistingDevices() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
Device device = new Device();
device.setTenantId(tenantId);
@ -189,7 +220,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class)
public void testChangeDeviceProfileTransportTypeWithExistingDevices() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
Device device = new Device();
device.setTenantId(tenantId);
@ -203,7 +234,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class)
public void testDeleteDeviceProfileWithExistingDevice() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
Device device = new Device();
device.setTenantId(tenantId);
@ -216,7 +247,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@Test
public void testDeleteDeviceProfile() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile");
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId());
@ -233,8 +264,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, pageData.getTotalElements());
deviceProfiles.addAll(pageData.getData());
for (int i=0;i<28;i++) {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"+i);
for (int i = 0; i < 28; i++) {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile" + i);
deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile));
}
@ -275,8 +306,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, deviceProfilePageData.getTotalElements());
deviceProfiles.addAll(deviceProfilePageData.getData());
for (int i=0;i<28;i++) {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"+i);
for (int i = 0; i < 28; i++) {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile" + i);
deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile));
}
@ -297,7 +328,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream()
.map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(),
deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);
@ -312,4 +343,5 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getTotalElements());
}
}

44
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -88,6 +89,49 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
deviceService.deleteDevice(tenantId, savedDevice.getId());
}
@Test
public void testSaveDeviceWithFirmware() {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Assert.assertNotNull(savedDevice);
Assert.assertNotNull(savedDevice.getId());
Assert.assertTrue(savedDevice.getCreatedTime() > 0);
Assert.assertEquals(device.getTenantId(), savedDevice.getTenantId());
Assert.assertNotNull(savedDevice.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
Assert.assertEquals(device.getName(), savedDevice.getName());
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId());
Assert.assertNotNull(deviceCredentials);
Assert.assertNotNull(deviceCredentials.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType());
Assert.assertNotNull(deviceCredentials.getCredentialsId());
Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle("my firmware");
firmware.setVersion("v1.0");
firmware.setFileName("test.txt");
firmware.setContentType("text/plain");
firmware.setChecksumAlgorithm("sha256");
firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
firmware.setData(ByteBuffer.wrap(new byte[]{1}));
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
savedDevice.setFirmwareId(savedFirmware.getId());
deviceService.saveDevice(savedDevice);
Device foundDevice = deviceService.findDeviceById(tenantId, savedDevice.getId());
Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
}
@Test(expected = DataValidationException.class)
public void testSaveDeviceWithEmptyName() {

481
dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java

@ -0,0 +1,481 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Firmware;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class BaseFirmwareServiceTest extends AbstractServiceTest {
public static final String TITLE = "My firmware";
private static final String FILE_NAME = "filename.txt";
private static final String VERSION = "v1.0";
private static final String CONTENT_TYPE = "text/plain";
private static final String CHECKSUM_ALGORITHM = "sha256";
private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
private IdComparator<FirmwareInfo> idComparator = new IdComparator<>();
private TenantId tenantId;
@Before
public void before() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
}
@Test
public void testSaveFirmware() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
Assert.assertNotNull(savedFirmware);
Assert.assertNotNull(savedFirmware.getId());
Assert.assertTrue(savedFirmware.getCreatedTime() > 0);
Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId());
Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle());
Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName());
Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType());
Assert.assertEquals(firmware.getData(), savedFirmware.getData());
savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode());
firmwareService.saveFirmware(savedFirmware);
Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle());
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
}
@Test
public void testSaveFirmwareInfoAndUpdateWithData() {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTenantId(tenantId);
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo);
Assert.assertNotNull(savedFirmwareInfo);
Assert.assertNotNull(savedFirmwareInfo.getId());
Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0);
Assert.assertEquals(firmwareInfo.getTenantId(), savedFirmwareInfo.getTenantId());
Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle());
Firmware firmware = new Firmware(savedFirmwareInfo.getId());
firmware.setCreatedTime(firmwareInfo.getCreatedTime());
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
firmwareService.saveFirmwareInfo(savedFirmwareInfo);
Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, firmware.getId());
firmware.setAdditionalInfo(JacksonUtil.newObjectNode());
Assert.assertEquals(foundFirmware.getTitle(), firmware.getTitle());
Assert.assertTrue(foundFirmware.isHasData());
firmwareService.deleteFirmware(tenantId, savedFirmwareInfo.getId());
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithEmptyTenant() {
Firmware firmware = new Firmware();
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithEmptyTitle() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithEmptyFileName() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithEmptyContentType() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithEmptyData() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithInvalidTenant() {
Firmware firmware = new Firmware();
firmware.setTenantId(new TenantId(Uuids.timeBased()));
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithEmptyChecksum() {
Firmware firmware = new Firmware();
firmware.setTenantId(new TenantId(Uuids.timeBased()));
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareInfoWithExistingTitleAndVersion() {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTenantId(tenantId);
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION);
firmwareService.saveFirmwareInfo(firmwareInfo);
FirmwareInfo newFirmwareInfo = new FirmwareInfo();
newFirmwareInfo.setTenantId(tenantId);
newFirmwareInfo.setTitle(TITLE);
newFirmwareInfo.setVersion(VERSION);
firmwareService.saveFirmwareInfo(newFirmwareInfo);
}
@Test(expected = DataValidationException.class)
public void testSaveFirmwareWithExistingTitleAndVersion() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
Firmware newFirmware = new Firmware();
newFirmware.setTenantId(tenantId);
newFirmware.setTitle(TITLE);
newFirmware.setVersion(VERSION);
newFirmware.setFileName(FILE_NAME);
newFirmware.setContentType(CONTENT_TYPE);
newFirmware.setData(DATA);
firmwareService.saveFirmware(newFirmware);
}
@Test(expected = DataValidationException.class)
public void testDeleteFirmwareWithReferenceByDevice() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
device.setType("default");
device.setFirmwareId(savedFirmware.getId());
Device savedDevice = deviceService.saveDevice(device);
try {
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
} finally {
deviceService.deleteDevice(tenantId, savedDevice.getId());
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
}
}
@Test(expected = DataValidationException.class)
public void testDeleteFirmwareWithReferenceByDeviceProfile() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
deviceProfile.setFirmwareId(savedFirmware.getId());
DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
try {
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
} finally {
deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
}
}
@Test
public void testFindFirmwareById() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
Assert.assertEquals(savedFirmware, foundFirmware);
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
}
@Test
public void testFindFirmwareInfoById() {
FirmwareInfo firmware = new FirmwareInfo();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
FirmwareInfo savedFirmware = firmwareService.saveFirmwareInfo(firmware);
FirmwareInfo foundFirmware = firmwareService.findFirmwareInfoById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
Assert.assertEquals(savedFirmware, foundFirmware);
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
}
@Test
public void testDeleteFirmware() {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
Firmware savedFirmware = firmwareService.saveFirmware(firmware);
Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
firmwareService.deleteFirmware(tenantId, savedFirmware.getId());
foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId());
Assert.assertNull(foundFirmware);
}
@Test
public void testFindTenantFirmwaresByTenantId() {
List<FirmwareInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
Firmware firmware = new Firmware();
firmware.setTenantId(tenantId);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION + i);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
FirmwareInfo info = new FirmwareInfo(firmwareService.saveFirmware(firmware));
info.setHasData(true);
firmwares.add(info);
}
List<FirmwareInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(16);
PageData<FirmwareInfo> pageData;
do {
pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink);
loadedFirmwares.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
firmwareService.deleteFirmwaresByTenantId(tenantId);
pageLink = new PageLink(31);
pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
}
@Test
public void testFindTenantFirmwaresByTenantIdAndHasData() {
List<FirmwareInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
FirmwareInfo firmwareInfo = new FirmwareInfo();
firmwareInfo.setTenantId(tenantId);
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION + i);
firmwares.add(firmwareService.saveFirmwareInfo(firmwareInfo));
}
List<FirmwareInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(16);
PageData<FirmwareInfo> pageData;
do {
pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, false, pageLink);
loadedFirmwares.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
firmwares.forEach(f -> {
Firmware firmware = new Firmware(f.getId());
firmware.setCreatedTime(f.getCreatedTime());
firmware.setTenantId(f.getTenantId());
firmware.setTitle(f.getTitle());
firmware.setVersion(f.getVersion());
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmwareService.saveFirmware(firmware);
f.setHasData(true);
});
loadedFirmwares = new ArrayList<>();
pageLink = new PageLink(16);
do {
pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, true, pageLink);
loadedFirmwares.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
firmwareService.deleteFirmwaresByTenantId(tenantId);
pageLink = new PageLink(31);
pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
}
}

23
dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.sql;
import org.thingsboard.server.dao.service.BaseFirmwareServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class FirmwareServiceSqlTest extends BaseFirmwareServiceTest {
}

3
dao/src/test/resources/application-test.properties

@ -36,6 +36,9 @@ caffeine.specs.tenantProfiles.maxSize=100000
caffeine.specs.deviceProfiles.timeToLiveInMinutes=1440
caffeine.specs.deviceProfiles.maxSize=100000
caffeine.specs.firmwares.timeToLiveInMinutes=1440
caffeine.specs.firmwares.maxSize=100000
redis.connection.host=localhost
redis.connection.port=6379
redis.connection.db=0

1
dao/src/test/resources/sql/hsql/drop-all-tables.sql

@ -29,4 +29,5 @@ DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration_template;
DROP TABLE IF EXISTS api_usage_state;
DROP TABLE IF EXISTS resource;
DROP TABLE IF EXISTS firmware;
DROP FUNCTION IF EXISTS to_uuid;

1
dao/src/test/resources/sql/psql/drop-all-tables.sql

@ -30,3 +30,4 @@ DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration_template;
DROP TABLE IF EXISTS api_usage_state;
DROP TABLE IF EXISTS resource;
DROP TABLE IF EXISTS firmware;

4
dao/src/test/resources/sql/timescale/drop-all-tables.sql

@ -28,4 +28,6 @@ DROP TABLE IF EXISTS tb_schema_settings;
DROP TABLE IF EXISTS oauth2_client_registration;
DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration_template;
DROP TABLE IF EXISTS api_usage_state;
DROP TABLE IF EXISTS api_usage_state;
DROP TABLE IF EXISTS resource;
DROP TABLE IF EXISTS firmware;

9
ui-ngx/src/app/core/http/entity.service.ts

@ -74,6 +74,7 @@ import {
StringOperation
} from '@shared/models/query/query.models';
import { alarmFields } from '@shared/models/alarm.models';
import { FirmwareService } from '@core/http/firmware.service';
@Injectable({
providedIn: 'root'
@ -93,6 +94,7 @@ export class EntityService {
private dashboardService: DashboardService,
private entityRelationService: EntityRelationService,
private attributeService: AttributeService,
private firmwareService: FirmwareService,
private utils: UtilsService
) { }
@ -128,6 +130,9 @@ export class EntityService {
case EntityType.ALARM:
console.error('Get Alarm Entity is not implemented!');
break;
case EntityType.FIRMWARE:
observable = this.firmwareService.getFirmwareInfo(entityId, config);
break;
}
return observable;
}
@ -326,6 +331,10 @@ export class EntityService {
case EntityType.ALARM:
console.error('Get Alarm Entities is not implemented!');
break;
case EntityType.FIRMWARE:
pageLink.sortOrder.property = 'title';
entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config);
break;
}
return entitiesObservable;
}

122
ui-ngx/src/app/core/http/firmware.service.ts

@ -0,0 +1,122 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { Firmware, FirmwareInfo } from '@shared/models/firmware.models';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
@Injectable({
providedIn: 'root'
})
export class FirmwareService {
constructor(
private http: HttpClient
) {
}
public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable<PageData<FirmwareInfo>> {
let url = `/api/firmwares`;
if (isDefinedAndNotNull(hasData)) {
url += `/${hasData}`;
}
url += `${pageLink.toQuery()}`;
return this.http.get<PageData<FirmwareInfo>>(url, defaultHttpOptionsFromConfig(config));
}
public getFirmware(firmwareId: string, config?: RequestConfig): Observable<Firmware> {
return this.http.get<Firmware>(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config));
}
public getFirmwareInfo(firmwareId: string, config?: RequestConfig): Observable<FirmwareInfo> {
return this.http.get<FirmwareInfo>(`/api/firmware/info/${firmwareId}`, defaultHttpOptionsFromConfig(config));
}
public downloadFirmware(firmwareId: string): Observable<any> {
return this.http.get(`/api/firmware/${firmwareId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe(
map((response) => {
const headers = response.headers;
const filename = headers.get('x-filename');
const contentType = headers.get('content-type');
const linkElement = document.createElement('a');
try {
const blob = new Blob([response.body], { type: contentType });
const url = URL.createObjectURL(blob);
linkElement.setAttribute('href', url);
linkElement.setAttribute('download', filename);
const clickEvent = new MouseEvent('click',
{
view: window,
bubbles: true,
cancelable: false
}
);
linkElement.dispatchEvent(clickEvent);
return null;
} catch (e) {
throw e;
}
})
);
}
public saveFirmware(firmware: Firmware, config?: RequestConfig): Observable<Firmware> {
if (!firmware.file) {
return this.saveFirmwareInfo(firmware, config);
}
const firmwareInfo = deepClone(firmware);
delete firmwareInfo.file;
delete firmwareInfo.checksum;
delete firmwareInfo.checksumAlgorithm;
return this.saveFirmwareInfo(firmwareInfo, config).pipe(
mergeMap(res => {
return this.uploadFirmwareFile(res.id.id, firmware.file, firmware.checksumAlgorithm, firmware.checksum).pipe(
catchError(() => this.deleteFirmware(res.id.id))
);
})
);
}
public saveFirmwareInfo(firmware: FirmwareInfo, config?: RequestConfig): Observable<Firmware> {
return this.http.post<Firmware>('/api/firmware', firmware, defaultHttpOptionsFromConfig(config));
}
public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm?: string,
checksum?: string, config?: RequestConfig): Observable<any> {
if (!config) {
config = {};
}
const formData = new FormData();
formData.append('file', file);
let url = `/api/firmware/${firmwareId}`;
if (checksumAlgorithm && checksum) {
url += `?checksumAlgorithm=${checksumAlgorithm}&checksum=${checksum}`;
}
return this.http.post(url, formData,
defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
}
public deleteFirmware(firmwareId: string, config?: RequestConfig) {
return this.http.delete(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config));
}
}

8
ui-ngx/src/app/core/http/http-utils.ts

@ -39,3 +39,11 @@ export function defaultHttpOptions(ignoreLoading: boolean = false,
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest))
};
}
export function defaultHttpUploadOptions(ignoreLoading: boolean = false,
ignoreErrors: boolean = false,
resendRequest: boolean = false) {
return {
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest))
};
}

23
ui-ngx/src/app/core/http/resource.service.ts

@ -18,10 +18,10 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { forkJoin, Observable, of } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { Resource, ResourceInfo } from '@shared/models/resource.models';
import { map } from 'rxjs/operators';
import { catchError, map, mergeMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
@ -70,6 +70,25 @@ export class ResourceService {
);
}
public saveResources(resources: Resource[], config?: RequestConfig) {
let partSize = 100;
partSize = resources.length > partSize ? partSize : resources.length;
const resourceObservables = [];
for (let i = 0; i < partSize; i++) {
resourceObservables.push(this.saveResource(resources[i], config).pipe(catchError(error => of(error))));
}
return forkJoin(resourceObservables).pipe(
mergeMap((resource) => {
resources.splice(0, partSize);
if (resources.length) {
return this.saveResources(resources, config);
} else {
return of(resource);
}
})
);
}
public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> {
return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config));
}

12
ui-ngx/src/app/core/services/menu.service.ts

@ -279,6 +279,13 @@ export class MenuService {
icon: 'mdi:alpha-d-box',
isMdiIcon: true
},
{
id: guid(),
name: 'firmware.firmware',
type: 'link',
path: '/firmwares',
icon: 'memory'
},
{
id: guid(),
name: 'entity-view.entity-views',
@ -379,6 +386,11 @@ export class MenuService {
icon: 'mdi:alpha-d-box',
isMdiIcon: true,
path: '/deviceProfiles'
},
{
name: 'firmware.firmware',
icon: 'memory',
path: '/firmwares'
}
]
},

2
ui-ngx/src/app/core/utils.ts

@ -407,7 +407,7 @@ export function sortObjectKeys<T>(obj: T): T {
}
export function deepTrim<T>(obj: T): T {
if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null) {
if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || obj instanceof File) {
return obj;
}
return Object.keys(obj).reduce((acc, curr) => {

54
ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html

@ -0,0 +1,54 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-form-field [formGroup]="firmwareFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ placeholderText | translate }}"
#firmwareInput
formControlName="firmwareId"
(focusin)="onFocus()"
[required]="required"
[matAutocomplete]="firmwareAutocomplete">
<button *ngIf="firmwareFormGroup.get('firmwareId').value && !disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete class="tb-autocomplete"
#firmwareAutocomplete="matAutocomplete"
[displayWith]="displayFirmwareFn">
<mat-option *ngFor="let firmware of filteredFirmwares | async" [value]="firmware">
<span [innerHTML]="this.firmwareTitleText(firmware) | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredFirmwares | async)?.length" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span translate>firmware.no-firmware-text</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('firmware.no-firmware-matching',
{entity: truncate.transform(searchText, true, 6, &apos;...&apos;)}) | async }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="firmwareFormGroup.get('firmwareId').hasError('required')">
{{ requiredErrorText | translate }}
</mat-error>
</mat-form-field>

236
ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts

@ -0,0 +1,236 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models';
import { BaseData } from '@shared/models/base-data';
import { EntityService } from '@core/http/entity.service';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { FirmwareInfo } from '@shared/models/firmware.models';
import { FirmwareService } from '@core/http/firmware.service';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
@Component({
selector: 'tb-firmware-autocomplete',
templateUrl: './firmware-autocomplete.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FirmwareAutocompleteComponent),
multi: true
}]
})
export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnInit {
firmwareFormGroup: FormGroup;
modelValue: string | null;
@Input()
labelText: string;
@Input()
requiredText: string;
@Input()
useFullEntityId = false;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@ViewChild('firmwareInput', {static: true}) firmwareInput: ElementRef;
@ViewChild('firmwareInput', {read: MatAutocompleteTrigger}) firmwareAutocomplete: MatAutocompleteTrigger;
filteredFirmwares: Observable<Array<FirmwareInfo>>;
searchText = '';
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
public truncate: TruncatePipe,
private entityService: EntityService,
private firmwareService: FirmwareService,
private fb: FormBuilder) {
this.firmwareFormGroup = this.fb.group({
firmwareId: [null]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.filteredFirmwares = this.firmwareFormGroup.get('firmwareId').valueChanges
.pipe(
tap(value => {
let modelValue;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = this.useFullEntityId ? value.id : value.id.id;
}
this.updateView(modelValue);
if (value === null) {
this.clear();
}
}),
map(value => value ? (typeof value === 'string' ? value : value.title) : ''),
mergeMap(name => this.fetchFirmware(name)),
share()
);
}
ngAfterViewInit(): void {
}
getCurrentEntity(): BaseData<EntityId> | null {
const currentRuleChain = this.firmwareFormGroup.get('firmwareId').value;
if (currentRuleChain && typeof currentRuleChain !== 'string') {
return currentRuleChain as BaseData<EntityId>;
} else {
return null;
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.firmwareFormGroup.disable({emitEvent: false});
} else {
this.firmwareFormGroup.enable({emitEvent: false});
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
writeValue(value: string | EntityId | null): void {
this.searchText = '';
if (value != null && value !== '') {
let firmwareId = '';
if (typeof value === 'string') {
firmwareId = value;
} else if (value.entityType && value.id) {
firmwareId = value.id;
}
if (firmwareId !== '') {
this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => {
this.modelValue = entity.id.id;
this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false});
},
() => {
this.modelValue = null;
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
if (value !== null) {
this.propagateChange(this.modelValue);
}
}
);
} else {
this.modelValue = null;
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
}
} else {
this.modelValue = null;
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
}
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.firmwareFormGroup.get('firmwareId').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
reset() {
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
}
updateView(value: string | null) {
if (this.modelValue !== value) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
displayFirmwareFn(firmware?: FirmwareInfo): string | undefined {
return firmware ? `${firmware.title} (${firmware.version})` : undefined;
}
fetchFirmware(searchText?: string): Observable<Array<FirmwareInfo>> {
this.searchText = searchText;
const pageLink = new PageLink(50, 0, searchText, {
property: 'title',
direction: Direction.ASC
});
return this.firmwareService.getFirmwares(pageLink, true, {ignoreLoading: true}).pipe(
map((data) => data && data.data.length ? data.data : null)
);
}
clear() {
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: true});
setTimeout(() => {
this.firmwareInput.nativeElement.blur();
this.firmwareInput.nativeElement.focus();
}, 0);
}
get placeholderText(): string {
return this.labelText || 'firmware.firmware';
}
get requiredErrorText(): string {
return this.requiredText || 'firmware.firmware-required';
}
firmwareTitleText(firmware: FirmwareInfo): string {
return `${firmware.title} (${firmware.version})`;
}
}

7
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -134,6 +134,7 @@ import { DashboardStateDialogComponent } from '@home/components/dashboard-page/s
import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component';
import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token';
import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
import { FirmwareAutocompleteComponent } from '@home/components/firmware/firmware-autocomplete.component';
@NgModule({
declarations:
@ -247,7 +248,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
ManageDashboardStatesDialogComponent,
DashboardStateDialogComponent,
EmbedDashboardDialogComponent,
DisplayWidgetTypesPanelComponent
DisplayWidgetTypesPanelComponent,
FirmwareAutocompleteComponent
],
imports: [
CommonModule,
@ -351,7 +353,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
ManageDashboardStatesDialogComponent,
DashboardStateDialogComponent,
EmbedDashboardDialogComponent,
DisplayWidgetTypesPanelComponent
DisplayWidgetTypesPanelComponent,
FirmwareAutocompleteComponent
],
providers: [
WidgetComponentService,

4
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html

@ -60,6 +60,10 @@
{{ 'device-profile.type-required' | translate }}
</mat-error>
</mat-form-field>
<tb-firmware-autocomplete
[useFullEntityId]="true"
formControlName="firmwareId">
</tb-firmware-autocomplete>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>

8
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts

@ -48,7 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { deepTrim } from '@core/utils';
import {ServiceType} from "@shared/models/queue.models";
import { ServiceType } from '@shared/models/queue.models';
export interface AddDeviceProfileDialogData {
deviceProfileName: string;
@ -72,13 +72,13 @@ export class AddDeviceProfileDialogComponent extends
entityType = EntityType;
deviceProfileTypes = Object.keys(DeviceProfileType);
deviceProfileTypes = Object.values(DeviceProfileType);
deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
deviceTransportTypeHints = deviceTransportTypeHintMap;
deviceTransportTypes = Object.keys(DeviceTransportType);
deviceTransportTypes = Object.values(DeviceTransportType);
deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
@ -108,6 +108,7 @@ export class AddDeviceProfileDialogComponent extends
type: [DeviceProfileType.DEFAULT, [Validators.required]],
defaultRuleChainId: [null, []],
defaultQueueName: ['', []],
firmwareId: [null],
description: ['', []]
}
);
@ -186,6 +187,7 @@ export class AddDeviceProfileDialogComponent extends
transportType: this.transportConfigFormGroup.get('transportType').value,
provisionType: deviceProvisionConfiguration.type,
provisionDeviceKey,
firmwareId: this.deviceProfileDetailsFormGroup.get('firmwareId').value,
description: this.deviceProfileDetailsFormGroup.get('description').value,
profileData: {
configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),

4
ui-ngx/src/app/modules/home/components/profile/device-profile.component.html

@ -63,6 +63,10 @@
[queueType]="serviceType"
formControlName="defaultQueueName">
</tb-queue-type-list>
<tb-firmware-autocomplete
[useFullEntityId]="true"
formControlName="firmwareId">
</tb-firmware-autocomplete>
<mat-form-field fxHide class="mat-block">
<mat-label translate>device-profile.type</mat-label>
<mat-select formControlName="type" required>

6
ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts

@ -53,11 +53,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
entityType = EntityType;
deviceProfileTypes = Object.keys(DeviceProfileType);
deviceProfileTypes = Object.values(DeviceProfileType);
deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
deviceTransportTypes = Object.keys(DeviceTransportType);
deviceTransportTypes = Object.values(DeviceTransportType);
deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
@ -109,6 +109,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
}),
defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
defaultQueueName: [entity ? entity.defaultQueueName : '', []],
firmwareId: [entity ? entity.firmwareId : null],
description: [entity ? entity.description : '', []],
}
);
@ -184,6 +185,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
}}, {emitEvent: false});
this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false});
this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false});
this.entityForm.patchValue({firmwareId: entity.firmwareId}, {emitEvent: false});
this.entityForm.patchValue({description: entity.description}, {emitEvent: false});
}

4
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html

@ -48,6 +48,10 @@
<mat-label translate>device.label</mat-label>
<input matInput formControlName="label">
</mat-form-field>
<tb-firmware-autocomplete
[useFullEntityId]="true"
formControlName="firmwareId">
</tb-firmware-autocomplete>
<mat-form-field class="mat-block" style="padding-bottom: 14px;">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>

4
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts

@ -70,7 +70,7 @@ export class DeviceWizardDialogComponent extends
entityType = EntityType;
deviceTransportTypes = Object.keys(DeviceTransportType);
deviceTransportTypes = Object.values(DeviceTransportType);
deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
@ -107,6 +107,7 @@ export class DeviceWizardDialogComponent extends
this.deviceWizardFormGroup = this.fb.group({
name: ['', Validators.required],
label: [''],
firmwareId: [null],
gateway: [false],
overwriteActivityTime: [false],
transportType: [DeviceTransportType.DEFAULT, Validators.required],
@ -312,6 +313,7 @@ export class DeviceWizardDialogComponent extends
const device = {
name: this.deviceWizardFormGroup.get('name').value,
label: this.deviceWizardFormGroup.get('label').value,
firmwareId: this.deviceWizardFormGroup.get('firmwareId').value,
deviceProfileId: profileId,
additionalInfo: {
gateway: this.deviceWizardFormGroup.get('gateway').value,

2
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -172,7 +172,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
deleteEntityContent: EntityStringFunction<L> = () => '';
deleteEntitiesTitle: EntityCountStringFunction = () => '';
deleteEntitiesContent: EntityCountStringFunction = () => '';
loadEntity: EntityByIdOperation<T> = () => of();
loadEntity: EntityByIdOperation<T | L> = () => of();
saveEntity: EntityTwoWayOperation<T> = (entity) => of(entity);
deleteEntity: EntityIdOneWayOperation = () => of();
entitiesFetchFunction: EntitiesFetchFunction<L, P> = () => of(emptyPageData<L>());

4
ui-ngx/src/app/modules/home/pages/device/device.component.html

@ -95,6 +95,10 @@
<mat-label translate>device.label</mat-label>
<input matInput formControlName="label">
</mat-form-field>
<tb-firmware-autocomplete
[useFullEntityId]="true"
formControlName="firmwareId">
</tb-firmware-autocomplete>
<tb-device-data
formControlName="deviceData"
required>

2
ui-ngx/src/app/modules/home/pages/device/device.component.ts

@ -79,6 +79,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
{
name: [entity ? entity.name : '', [Validators.required]],
deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]],
firmwareId: [entity ? entity.firmwareId : null],
label: [entity ? entity.label : ''],
deviceData: [entity ? entity.deviceData : null, [Validators.required]],
additionalInfo: this.fb.group(
@ -95,6 +96,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
updateForm(entity: DeviceInfo) {
this.entityForm.patchValue({name: entity.name});
this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId});
this.entityForm.patchValue({firmwareId: entity.firmwareId});
this.entityForm.patchValue({label: entity.label});
this.entityForm.patchValue({deviceData: entity.deviceData});
this.entityForm.patchValue({

48
ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts

@ -0,0 +1,48 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { NgModule } from '@angular/core';
import { FirmwareTableConfigResolve } from '@home/pages/firmware/firmware-table-config.resolve';
const routes: Routes = [
{
path: 'firmwares',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'firmware.firmware',
breadcrumb: {
label: 'firmware.firmware',
icon: 'memory'
}
},
resolve: {
entitiesTableConfig: FirmwareTableConfigResolve
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
FirmwareTableConfigResolve
]
})
export class FirmwareRoutingModule{ }

99
ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts

@ -0,0 +1,99 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import {
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { Firmware, FirmwareInfo } from '@shared/models/firmware.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { FirmwareService } from '@core/http/firmware.service';
import { PageLink } from '@shared/models/page/page-link';
import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { DeviceInfo } from '@shared/models/device.models';
@Injectable()
export class FirmwareTableConfigResolve implements Resolve<EntityTableConfig<Firmware, PageLink, FirmwareInfo>> {
private readonly config: EntityTableConfig<Firmware, PageLink, FirmwareInfo> = new EntityTableConfig<Firmware, PageLink, FirmwareInfo>();
constructor(private translate: TranslateService,
private datePipe: DatePipe,
private firmwareService: FirmwareService) {
this.config.entityType = EntityType.FIRMWARE;
this.config.entityComponent = FirmwaresComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.FIRMWARE);
this.config.entityResources = entityTypeResources.get(EntityType.FIRMWARE);
this.config.entityTitle = (firmware) => firmware ? firmware.title : '';
this.config.columns.push(
new DateEntityTableColumn<FirmwareInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<FirmwareInfo>('title', 'firmware.title', '50%'),
new EntityTableColumn<FirmwareInfo>('version', 'firmware.version', '50%')
);
this.config.cellActionDescriptors.push(
{
name: this.translate.instant('firmware.export'),
icon: 'file_download',
isEnabled: (firmware) => firmware.hasData,
onAction: ($event, entity) => this.exportFirmware($event, entity)
}
);
this.config.deleteEntityTitle = firmware => this.translate.instant('firmware.delete-firmware-title',
{ firmwareTitle: firmware.title });
this.config.deleteEntityContent = () => this.translate.instant('firmware.delete-firmware-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('firmware.delete-firmwares-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('firmware.delete-firmwares-text');
this.config.entitiesFetchFunction = pageLink => this.firmwareService.getFirmwares(pageLink);
this.config.loadEntity = id => this.firmwareService.getFirmwareInfo(id.id);
this.config.saveEntity = firmware => this.firmwareService.saveFirmware(firmware);
this.config.deleteEntity = id => this.firmwareService.deleteFirmware(id.id);
this.config.onEntityAction = action => this.onFirmwareAction(action);
}
resolve(): EntityTableConfig<Firmware, PageLink, FirmwareInfo> {
this.config.tableTitle = this.translate.instant('firmware.firmware');
return this.config;
}
exportFirmware($event: Event, firmware: FirmwareInfo) {
if ($event) {
$event.stopPropagation();
}
this.firmwareService.downloadFirmware(firmware.id.id).subscribe();
}
onFirmwareAction(action: EntityAction<FirmwareInfo>): boolean {
switch (action.action) {
case 'uploadFirmware':
this.exportFirmware(action.event, action.entity);
return true;
}
return false;
}
}

35
ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts

@ -0,0 +1,35 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { HomeComponentsModule } from '@home/components/home-components.module';
import { FirmwareRoutingModule } from '@home/pages/firmware/firmware-routing.module';
import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component';
@NgModule({
declarations: [
FirmwaresComponent
],
imports: [
CommonModule,
SharedModule,
HomeComponentsModule,
FirmwareRoutingModule
]
})
export class FirmwareModule { }

97
ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html

@ -0,0 +1,97 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async) || !entity?.hasData"
(click)="onEntityAction($event, 'uploadFirmware')"
[fxShow]="!isEdit">
{{'firmware.export' | translate }}
</button>
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[fxShow]="!hideDelete() && !isEdit">
{{'resource.delete' | translate }}
</button>
<div fxLayout="row" fxLayout.xs="column">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onFirmwareIdCopied($event)"
[cbContent]="entity?.id?.id"
[fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>firmware.copyId</span>
</button>
</div>
</div>
<div class="mat-padding" fxLayout="column">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-hint class="tb-hint" translate *ngIf="isAdd">firmware.warning-after-save-no-edit</mat-hint>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex="45">
<mat-label translate>firmware.title</mat-label>
<input matInput formControlName="title" type="text" required>
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'firmware.title-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>firmware.version</mat-label>
<input matInput formControlName="version" type="text" required>
<mat-error *ngIf="entityForm.get('version').hasError('required')">
{{ 'firmware.version-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<section *ngIf="isAdd" style="padding-bottom: 8px">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex="45">
<mat-label translate>firmware.checksum-algorithm</mat-label>
<mat-select formControlName="checksumAlgorithm">
<mat-option [value]=null></mat-option>
<mat-option *ngFor="let checksumAlgorithm of checksumAlgorithms" [value]="checksumAlgorithm">
{{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>firmware.checksum</mat-label>
<input matInput formControlName="checksum" type="text"
[required]="entityForm.get('checksumAlgorithm').value != null">
<mat-error *ngIf="entityForm.get('checksumAlgorithm').hasError('required')">
{{ 'firmware.checksum-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<tb-file-input
formControlName="file"
workFromFileObj="true"
required
dropLabel="{{'resource.drop-file' | translate}}">
</tb-file-input>
</section>
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-label translate>firmware.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
</div>
</fieldset>
</form>
</div>

124
ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts

@ -0,0 +1,124 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { EntityComponent } from '@home/components/entity/entity.component';
import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { ActionNotificationShow } from '@core/notification/notification.actions';
@Component({
selector: 'tb-firmware',
templateUrl: './firmwares.component.html'
})
export class FirmwaresComponent extends EntityComponent<Firmware> implements OnInit, OnDestroy {
private destroy$ = new Subject();
checksumAlgorithms = Object.values(ChecksumAlgorithm);
checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: Firmware,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Firmware>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfigValue);
}
ngOnInit() {
super.ngOnInit();
if (this.isAdd) {
this.entityForm.get('checksumAlgorithm').valueChanges.pipe(
map(algorithm => !!algorithm),
distinctUntilChanged(),
takeUntil(this.destroy$)
).subscribe(
setAlgorithm => {
if (setAlgorithm) {
this.entityForm.get('checksum').setValidators([Validators.maxLength(1020), Validators.required]);
} else {
this.entityForm.get('checksum').clearValidators();
}
this.entityForm.get('checksum').updateValueAndValidity({emitEvent: false});
}
);
}
}
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
hideDelete() {
if (this.entitiesTableConfig) {
return !this.entitiesTableConfig.deleteEnabled(this.entity);
} else {
return false;
}
}
buildForm(entity: Firmware): FormGroup {
const form = this.fb.group({
title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]],
additionalInfo: this.fb.group(
{
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''],
}
)
});
if (this.isAdd) {
form.addControl('checksumAlgorithm', this.fb.control(null));
form.addControl('checksum', this.fb.control('', Validators.maxLength(1020)));
form.addControl('file', this.fb.control(null, Validators.required));
}
return form;
}
updateForm(entity: Firmware) {
if (this.isEdit) {
this.entityForm.get('title').disable({emitEvent: false});
this.entityForm.get('version').disable({emitEvent: false});
}
this.entityForm.patchValue({
title: entity.title,
version: entity.version,
additionalInfo: {
description: entity.additionalInfo ? entity.additionalInfo.description : ''
}
});
}
onFirmwareIdCopied($event) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('firmware.idCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}));
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save