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