Commit 8518b993a331117862622811f3246d701d3587fa
Committed by
GitHub
Merge pull request #5220 from thingsboard/feature/bulk-import/device-credentials
[3.3.2] Feature/bulk import/device credentials
Showing
51 changed files
with
2006 additions
and
237 deletions
@@ -16,9 +16,12 @@ | @@ -16,9 +16,12 @@ | ||
16 | package org.thingsboard.server.controller; | 16 | package org.thingsboard.server.controller; |
17 | 17 | ||
18 | import com.google.common.util.concurrent.ListenableFuture; | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | +import lombok.RequiredArgsConstructor; | ||
20 | +import lombok.extern.slf4j.Slf4j; | ||
19 | import org.springframework.http.HttpStatus; | 21 | import org.springframework.http.HttpStatus; |
20 | import org.springframework.security.access.prepost.PreAuthorize; | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | import org.springframework.web.bind.annotation.PathVariable; | 23 | import org.springframework.web.bind.annotation.PathVariable; |
24 | +import org.springframework.web.bind.annotation.PostMapping; | ||
22 | import org.springframework.web.bind.annotation.RequestBody; | 25 | import org.springframework.web.bind.annotation.RequestBody; |
23 | import org.springframework.web.bind.annotation.RequestMapping; | 26 | import org.springframework.web.bind.annotation.RequestMapping; |
24 | import org.springframework.web.bind.annotation.RequestMethod; | 27 | import org.springframework.web.bind.annotation.RequestMethod; |
@@ -34,7 +37,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo; | @@ -34,7 +37,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo; | ||
34 | import org.thingsboard.server.common.data.asset.AssetSearchQuery; | 37 | import org.thingsboard.server.common.data.asset.AssetSearchQuery; |
35 | import org.thingsboard.server.common.data.audit.ActionType; | 38 | import org.thingsboard.server.common.data.audit.ActionType; |
36 | import org.thingsboard.server.common.data.edge.Edge; | 39 | import org.thingsboard.server.common.data.edge.Edge; |
37 | -import org.thingsboard.server.common.data.edge.EdgeEventType; | ||
38 | import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; | 40 | import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
39 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; | 41 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
40 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 42 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
@@ -48,6 +50,9 @@ import org.thingsboard.server.common.data.page.TimePageLink; | @@ -48,6 +50,9 @@ import org.thingsboard.server.common.data.page.TimePageLink; | ||
48 | import org.thingsboard.server.dao.exception.IncorrectParameterException; | 50 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
49 | import org.thingsboard.server.dao.model.ModelConstants; | 51 | import org.thingsboard.server.dao.model.ModelConstants; |
50 | import org.thingsboard.server.queue.util.TbCoreComponent; | 52 | import org.thingsboard.server.queue.util.TbCoreComponent; |
53 | +import org.thingsboard.server.service.asset.AssetBulkImportService; | ||
54 | +import org.thingsboard.server.service.importing.BulkImportRequest; | ||
55 | +import org.thingsboard.server.service.importing.BulkImportResult; | ||
51 | import org.thingsboard.server.service.security.model.SecurityUser; | 56 | import org.thingsboard.server.service.security.model.SecurityUser; |
52 | import org.thingsboard.server.service.security.permission.Operation; | 57 | import org.thingsboard.server.service.security.permission.Operation; |
53 | import org.thingsboard.server.service.security.permission.Resource; | 58 | import org.thingsboard.server.service.security.permission.Resource; |
@@ -63,7 +68,10 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; | @@ -63,7 +68,10 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; | ||
63 | @RestController | 68 | @RestController |
64 | @TbCoreComponent | 69 | @TbCoreComponent |
65 | @RequestMapping("/api") | 70 | @RequestMapping("/api") |
71 | +@RequiredArgsConstructor | ||
72 | +@Slf4j | ||
66 | public class AssetController extends BaseController { | 73 | public class AssetController extends BaseController { |
74 | + private final AssetBulkImportService assetBulkImportService; | ||
67 | 75 | ||
68 | public static final String ASSET_ID = "assetId"; | 76 | public static final String ASSET_ID = "assetId"; |
69 | 77 | ||
@@ -108,13 +116,7 @@ public class AssetController extends BaseController { | @@ -108,13 +116,7 @@ public class AssetController extends BaseController { | ||
108 | 116 | ||
109 | Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); | 117 | Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); |
110 | 118 | ||
111 | - logEntityAction(savedAsset.getId(), savedAsset, | ||
112 | - savedAsset.getCustomerId(), | ||
113 | - asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | ||
114 | - | ||
115 | - if (asset.getId() != null) { | ||
116 | - sendEntityNotificationMsg(savedAsset.getTenantId(), savedAsset.getId(), EdgeEventActionType.UPDATED); | ||
117 | - } | 119 | + onAssetCreatedOrUpdated(savedAsset, asset.getId() != null); |
118 | 120 | ||
119 | return savedAsset; | 121 | return savedAsset; |
120 | } catch (Exception e) { | 122 | } catch (Exception e) { |
@@ -124,6 +126,20 @@ public class AssetController extends BaseController { | @@ -124,6 +126,20 @@ public class AssetController extends BaseController { | ||
124 | } | 126 | } |
125 | } | 127 | } |
126 | 128 | ||
129 | + private void onAssetCreatedOrUpdated(Asset asset, boolean updated) { | ||
130 | + try { | ||
131 | + logEntityAction(asset.getId(), asset, | ||
132 | + asset.getCustomerId(), | ||
133 | + updated ? ActionType.UPDATED : ActionType.ADDED, null); | ||
134 | + } catch (ThingsboardException e) { | ||
135 | + log.error("Failed to log entity action", e); | ||
136 | + } | ||
137 | + | ||
138 | + if (updated) { | ||
139 | + sendEntityNotificationMsg(asset.getTenantId(), asset.getId(), EdgeEventActionType.UPDATED); | ||
140 | + } | ||
141 | + } | ||
142 | + | ||
127 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") | 143 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
128 | @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE) | 144 | @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE) |
129 | @ResponseStatus(value = HttpStatus.OK) | 145 | @ResponseStatus(value = HttpStatus.OK) |
@@ -258,7 +274,7 @@ public class AssetController extends BaseController { | @@ -258,7 +274,7 @@ public class AssetController extends BaseController { | ||
258 | try { | 274 | try { |
259 | TenantId tenantId = getCurrentUser().getTenantId(); | 275 | TenantId tenantId = getCurrentUser().getTenantId(); |
260 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | 276 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
261 | - if (type != null && type.trim().length()>0) { | 277 | + if (type != null && type.trim().length() > 0) { |
262 | return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); | 278 | return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); |
263 | } else { | 279 | } else { |
264 | return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink)); | 280 | return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink)); |
@@ -321,7 +337,7 @@ public class AssetController extends BaseController { | @@ -321,7 +337,7 @@ public class AssetController extends BaseController { | ||
321 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | 337 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
322 | checkCustomerId(customerId, Operation.READ); | 338 | checkCustomerId(customerId, Operation.READ); |
323 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | 339 | PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
324 | - if (type != null && type.trim().length()>0) { | 340 | + if (type != null && type.trim().length() > 0) { |
325 | return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); | 341 | return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); |
326 | } else { | 342 | } else { |
327 | return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); | 343 | return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); |
@@ -426,7 +442,7 @@ public class AssetController extends BaseController { | @@ -426,7 +442,7 @@ public class AssetController extends BaseController { | ||
426 | @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) | 442 | @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) |
427 | @ResponseBody | 443 | @ResponseBody |
428 | public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId, | 444 | public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId, |
429 | - @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { | 445 | + @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { |
430 | checkParameter(EDGE_ID, strEdgeId); | 446 | checkParameter(EDGE_ID, strEdgeId); |
431 | checkParameter(ASSET_ID, strAssetId); | 447 | checkParameter(ASSET_ID, strAssetId); |
432 | try { | 448 | try { |
@@ -444,7 +460,7 @@ public class AssetController extends BaseController { | @@ -444,7 +460,7 @@ public class AssetController extends BaseController { | ||
444 | 460 | ||
445 | sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE); | 461 | sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE); |
446 | 462 | ||
447 | - return savedAsset; | 463 | + return savedAsset; |
448 | } catch (Exception e) { | 464 | } catch (Exception e) { |
449 | 465 | ||
450 | logEntityAction(emptyId(EntityType.ASSET), null, | 466 | logEntityAction(emptyId(EntityType.ASSET), null, |
@@ -530,4 +546,13 @@ public class AssetController extends BaseController { | @@ -530,4 +546,13 @@ public class AssetController extends BaseController { | ||
530 | throw handleException(e); | 546 | throw handleException(e); |
531 | } | 547 | } |
532 | } | 548 | } |
549 | + | ||
550 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | ||
551 | + @PostMapping("/asset/bulk_import") | ||
552 | + public BulkImportResult<Asset> processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception { | ||
553 | + return assetBulkImportService.processBulkImport(request, getCurrentUser(), importedAssetInfo -> { | ||
554 | + onAssetCreatedOrUpdated(importedAssetInfo.getEntity(), importedAssetInfo.isUpdated()); | ||
555 | + }); | ||
556 | + } | ||
557 | + | ||
533 | } | 558 | } |
@@ -121,7 +121,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; | @@ -121,7 +121,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; | ||
121 | import org.thingsboard.server.queue.discovery.PartitionService; | 121 | import org.thingsboard.server.queue.discovery.PartitionService; |
122 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; | 122 | import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
123 | import org.thingsboard.server.queue.util.TbCoreComponent; | 123 | import org.thingsboard.server.queue.util.TbCoreComponent; |
124 | -import org.thingsboard.server.service.action.RuleEngineEntityActionService; | 124 | +import org.thingsboard.server.service.action.EntityActionService; |
125 | import org.thingsboard.server.service.component.ComponentDiscoveryService; | 125 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
126 | import org.thingsboard.server.service.edge.EdgeLicenseService; | 126 | import org.thingsboard.server.service.edge.EdgeLicenseService; |
127 | import org.thingsboard.server.service.edge.EdgeNotificationService; | 127 | import org.thingsboard.server.service.edge.EdgeNotificationService; |
@@ -279,7 +279,7 @@ public abstract class BaseController { | @@ -279,7 +279,7 @@ public abstract class BaseController { | ||
279 | protected EdgeLicenseService edgeLicenseService; | 279 | protected EdgeLicenseService edgeLicenseService; |
280 | 280 | ||
281 | @Autowired | 281 | @Autowired |
282 | - protected RuleEngineEntityActionService ruleEngineEntityActionService; | 282 | + protected EntityActionService entityActionService; |
283 | 283 | ||
284 | @Value("${server.log_controller_error_stack_trace}") | 284 | @Value("${server.log_controller_error_stack_trace}") |
285 | @Getter | 285 | @Getter |
@@ -819,13 +819,7 @@ public abstract class BaseController { | @@ -819,13 +819,7 @@ public abstract class BaseController { | ||
819 | 819 | ||
820 | protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId, | 820 | protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId, |
821 | ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { | 821 | ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { |
822 | - if (customerId == null || customerId.isNullUid()) { | ||
823 | - customerId = user.getCustomerId(); | ||
824 | - } | ||
825 | - if (e == null) { | ||
826 | - ruleEngineEntityActionService.pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo); | ||
827 | - } | ||
828 | - auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); | 822 | + entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); |
829 | } | 823 | } |
830 | 824 | ||
831 | 825 |
@@ -19,10 +19,13 @@ import com.google.common.util.concurrent.FutureCallback; | @@ -19,10 +19,13 @@ import com.google.common.util.concurrent.FutureCallback; | ||
19 | import com.google.common.util.concurrent.Futures; | 19 | import com.google.common.util.concurrent.Futures; |
20 | import com.google.common.util.concurrent.ListenableFuture; | 20 | import com.google.common.util.concurrent.ListenableFuture; |
21 | import com.google.common.util.concurrent.MoreExecutors; | 21 | import com.google.common.util.concurrent.MoreExecutors; |
22 | +import lombok.RequiredArgsConstructor; | ||
23 | +import lombok.extern.slf4j.Slf4j; | ||
22 | import org.springframework.http.HttpStatus; | 24 | import org.springframework.http.HttpStatus; |
23 | import org.springframework.http.ResponseEntity; | 25 | import org.springframework.http.ResponseEntity; |
24 | import org.springframework.security.access.prepost.PreAuthorize; | 26 | import org.springframework.security.access.prepost.PreAuthorize; |
25 | import org.springframework.web.bind.annotation.PathVariable; | 27 | import org.springframework.web.bind.annotation.PathVariable; |
28 | +import org.springframework.web.bind.annotation.PostMapping; | ||
26 | import org.springframework.web.bind.annotation.RequestBody; | 29 | import org.springframework.web.bind.annotation.RequestBody; |
27 | import org.springframework.web.bind.annotation.RequestMapping; | 30 | import org.springframework.web.bind.annotation.RequestMapping; |
28 | import org.springframework.web.bind.annotation.RequestMethod; | 31 | import org.springframework.web.bind.annotation.RequestMethod; |
@@ -56,7 +59,6 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; | @@ -56,7 +59,6 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; | ||
56 | import org.thingsboard.server.common.data.page.PageData; | 59 | import org.thingsboard.server.common.data.page.PageData; |
57 | import org.thingsboard.server.common.data.page.PageLink; | 60 | import org.thingsboard.server.common.data.page.PageLink; |
58 | import org.thingsboard.server.common.data.page.TimePageLink; | 61 | import org.thingsboard.server.common.data.page.TimePageLink; |
59 | -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | ||
60 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 62 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
61 | import org.thingsboard.server.common.msg.TbMsg; | 63 | import org.thingsboard.server.common.msg.TbMsg; |
62 | import org.thingsboard.server.common.msg.TbMsgDataType; | 64 | import org.thingsboard.server.common.msg.TbMsgDataType; |
@@ -67,6 +69,9 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult; | @@ -67,6 +69,9 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult; | ||
67 | import org.thingsboard.server.dao.exception.IncorrectParameterException; | 69 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
68 | import org.thingsboard.server.dao.model.ModelConstants; | 70 | import org.thingsboard.server.dao.model.ModelConstants; |
69 | import org.thingsboard.server.queue.util.TbCoreComponent; | 71 | import org.thingsboard.server.queue.util.TbCoreComponent; |
72 | +import org.thingsboard.server.service.device.DeviceBulkImportService; | ||
73 | +import org.thingsboard.server.service.importing.BulkImportRequest; | ||
74 | +import org.thingsboard.server.service.importing.BulkImportResult; | ||
70 | import org.thingsboard.server.service.security.model.SecurityUser; | 75 | import org.thingsboard.server.service.security.model.SecurityUser; |
71 | import org.thingsboard.server.service.security.permission.Operation; | 76 | import org.thingsboard.server.service.security.permission.Operation; |
72 | import org.thingsboard.server.service.security.permission.Resource; | 77 | import org.thingsboard.server.service.security.permission.Resource; |
@@ -83,7 +88,10 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; | @@ -83,7 +88,10 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; | ||
83 | @RestController | 88 | @RestController |
84 | @TbCoreComponent | 89 | @TbCoreComponent |
85 | @RequestMapping("/api") | 90 | @RequestMapping("/api") |
91 | +@RequiredArgsConstructor | ||
92 | +@Slf4j | ||
86 | public class DeviceController extends BaseController { | 93 | public class DeviceController extends BaseController { |
94 | + private final DeviceBulkImportService deviceBulkImportService; | ||
87 | 95 | ||
88 | private static final String DEVICE_ID = "deviceId"; | 96 | private static final String DEVICE_ID = "deviceId"; |
89 | private static final String DEVICE_NAME = "deviceName"; | 97 | private static final String DEVICE_NAME = "deviceName"; |
@@ -133,11 +141,7 @@ public class DeviceController extends BaseController { | @@ -133,11 +141,7 @@ public class DeviceController extends BaseController { | ||
133 | 141 | ||
134 | Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); | 142 | Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); |
135 | 143 | ||
136 | - tbClusterService.onDeviceUpdated(savedDevice, oldDevice); | ||
137 | - | ||
138 | - logEntityAction(savedDevice.getId(), savedDevice, | ||
139 | - savedDevice.getCustomerId(), | ||
140 | - created ? ActionType.ADDED : ActionType.UPDATED, null); | 144 | + onDeviceCreatedOrUpdated(savedDevice, oldDevice, !created); |
141 | 145 | ||
142 | return savedDevice; | 146 | return savedDevice; |
143 | } catch (Exception e) { | 147 | } catch (Exception e) { |
@@ -148,6 +152,18 @@ public class DeviceController extends BaseController { | @@ -148,6 +152,18 @@ public class DeviceController extends BaseController { | ||
148 | 152 | ||
149 | } | 153 | } |
150 | 154 | ||
155 | + private void onDeviceCreatedOrUpdated(Device savedDevice, Device oldDevice, boolean updated) { | ||
156 | + tbClusterService.onDeviceUpdated(savedDevice, oldDevice); | ||
157 | + | ||
158 | + try { | ||
159 | + logEntityAction(savedDevice.getId(), savedDevice, | ||
160 | + savedDevice.getCustomerId(), | ||
161 | + updated ? ActionType.UPDATED : ActionType.ADDED, null); | ||
162 | + } catch (ThingsboardException e) { | ||
163 | + log.error("Failed to log entity action", e); | ||
164 | + } | ||
165 | + } | ||
166 | + | ||
151 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") | 167 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
152 | @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) | 168 | @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) |
153 | @ResponseStatus(value = HttpStatus.OK) | 169 | @ResponseStatus(value = HttpStatus.OK) |
@@ -776,4 +792,13 @@ public class DeviceController extends BaseController { | @@ -776,4 +792,13 @@ public class DeviceController extends BaseController { | ||
776 | throw handleException(e); | 792 | throw handleException(e); |
777 | } | 793 | } |
778 | } | 794 | } |
795 | + | ||
796 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | ||
797 | + @PostMapping("/device/bulk_import") | ||
798 | + public BulkImportResult<Device> processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception { | ||
799 | + return deviceBulkImportService.processBulkImport(request, getCurrentUser(), importedDeviceInfo -> { | ||
800 | + onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity(), importedDeviceInfo.isUpdated()); | ||
801 | + }); | ||
802 | + } | ||
803 | + | ||
779 | } | 804 | } |
@@ -17,11 +17,13 @@ package org.thingsboard.server.controller; | @@ -17,11 +17,13 @@ package org.thingsboard.server.controller; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | import com.google.common.util.concurrent.ListenableFuture; | 19 | import com.google.common.util.concurrent.ListenableFuture; |
20 | +import lombok.RequiredArgsConstructor; | ||
20 | import lombok.extern.slf4j.Slf4j; | 21 | import lombok.extern.slf4j.Slf4j; |
21 | import org.springframework.http.HttpStatus; | 22 | import org.springframework.http.HttpStatus; |
22 | import org.springframework.http.ResponseEntity; | 23 | import org.springframework.http.ResponseEntity; |
23 | import org.springframework.security.access.prepost.PreAuthorize; | 24 | import org.springframework.security.access.prepost.PreAuthorize; |
24 | import org.springframework.web.bind.annotation.PathVariable; | 25 | import org.springframework.web.bind.annotation.PathVariable; |
26 | +import org.springframework.web.bind.annotation.PostMapping; | ||
25 | import org.springframework.web.bind.annotation.RequestBody; | 27 | import org.springframework.web.bind.annotation.RequestBody; |
26 | import org.springframework.web.bind.annotation.RequestMapping; | 28 | import org.springframework.web.bind.annotation.RequestMapping; |
27 | import org.springframework.web.bind.annotation.RequestMethod; | 29 | import org.springframework.web.bind.annotation.RequestMethod; |
@@ -52,10 +54,14 @@ import org.thingsboard.server.dao.exception.DataValidationException; | @@ -52,10 +54,14 @@ import org.thingsboard.server.dao.exception.DataValidationException; | ||
52 | import org.thingsboard.server.dao.exception.IncorrectParameterException; | 54 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
53 | import org.thingsboard.server.dao.model.ModelConstants; | 55 | import org.thingsboard.server.dao.model.ModelConstants; |
54 | import org.thingsboard.server.queue.util.TbCoreComponent; | 56 | import org.thingsboard.server.queue.util.TbCoreComponent; |
57 | +import org.thingsboard.server.service.edge.EdgeBulkImportService; | ||
58 | +import org.thingsboard.server.service.importing.BulkImportRequest; | ||
59 | +import org.thingsboard.server.service.importing.BulkImportResult; | ||
55 | import org.thingsboard.server.service.security.model.SecurityUser; | 60 | import org.thingsboard.server.service.security.model.SecurityUser; |
56 | import org.thingsboard.server.service.security.permission.Operation; | 61 | import org.thingsboard.server.service.security.permission.Operation; |
57 | import org.thingsboard.server.service.security.permission.Resource; | 62 | import org.thingsboard.server.service.security.permission.Resource; |
58 | 63 | ||
64 | +import java.io.IOException; | ||
59 | import java.util.ArrayList; | 65 | import java.util.ArrayList; |
60 | import java.util.List; | 66 | import java.util.List; |
61 | import java.util.stream.Collectors; | 67 | import java.util.stream.Collectors; |
@@ -64,7 +70,9 @@ import java.util.stream.Collectors; | @@ -64,7 +70,9 @@ import java.util.stream.Collectors; | ||
64 | @TbCoreComponent | 70 | @TbCoreComponent |
65 | @Slf4j | 71 | @Slf4j |
66 | @RequestMapping("/api") | 72 | @RequestMapping("/api") |
73 | +@RequiredArgsConstructor | ||
67 | public class EdgeController extends BaseController { | 74 | public class EdgeController extends BaseController { |
75 | + private final EdgeBulkImportService edgeBulkImportService; | ||
68 | 76 | ||
69 | public static final String EDGE_ID = "edgeId"; | 77 | public static final String EDGE_ID = "edgeId"; |
70 | 78 | ||
@@ -132,17 +140,8 @@ public class EdgeController extends BaseController { | @@ -132,17 +140,8 @@ public class EdgeController extends BaseController { | ||
132 | edge.getId(), edge); | 140 | edge.getId(), edge); |
133 | 141 | ||
134 | Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true)); | 142 | Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true)); |
143 | + onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created); | ||
135 | 144 | ||
136 | - if (created) { | ||
137 | - ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), savedEdge.getId()); | ||
138 | - edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId()); | ||
139 | - edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId()); | ||
140 | - } | ||
141 | - | ||
142 | - tbClusterService.broadcastEntityStateChangeEvent(savedEdge.getTenantId(), savedEdge.getId(), | ||
143 | - created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | ||
144 | - | ||
145 | - logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null); | ||
146 | return savedEdge; | 145 | return savedEdge; |
147 | } catch (Exception e) { | 146 | } catch (Exception e) { |
148 | logEntityAction(emptyId(EntityType.EDGE), edge, | 147 | logEntityAction(emptyId(EntityType.EDGE), edge, |
@@ -151,6 +150,19 @@ public class EdgeController extends BaseController { | @@ -151,6 +150,19 @@ public class EdgeController extends BaseController { | ||
151 | } | 150 | } |
152 | } | 151 | } |
153 | 152 | ||
153 | + private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated) throws IOException, ThingsboardException { | ||
154 | + if (!updated) { | ||
155 | + ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edge.getId()); | ||
156 | + edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, edgeTemplateRootRuleChain.getId()); | ||
157 | + edgeService.assignDefaultRuleChainsToEdge(tenantId, edge.getId()); | ||
158 | + } | ||
159 | + | ||
160 | + tbClusterService.broadcastEntityStateChangeEvent(edge.getTenantId(), edge.getId(), | ||
161 | + updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); | ||
162 | + | ||
163 | + logEntityAction(edge.getId(), edge, null, updated ? ActionType.UPDATED : ActionType.ADDED, null); | ||
164 | + } | ||
165 | + | ||
154 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") | 166 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
155 | @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE) | 167 | @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE) |
156 | @ResponseStatus(value = HttpStatus.OK) | 168 | @ResponseStatus(value = HttpStatus.OK) |
@@ -563,6 +575,24 @@ public class EdgeController extends BaseController { | @@ -563,6 +575,24 @@ public class EdgeController extends BaseController { | ||
563 | } | 575 | } |
564 | } | 576 | } |
565 | 577 | ||
578 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | ||
579 | + @PostMapping("/edge/bulk_import") | ||
580 | + public BulkImportResult<Edge> processEdgeBulkImport(@RequestBody BulkImportRequest request) throws Exception { | ||
581 | + SecurityUser user = getCurrentUser(); | ||
582 | + RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(user.getTenantId()); | ||
583 | + if (edgeTemplateRootRuleChain == null) { | ||
584 | + throw new DataValidationException("Root edge rule chain is not available!"); | ||
585 | + } | ||
586 | + | ||
587 | + return edgeBulkImportService.processBulkImport(request, user, importedAssetInfo -> { | ||
588 | + try { | ||
589 | + onEdgeCreatedOrUpdated(user.getTenantId(), importedAssetInfo.getEntity(), edgeTemplateRootRuleChain, importedAssetInfo.isUpdated()); | ||
590 | + } catch (Exception e) { | ||
591 | + throw new RuntimeException(e); | ||
592 | + } | ||
593 | + }); | ||
594 | + } | ||
595 | + | ||
566 | private void cleanUpLicenseKey(Edge edge) { | 596 | private void cleanUpLicenseKey(Edge edge) { |
567 | edge.setEdgeLicenseKey(null); | 597 | edge.setEdgeLicenseKey(null); |
568 | } | 598 | } |
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
renamed from
application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.HasName; | @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.HasName; | ||
28 | import org.thingsboard.server.common.data.HasTenantId; | 28 | import org.thingsboard.server.common.data.HasTenantId; |
29 | import org.thingsboard.server.common.data.User; | 29 | import org.thingsboard.server.common.data.User; |
30 | import org.thingsboard.server.common.data.audit.ActionType; | 30 | import org.thingsboard.server.common.data.audit.ActionType; |
31 | +import org.thingsboard.server.common.data.exception.ThingsboardException; | ||
31 | import org.thingsboard.server.common.data.id.CustomerId; | 32 | import org.thingsboard.server.common.data.id.CustomerId; |
32 | import org.thingsboard.server.common.data.id.EntityId; | 33 | import org.thingsboard.server.common.data.id.EntityId; |
33 | import org.thingsboard.server.common.data.id.TenantId; | 34 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; | @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
38 | import org.thingsboard.server.common.msg.TbMsg; | 39 | import org.thingsboard.server.common.msg.TbMsg; |
39 | import org.thingsboard.server.common.msg.TbMsgDataType; | 40 | import org.thingsboard.server.common.msg.TbMsgDataType; |
40 | import org.thingsboard.server.common.msg.TbMsgMetaData; | 41 | import org.thingsboard.server.common.msg.TbMsgMetaData; |
42 | +import org.thingsboard.server.dao.audit.AuditLogService; | ||
41 | import org.thingsboard.server.queue.util.TbCoreComponent; | 43 | import org.thingsboard.server.queue.util.TbCoreComponent; |
42 | import org.thingsboard.server.cluster.TbClusterService; | 44 | import org.thingsboard.server.cluster.TbClusterService; |
43 | 45 | ||
@@ -49,8 +51,9 @@ import java.util.stream.Collectors; | @@ -49,8 +51,9 @@ import java.util.stream.Collectors; | ||
49 | @Service | 51 | @Service |
50 | @RequiredArgsConstructor | 52 | @RequiredArgsConstructor |
51 | @Slf4j | 53 | @Slf4j |
52 | -public class RuleEngineEntityActionService { | 54 | +public class EntityActionService { |
53 | private final TbClusterService tbClusterService; | 55 | private final TbClusterService tbClusterService; |
56 | + private final AuditLogService auditLogService; | ||
54 | 57 | ||
55 | private static final ObjectMapper json = new ObjectMapper(); | 58 | private static final ObjectMapper json = new ObjectMapper(); |
56 | 59 | ||
@@ -209,6 +212,17 @@ public class RuleEngineEntityActionService { | @@ -209,6 +212,17 @@ public class RuleEngineEntityActionService { | ||
209 | } | 212 | } |
210 | } | 213 | } |
211 | 214 | ||
215 | + public <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId, | ||
216 | + ActionType actionType, Exception e, Object... additionalInfo) { | ||
217 | + if (customerId == null || customerId.isNullUid()) { | ||
218 | + customerId = user.getCustomerId(); | ||
219 | + } | ||
220 | + if (e == null) { | ||
221 | + pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo); | ||
222 | + } | ||
223 | + auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); | ||
224 | + } | ||
225 | + | ||
212 | 226 | ||
213 | private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { | 227 | private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { |
214 | T result = null; | 228 | T result = null; |
application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.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.asset; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
19 | +import com.fasterxml.jackson.databind.node.TextNode; | ||
20 | +import org.springframework.stereotype.Service; | ||
21 | +import org.thingsboard.common.util.JacksonUtil; | ||
22 | +import org.thingsboard.server.cluster.TbClusterService; | ||
23 | +import org.thingsboard.server.common.data.asset.Asset; | ||
24 | +import org.thingsboard.server.dao.asset.AssetService; | ||
25 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | ||
26 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
27 | +import org.thingsboard.server.service.action.EntityActionService; | ||
28 | +import org.thingsboard.server.service.importing.AbstractBulkImportService; | ||
29 | +import org.thingsboard.server.service.importing.BulkImportColumnType; | ||
30 | +import org.thingsboard.server.service.importing.BulkImportRequest; | ||
31 | +import org.thingsboard.server.service.importing.ImportedEntityInfo; | ||
32 | +import org.thingsboard.server.service.security.AccessValidator; | ||
33 | +import org.thingsboard.server.service.security.model.SecurityUser; | ||
34 | +import org.thingsboard.server.service.security.permission.AccessControlService; | ||
35 | +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; | ||
36 | + | ||
37 | +import java.util.Map; | ||
38 | +import java.util.Optional; | ||
39 | + | ||
40 | +@Service | ||
41 | +@TbCoreComponent | ||
42 | +public class AssetBulkImportService extends AbstractBulkImportService<Asset> { | ||
43 | + private final AssetService assetService; | ||
44 | + | ||
45 | + public AssetBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache, | ||
46 | + AccessControlService accessControlService, AccessValidator accessValidator, | ||
47 | + EntityActionService entityActionService, TbClusterService clusterService, AssetService assetService) { | ||
48 | + super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService); | ||
49 | + this.assetService = assetService; | ||
50 | + } | ||
51 | + | ||
52 | + @Override | ||
53 | + protected ImportedEntityInfo<Asset> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { | ||
54 | + ImportedEntityInfo<Asset> importedEntityInfo = new ImportedEntityInfo<>(); | ||
55 | + | ||
56 | + Asset asset = new Asset(); | ||
57 | + asset.setTenantId(user.getTenantId()); | ||
58 | + setAssetFields(asset, fields); | ||
59 | + | ||
60 | + Asset existingAsset = assetService.findAssetByTenantIdAndName(user.getTenantId(), asset.getName()); | ||
61 | + if (existingAsset != null && importRequest.getMapping().getUpdate()) { | ||
62 | + importedEntityInfo.setOldEntity(new Asset(existingAsset)); | ||
63 | + importedEntityInfo.setUpdated(true); | ||
64 | + existingAsset.update(asset); | ||
65 | + asset = existingAsset; | ||
66 | + } | ||
67 | + asset = assetService.saveAsset(asset); | ||
68 | + | ||
69 | + importedEntityInfo.setEntity(asset); | ||
70 | + return importedEntityInfo; | ||
71 | + } | ||
72 | + | ||
73 | + private void setAssetFields(Asset asset, Map<BulkImportColumnType, String> fields) { | ||
74 | + ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(asset.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); | ||
75 | + fields.forEach((columnType, value) -> { | ||
76 | + switch (columnType) { | ||
77 | + case NAME: | ||
78 | + asset.setName(value); | ||
79 | + break; | ||
80 | + case TYPE: | ||
81 | + asset.setType(value); | ||
82 | + break; | ||
83 | + case LABEL: | ||
84 | + asset.setLabel(value); | ||
85 | + break; | ||
86 | + case DESCRIPTION: | ||
87 | + additionalInfo.set("description", new TextNode(value)); | ||
88 | + break; | ||
89 | + } | ||
90 | + }); | ||
91 | + asset.setAdditionalInfo(additionalInfo); | ||
92 | + } | ||
93 | + | ||
94 | +} |
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.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.device; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.node.BooleanNode; | ||
19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
20 | +import com.fasterxml.jackson.databind.node.TextNode; | ||
21 | +import lombok.SneakyThrows; | ||
22 | +import org.apache.commons.collections.CollectionUtils; | ||
23 | +import org.apache.commons.lang3.RandomStringUtils; | ||
24 | +import org.apache.commons.lang3.StringUtils; | ||
25 | +import org.springframework.stereotype.Service; | ||
26 | +import org.thingsboard.common.util.JacksonUtil; | ||
27 | +import org.thingsboard.server.cluster.TbClusterService; | ||
28 | +import org.thingsboard.server.common.data.Device; | ||
29 | +import org.thingsboard.server.common.data.DeviceProfile; | ||
30 | +import org.thingsboard.server.common.data.DeviceProfileProvisionType; | ||
31 | +import org.thingsboard.server.common.data.DeviceProfileType; | ||
32 | +import org.thingsboard.server.common.data.DeviceTransportType; | ||
33 | +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; | ||
34 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; | ||
35 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; | ||
36 | +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; | ||
37 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | ||
38 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; | ||
39 | +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; | ||
40 | +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; | ||
41 | +import org.thingsboard.server.common.data.id.TenantId; | ||
42 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
43 | +import org.thingsboard.server.common.data.security.DeviceCredentialsType; | ||
44 | +import org.thingsboard.server.dao.device.DeviceCredentialsService; | ||
45 | +import org.thingsboard.server.dao.device.DeviceProfileService; | ||
46 | +import org.thingsboard.server.dao.device.DeviceService; | ||
47 | +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; | ||
48 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | ||
49 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
50 | +import org.thingsboard.server.service.action.EntityActionService; | ||
51 | +import org.thingsboard.server.service.importing.AbstractBulkImportService; | ||
52 | +import org.thingsboard.server.service.importing.BulkImportColumnType; | ||
53 | +import org.thingsboard.server.service.importing.BulkImportRequest; | ||
54 | +import org.thingsboard.server.service.importing.ImportedEntityInfo; | ||
55 | +import org.thingsboard.server.service.security.AccessValidator; | ||
56 | +import org.thingsboard.server.service.security.model.SecurityUser; | ||
57 | +import org.thingsboard.server.service.security.permission.AccessControlService; | ||
58 | +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; | ||
59 | + | ||
60 | +import java.util.Collection; | ||
61 | +import java.util.EnumSet; | ||
62 | +import java.util.Map; | ||
63 | +import java.util.Objects; | ||
64 | +import java.util.Optional; | ||
65 | +import java.util.Set; | ||
66 | + | ||
67 | +@Service | ||
68 | +@TbCoreComponent | ||
69 | +public class DeviceBulkImportService extends AbstractBulkImportService<Device> { | ||
70 | + protected final DeviceService deviceService; | ||
71 | + protected final DeviceCredentialsService deviceCredentialsService; | ||
72 | + protected final DeviceProfileService deviceProfileService; | ||
73 | + | ||
74 | + public DeviceBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache, | ||
75 | + AccessControlService accessControlService, AccessValidator accessValidator, | ||
76 | + EntityActionService entityActionService, TbClusterService clusterService, | ||
77 | + DeviceService deviceService, DeviceCredentialsService deviceCredentialsService, | ||
78 | + DeviceProfileService deviceProfileService) { | ||
79 | + super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService); | ||
80 | + this.deviceService = deviceService; | ||
81 | + this.deviceCredentialsService = deviceCredentialsService; | ||
82 | + this.deviceProfileService = deviceProfileService; | ||
83 | + } | ||
84 | + | ||
85 | + @Override | ||
86 | + protected ImportedEntityInfo<Device> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { | ||
87 | + ImportedEntityInfo<Device> importedEntityInfo = new ImportedEntityInfo<>(); | ||
88 | + | ||
89 | + Device device = new Device(); | ||
90 | + device.setTenantId(user.getTenantId()); | ||
91 | + setDeviceFields(device, fields); | ||
92 | + | ||
93 | + Device existingDevice = deviceService.findDeviceByTenantIdAndName(user.getTenantId(), device.getName()); | ||
94 | + if (existingDevice != null && importRequest.getMapping().getUpdate()) { | ||
95 | + importedEntityInfo.setOldEntity(new Device(existingDevice)); | ||
96 | + importedEntityInfo.setUpdated(true); | ||
97 | + existingDevice.updateDevice(device); | ||
98 | + device = existingDevice; | ||
99 | + } | ||
100 | + | ||
101 | + DeviceCredentials deviceCredentials; | ||
102 | + try { | ||
103 | + deviceCredentials = createDeviceCredentials(fields); | ||
104 | + deviceCredentialsService.formatCredentials(deviceCredentials); | ||
105 | + } catch (Exception e) { | ||
106 | + throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage()); | ||
107 | + } | ||
108 | + | ||
109 | + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { | ||
110 | + setUpLwM2mDeviceProfile(user.getTenantId(), device); | ||
111 | + } | ||
112 | + | ||
113 | + device = deviceService.saveDeviceWithCredentials(device, deviceCredentials); | ||
114 | + | ||
115 | + importedEntityInfo.setEntity(device); | ||
116 | + return importedEntityInfo; | ||
117 | + } | ||
118 | + | ||
119 | + private void setDeviceFields(Device device, Map<BulkImportColumnType, String> fields) { | ||
120 | + ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(device.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); | ||
121 | + fields.forEach((columnType, value) -> { | ||
122 | + switch (columnType) { | ||
123 | + case NAME: | ||
124 | + device.setName(value); | ||
125 | + break; | ||
126 | + case TYPE: | ||
127 | + device.setType(value); | ||
128 | + break; | ||
129 | + case LABEL: | ||
130 | + device.setLabel(value); | ||
131 | + break; | ||
132 | + case DESCRIPTION: | ||
133 | + additionalInfo.set("description", new TextNode(value)); | ||
134 | + break; | ||
135 | + case IS_GATEWAY: | ||
136 | + additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value))); | ||
137 | + break; | ||
138 | + } | ||
139 | + device.setAdditionalInfo(additionalInfo); | ||
140 | + }); | ||
141 | + } | ||
142 | + | ||
143 | + @SneakyThrows | ||
144 | + private DeviceCredentials createDeviceCredentials(Map<BulkImportColumnType, String> fields) { | ||
145 | + DeviceCredentials credentials = new DeviceCredentials(); | ||
146 | + if (fields.containsKey(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT)) { | ||
147 | + credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); | ||
148 | + setUpLwm2mCredentials(fields, credentials); | ||
149 | + } else if (fields.containsKey(BulkImportColumnType.X509)) { | ||
150 | + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); | ||
151 | + setUpX509CertificateCredentials(fields, credentials); | ||
152 | + } else if (CollectionUtils.containsAny(fields.keySet(), EnumSet.of(BulkImportColumnType.MQTT_CLIENT_ID, BulkImportColumnType.MQTT_USER_NAME, BulkImportColumnType.MQTT_PASSWORD))) { | ||
153 | + credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); | ||
154 | + setUpBasicMqttCredentials(fields, credentials); | ||
155 | + } else { | ||
156 | + credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); | ||
157 | + setUpAccessTokenCredentials(fields, credentials); | ||
158 | + } | ||
159 | + return credentials; | ||
160 | + } | ||
161 | + | ||
162 | + private void setUpAccessTokenCredentials(Map<BulkImportColumnType, String> fields, DeviceCredentials credentials) { | ||
163 | + credentials.setCredentialsId(Optional.ofNullable(fields.get(BulkImportColumnType.ACCESS_TOKEN)) | ||
164 | + .orElseGet(() -> RandomStringUtils.randomAlphanumeric(20))); | ||
165 | + } | ||
166 | + | ||
167 | + private void setUpBasicMqttCredentials(Map<BulkImportColumnType, String> fields, DeviceCredentials credentials) { | ||
168 | + BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); | ||
169 | + basicMqttCredentials.setClientId(fields.get(BulkImportColumnType.MQTT_CLIENT_ID)); | ||
170 | + basicMqttCredentials.setUserName(fields.get(BulkImportColumnType.MQTT_USER_NAME)); | ||
171 | + basicMqttCredentials.setPassword(fields.get(BulkImportColumnType.MQTT_PASSWORD)); | ||
172 | + credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); | ||
173 | + } | ||
174 | + | ||
175 | + private void setUpX509CertificateCredentials(Map<BulkImportColumnType, String> fields, DeviceCredentials credentials) { | ||
176 | + credentials.setCredentialsValue(fields.get(BulkImportColumnType.X509)); | ||
177 | + } | ||
178 | + | ||
179 | + private void setUpLwm2mCredentials(Map<BulkImportColumnType, String> fields, DeviceCredentials credentials) throws com.fasterxml.jackson.core.JsonProcessingException { | ||
180 | + ObjectNode lwm2mCredentials = JacksonUtil.newObjectNode(); | ||
181 | + | ||
182 | + Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE, BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, | ||
183 | + BulkImportColumnType.LWM2M_SERVER_SECURITY_MODE).stream() | ||
184 | + .map(fields::get) | ||
185 | + .filter(Objects::nonNull) | ||
186 | + .forEach(securityMode -> { | ||
187 | + try { | ||
188 | + LwM2MSecurityMode.valueOf(securityMode.toUpperCase()); | ||
189 | + } catch (IllegalArgumentException e) { | ||
190 | + throw new DeviceCredentialsValidationException("Unknown LwM2M security mode: " + securityMode + ", (the mode should be: NO_SEC, PSK, RPK, X509)!"); | ||
191 | + } | ||
192 | + }); | ||
193 | + | ||
194 | + ObjectNode client = JacksonUtil.newObjectNode(); | ||
195 | + setValues(client, fields, Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE, | ||
196 | + BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_IDENTITY, | ||
197 | + BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT)); | ||
198 | + LwM2MClientCredentials lwM2MClientCredentials = JacksonUtil.treeToValue(client, LwM2MClientCredentials.class); | ||
199 | + // so that only fields needed for specific type of lwM2MClientCredentials were saved in json | ||
200 | + lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredentials)); | ||
201 | + | ||
202 | + ObjectNode bootstrapServer = JacksonUtil.newObjectNode(); | ||
203 | + setValues(bootstrapServer, fields, Set.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, | ||
204 | + BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID, BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECRET_KEY)); | ||
205 | + | ||
206 | + ObjectNode lwm2mServer = JacksonUtil.newObjectNode(); | ||
207 | + setValues(lwm2mServer, fields, Set.of(BulkImportColumnType.LWM2M_SERVER_SECURITY_MODE, | ||
208 | + BulkImportColumnType.LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID, BulkImportColumnType.LWM2M_SERVER_CLIENT_SECRET_KEY)); | ||
209 | + | ||
210 | + ObjectNode bootstrap = JacksonUtil.newObjectNode(); | ||
211 | + bootstrap.set("bootstrapServer", bootstrapServer); | ||
212 | + bootstrap.set("lwm2mServer", lwm2mServer); | ||
213 | + lwm2mCredentials.set("bootstrap", bootstrap); | ||
214 | + | ||
215 | + credentials.setCredentialsValue(lwm2mCredentials.toString()); | ||
216 | + } | ||
217 | + | ||
218 | + private void setUpLwM2mDeviceProfile(TenantId tenantId, Device device) { | ||
219 | + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByName(tenantId, device.getType()); | ||
220 | + if (deviceProfile != null) { | ||
221 | + if (deviceProfile.getTransportType() != DeviceTransportType.LWM2M) { | ||
222 | + deviceProfile.setTransportType(DeviceTransportType.LWM2M); | ||
223 | + deviceProfile.getProfileData().setTransportConfiguration(new Lwm2mDeviceProfileTransportConfiguration()); | ||
224 | + deviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); | ||
225 | + device.setDeviceProfileId(deviceProfile.getId()); | ||
226 | + } | ||
227 | + } else { | ||
228 | + deviceProfile = new DeviceProfile(); | ||
229 | + deviceProfile.setTenantId(tenantId); | ||
230 | + deviceProfile.setType(DeviceProfileType.DEFAULT); | ||
231 | + deviceProfile.setName(device.getType()); | ||
232 | + deviceProfile.setTransportType(DeviceTransportType.LWM2M); | ||
233 | + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); | ||
234 | + | ||
235 | + DeviceProfileData deviceProfileData = new DeviceProfileData(); | ||
236 | + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); | ||
237 | + DeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); | ||
238 | + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); | ||
239 | + | ||
240 | + deviceProfileData.setConfiguration(configuration); | ||
241 | + deviceProfileData.setTransportConfiguration(transportConfiguration); | ||
242 | + deviceProfileData.setProvisionConfiguration(provisionConfiguration); | ||
243 | + deviceProfile.setProfileData(deviceProfileData); | ||
244 | + | ||
245 | + deviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); | ||
246 | + device.setDeviceProfileId(deviceProfile.getId()); | ||
247 | + } | ||
248 | + } | ||
249 | + | ||
250 | + private void setValues(ObjectNode objectNode, Map<BulkImportColumnType, String> data, Collection<BulkImportColumnType> columns) { | ||
251 | + for (BulkImportColumnType column : columns) { | ||
252 | + String value = StringUtils.defaultString(data.get(column), column.getDefaultValue()); | ||
253 | + if (value != null && column.getKey() != null) { | ||
254 | + objectNode.set(column.getKey(), new TextNode(value)); | ||
255 | + } | ||
256 | + } | ||
257 | + } | ||
258 | + | ||
259 | +} |
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.edge; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
19 | +import com.fasterxml.jackson.databind.node.TextNode; | ||
20 | +import org.springframework.stereotype.Service; | ||
21 | +import org.thingsboard.common.util.JacksonUtil; | ||
22 | +import org.thingsboard.server.cluster.TbClusterService; | ||
23 | +import org.thingsboard.server.common.data.edge.Edge; | ||
24 | +import org.thingsboard.server.dao.edge.EdgeService; | ||
25 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | ||
26 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
27 | +import org.thingsboard.server.service.action.EntityActionService; | ||
28 | +import org.thingsboard.server.service.importing.AbstractBulkImportService; | ||
29 | +import org.thingsboard.server.service.importing.BulkImportColumnType; | ||
30 | +import org.thingsboard.server.service.importing.BulkImportRequest; | ||
31 | +import org.thingsboard.server.service.importing.ImportedEntityInfo; | ||
32 | +import org.thingsboard.server.service.security.AccessValidator; | ||
33 | +import org.thingsboard.server.service.security.model.SecurityUser; | ||
34 | +import org.thingsboard.server.service.security.permission.AccessControlService; | ||
35 | +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; | ||
36 | + | ||
37 | +import java.util.Map; | ||
38 | +import java.util.Optional; | ||
39 | + | ||
40 | +@Service | ||
41 | +@TbCoreComponent | ||
42 | +public class EdgeBulkImportService extends AbstractBulkImportService<Edge> { | ||
43 | + private final EdgeService edgeService; | ||
44 | + | ||
45 | + public EdgeBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache, | ||
46 | + AccessControlService accessControlService, AccessValidator accessValidator, | ||
47 | + EntityActionService entityActionService, TbClusterService clusterService, EdgeService edgeService) { | ||
48 | + super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService); | ||
49 | + this.edgeService = edgeService; | ||
50 | + } | ||
51 | + | ||
52 | + @Override | ||
53 | + protected ImportedEntityInfo<Edge> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { | ||
54 | + ImportedEntityInfo<Edge> importedEntityInfo = new ImportedEntityInfo<>(); | ||
55 | + | ||
56 | + Edge edge = new Edge(); | ||
57 | + edge.setTenantId(user.getTenantId()); | ||
58 | + setEdgeFields(edge, fields); | ||
59 | + | ||
60 | + Edge existingEdge = edgeService.findEdgeByTenantIdAndName(user.getTenantId(), edge.getName()); | ||
61 | + if (existingEdge != null && importRequest.getMapping().getUpdate()) { | ||
62 | + importedEntityInfo.setOldEntity(new Edge(existingEdge)); | ||
63 | + importedEntityInfo.setUpdated(true); | ||
64 | + existingEdge.update(edge); | ||
65 | + edge = existingEdge; | ||
66 | + } | ||
67 | + edge = edgeService.saveEdge(edge, true); | ||
68 | + | ||
69 | + importedEntityInfo.setEntity(edge); | ||
70 | + return importedEntityInfo; | ||
71 | + } | ||
72 | + | ||
73 | + private void setEdgeFields(Edge edge, Map<BulkImportColumnType, String> fields) { | ||
74 | + ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(edge.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); | ||
75 | + fields.forEach((columnType, value) -> { | ||
76 | + switch (columnType) { | ||
77 | + case NAME: | ||
78 | + edge.setName(value); | ||
79 | + break; | ||
80 | + case TYPE: | ||
81 | + edge.setType(value); | ||
82 | + break; | ||
83 | + case LABEL: | ||
84 | + edge.setLabel(value); | ||
85 | + break; | ||
86 | + case DESCRIPTION: | ||
87 | + additionalInfo.set("description", new TextNode(value)); | ||
88 | + break; | ||
89 | + case EDGE_LICENSE_KEY: | ||
90 | + edge.setEdgeLicenseKey(value); | ||
91 | + break; | ||
92 | + case CLOUD_ENDPOINT: | ||
93 | + edge.setCloudEndpoint(value); | ||
94 | + break; | ||
95 | + case ROUTING_KEY: | ||
96 | + edge.setRoutingKey(value); | ||
97 | + break; | ||
98 | + case SECRET: | ||
99 | + edge.setSecret(value); | ||
100 | + break; | ||
101 | + } | ||
102 | + }); | ||
103 | + edge.setAdditionalInfo(additionalInfo); | ||
104 | + } | ||
105 | + | ||
106 | +} |
application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.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.importing; | ||
17 | + | ||
18 | +import com.google.common.util.concurrent.FutureCallback; | ||
19 | +import com.google.gson.JsonObject; | ||
20 | +import com.google.gson.JsonPrimitive; | ||
21 | +import lombok.Data; | ||
22 | +import lombok.RequiredArgsConstructor; | ||
23 | +import lombok.SneakyThrows; | ||
24 | +import org.apache.commons.lang3.StringUtils; | ||
25 | +import org.thingsboard.server.cluster.TbClusterService; | ||
26 | +import org.thingsboard.server.common.data.BaseData; | ||
27 | +import org.thingsboard.server.common.data.TenantProfile; | ||
28 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
29 | +import org.thingsboard.server.common.data.id.EntityId; | ||
30 | +import org.thingsboard.server.common.data.id.UUIDBased; | ||
31 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
32 | +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; | ||
33 | +import org.thingsboard.server.common.data.kv.DataType; | ||
34 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | ||
35 | +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; | ||
36 | +import org.thingsboard.server.common.transport.adaptor.JsonConverter; | ||
37 | +import org.thingsboard.server.controller.BaseController; | ||
38 | +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | ||
39 | +import org.thingsboard.server.service.action.EntityActionService; | ||
40 | +import org.thingsboard.server.service.importing.BulkImportRequest.ColumnMapping; | ||
41 | +import org.thingsboard.server.service.security.AccessValidator; | ||
42 | +import org.thingsboard.server.service.security.model.SecurityUser; | ||
43 | +import org.thingsboard.server.service.security.permission.AccessControlService; | ||
44 | +import org.thingsboard.server.service.security.permission.Operation; | ||
45 | +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; | ||
46 | +import org.thingsboard.server.utils.CsvUtils; | ||
47 | +import org.thingsboard.server.utils.TypeCastUtil; | ||
48 | + | ||
49 | +import javax.annotation.Nullable; | ||
50 | +import java.util.ArrayList; | ||
51 | +import java.util.Arrays; | ||
52 | +import java.util.LinkedHashMap; | ||
53 | +import java.util.List; | ||
54 | +import java.util.Map; | ||
55 | +import java.util.concurrent.TimeUnit; | ||
56 | +import java.util.concurrent.atomic.AtomicInteger; | ||
57 | +import java.util.function.Consumer; | ||
58 | +import java.util.stream.Collectors; | ||
59 | +import java.util.stream.Stream; | ||
60 | + | ||
61 | +@RequiredArgsConstructor | ||
62 | +public abstract class AbstractBulkImportService<E extends BaseData<? extends EntityId>> { | ||
63 | + protected final TelemetrySubscriptionService tsSubscriptionService; | ||
64 | + protected final TbTenantProfileCache tenantProfileCache; | ||
65 | + protected final AccessControlService accessControlService; | ||
66 | + protected final AccessValidator accessValidator; | ||
67 | + protected final EntityActionService entityActionService; | ||
68 | + protected final TbClusterService clusterService; | ||
69 | + | ||
70 | + public final BulkImportResult<E> processBulkImport(BulkImportRequest request, SecurityUser user, Consumer<ImportedEntityInfo<E>> onEntityImported) throws Exception { | ||
71 | + BulkImportResult<E> result = new BulkImportResult<>(); | ||
72 | + | ||
73 | + AtomicInteger i = new AtomicInteger(0); | ||
74 | + if (request.getMapping().getHeader()) { | ||
75 | + i.incrementAndGet(); | ||
76 | + } | ||
77 | + | ||
78 | + parseData(request).forEach(entityData -> { | ||
79 | + i.incrementAndGet(); | ||
80 | + try { | ||
81 | + ImportedEntityInfo<E> importedEntityInfo = saveEntity(request, entityData.getFields(), user); | ||
82 | + onEntityImported.accept(importedEntityInfo); | ||
83 | + | ||
84 | + E entity = importedEntityInfo.getEntity(); | ||
85 | + | ||
86 | + saveKvs(user, entity, entityData.getKvs()); | ||
87 | + | ||
88 | + if (importedEntityInfo.getRelatedError() != null) { | ||
89 | + throw new RuntimeException(importedEntityInfo.getRelatedError()); | ||
90 | + } | ||
91 | + | ||
92 | + if (importedEntityInfo.isUpdated()) { | ||
93 | + result.setUpdated(result.getUpdated() + 1); | ||
94 | + } else { | ||
95 | + result.setCreated(result.getCreated() + 1); | ||
96 | + } | ||
97 | + } catch (Exception e) { | ||
98 | + result.setErrors(result.getErrors() + 1); | ||
99 | + result.getErrorsList().add(String.format("Line %d: %s", i.get(), e.getMessage())); | ||
100 | + } | ||
101 | + }); | ||
102 | + | ||
103 | + return result; | ||
104 | + } | ||
105 | + | ||
106 | + protected abstract ImportedEntityInfo<E> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user); | ||
107 | + | ||
108 | + /* | ||
109 | + * Attributes' values are firstly added to JsonObject in order to then make some type cast, | ||
110 | + * because we get all values as strings from CSV | ||
111 | + * */ | ||
112 | + private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, ParsedValue> data) { | ||
113 | + Arrays.stream(BulkImportColumnType.values()) | ||
114 | + .filter(BulkImportColumnType::isKv) | ||
115 | + .map(kvType -> { | ||
116 | + JsonObject kvs = new JsonObject(); | ||
117 | + data.entrySet().stream() | ||
118 | + .filter(dataEntry -> dataEntry.getKey().getType() == kvType && | ||
119 | + StringUtils.isNotEmpty(dataEntry.getKey().getKey())) | ||
120 | + .forEach(dataEntry -> kvs.add(dataEntry.getKey().getKey(), dataEntry.getValue().toJsonPrimitive())); | ||
121 | + return Map.entry(kvType, kvs); | ||
122 | + }) | ||
123 | + .filter(kvsEntry -> kvsEntry.getValue().entrySet().size() > 0) | ||
124 | + .forEach(kvsEntry -> { | ||
125 | + BulkImportColumnType kvType = kvsEntry.getKey(); | ||
126 | + if (kvType == BulkImportColumnType.SHARED_ATTRIBUTE || kvType == BulkImportColumnType.SERVER_ATTRIBUTE) { | ||
127 | + saveAttributes(user, entity, kvsEntry, kvType); | ||
128 | + } else { | ||
129 | + saveTelemetry(user, entity, kvsEntry); | ||
130 | + } | ||
131 | + }); | ||
132 | + } | ||
133 | + | ||
134 | + @SneakyThrows | ||
135 | + private void saveTelemetry(SecurityUser user, E entity, Map.Entry<BulkImportColumnType, JsonObject> kvsEntry) { | ||
136 | + List<TsKvEntry> timeseries = JsonConverter.convertToTelemetry(kvsEntry.getValue(), System.currentTimeMillis()) | ||
137 | + .entrySet().stream() | ||
138 | + .flatMap(entry -> entry.getValue().stream().map(kvEntry -> new BasicTsKvEntry(entry.getKey(), kvEntry))) | ||
139 | + .collect(Collectors.toList()); | ||
140 | + | ||
141 | + accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entity.getId(), (result, tenantId, entityId) -> { | ||
142 | + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); | ||
143 | + long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); | ||
144 | + tsSubscriptionService.saveAndNotify(tenantId, user.getCustomerId(), entityId, timeseries, tenantTtl, new FutureCallback<Void>() { | ||
145 | + @Override | ||
146 | + public void onSuccess(@Nullable Void tmp) { | ||
147 | + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, | ||
148 | + ActionType.TIMESERIES_UPDATED, null, timeseries); | ||
149 | + } | ||
150 | + | ||
151 | + @Override | ||
152 | + public void onFailure(Throwable t) { | ||
153 | + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, | ||
154 | + ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries); | ||
155 | + throw new RuntimeException(t); | ||
156 | + } | ||
157 | + }); | ||
158 | + }); | ||
159 | + } | ||
160 | + | ||
161 | + @SneakyThrows | ||
162 | + private void saveAttributes(SecurityUser user, E entity, Map.Entry<BulkImportColumnType, JsonObject> kvsEntry, BulkImportColumnType kvType) { | ||
163 | + String scope = kvType.getKey(); | ||
164 | + List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue())); | ||
165 | + | ||
166 | + accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> { | ||
167 | + tsSubscriptionService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<>() { | ||
168 | + | ||
169 | + @Override | ||
170 | + public void onSuccess(Void unused) { | ||
171 | + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, | ||
172 | + null, ActionType.ATTRIBUTES_UPDATED, null, scope, attributes); | ||
173 | + } | ||
174 | + | ||
175 | + @Override | ||
176 | + public void onFailure(Throwable throwable) { | ||
177 | + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, | ||
178 | + null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable), | ||
179 | + scope, attributes); | ||
180 | + throw new RuntimeException(throwable); | ||
181 | + } | ||
182 | + | ||
183 | + }); | ||
184 | + }); | ||
185 | + } | ||
186 | + | ||
187 | + private List<EntityData> parseData(BulkImportRequest request) throws Exception { | ||
188 | + List<List<String>> records = CsvUtils.parseCsv(request.getFile(), request.getMapping().getDelimiter()); | ||
189 | + if (request.getMapping().getHeader()) { | ||
190 | + records.remove(0); | ||
191 | + } | ||
192 | + | ||
193 | + List<ColumnMapping> columnsMappings = request.getMapping().getColumns(); | ||
194 | + return records.stream() | ||
195 | + .map(record -> { | ||
196 | + EntityData entityData = new EntityData(); | ||
197 | + Stream.iterate(0, i -> i < record.size(), i -> i + 1) | ||
198 | + .map(i -> Map.entry(columnsMappings.get(i), record.get(i))) | ||
199 | + .filter(entry -> StringUtils.isNotEmpty(entry.getValue())) | ||
200 | + .forEach(entry -> { | ||
201 | + if (!entry.getKey().getType().isKv()) { | ||
202 | + entityData.getFields().put(entry.getKey().getType(), entry.getValue()); | ||
203 | + } else { | ||
204 | + Map.Entry<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue()); | ||
205 | + entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey())); | ||
206 | + } | ||
207 | + }); | ||
208 | + return entityData; | ||
209 | + }) | ||
210 | + .collect(Collectors.toList()); | ||
211 | + } | ||
212 | + | ||
213 | + @Data | ||
214 | + protected static class EntityData { | ||
215 | + private final Map<BulkImportColumnType, String> fields = new LinkedHashMap<>(); | ||
216 | + private final Map<ColumnMapping, ParsedValue> kvs = new LinkedHashMap<>(); | ||
217 | + } | ||
218 | + | ||
219 | + @Data | ||
220 | + protected static class ParsedValue { | ||
221 | + private final Object value; | ||
222 | + private final DataType dataType; | ||
223 | + | ||
224 | + public JsonPrimitive toJsonPrimitive() { | ||
225 | + switch (dataType) { | ||
226 | + case STRING: | ||
227 | + return new JsonPrimitive((String) value); | ||
228 | + case LONG: | ||
229 | + return new JsonPrimitive((Long) value); | ||
230 | + case DOUBLE: | ||
231 | + return new JsonPrimitive((Double) value); | ||
232 | + case BOOLEAN: | ||
233 | + return new JsonPrimitive((Boolean) value); | ||
234 | + default: | ||
235 | + return null; | ||
236 | + } | ||
237 | + } | ||
238 | + | ||
239 | + public String stringValue() { | ||
240 | + return value.toString(); | ||
241 | + } | ||
242 | + | ||
243 | + } | ||
244 | + | ||
245 | +} |
application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.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.importing; | ||
17 | + | ||
18 | +import lombok.Getter; | ||
19 | +import org.thingsboard.server.common.data.DataConstants; | ||
20 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; | ||
21 | + | ||
22 | +@Getter | ||
23 | +public enum BulkImportColumnType { | ||
24 | + NAME, | ||
25 | + TYPE, | ||
26 | + LABEL, | ||
27 | + SHARED_ATTRIBUTE(DataConstants.SHARED_SCOPE, true), | ||
28 | + SERVER_ATTRIBUTE(DataConstants.SERVER_SCOPE, true), | ||
29 | + TIMESERIES(true), | ||
30 | + ACCESS_TOKEN, | ||
31 | + X509, | ||
32 | + MQTT_CLIENT_ID, | ||
33 | + MQTT_USER_NAME, | ||
34 | + MQTT_PASSWORD, | ||
35 | + LWM2M_CLIENT_ENDPOINT("endpoint"), | ||
36 | + LWM2M_CLIENT_SECURITY_CONFIG_MODE("securityConfigClientMode", LwM2MSecurityMode.NO_SEC.name()), | ||
37 | + LWM2M_CLIENT_IDENTITY("identity"), | ||
38 | + LWM2M_CLIENT_KEY("key"), | ||
39 | + LWM2M_CLIENT_CERT("cert"), | ||
40 | + LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()), | ||
41 | + LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"), | ||
42 | + LWM2M_BOOTSTRAP_SERVER_SECRET_KEY("clientSecretKey"), | ||
43 | + LWM2M_SERVER_SECURITY_MODE("securityMode", LwM2MSecurityMode.NO_SEC.name()), | ||
44 | + LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"), | ||
45 | + LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"), | ||
46 | + IS_GATEWAY, | ||
47 | + DESCRIPTION, | ||
48 | + EDGE_LICENSE_KEY, | ||
49 | + CLOUD_ENDPOINT, | ||
50 | + ROUTING_KEY, | ||
51 | + SECRET; | ||
52 | + | ||
53 | + private String key; | ||
54 | + private String defaultValue; | ||
55 | + private boolean isKv = false; | ||
56 | + | ||
57 | + BulkImportColumnType() { | ||
58 | + } | ||
59 | + | ||
60 | + BulkImportColumnType(String key) { | ||
61 | + this.key = key; | ||
62 | + } | ||
63 | + | ||
64 | + BulkImportColumnType(String key, String defaultValue) { | ||
65 | + this.key = key; | ||
66 | + this.defaultValue = defaultValue; | ||
67 | + } | ||
68 | + | ||
69 | + BulkImportColumnType(boolean isKv) { | ||
70 | + this.isKv = isKv; | ||
71 | + } | ||
72 | + | ||
73 | + BulkImportColumnType(String key, boolean isKv) { | ||
74 | + this.key = key; | ||
75 | + this.isKv = isKv; | ||
76 | + } | ||
77 | +} |
application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.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.importing; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.util.List; | ||
21 | + | ||
22 | +@Data | ||
23 | +public class BulkImportRequest { | ||
24 | + private String file; | ||
25 | + private Mapping mapping; | ||
26 | + | ||
27 | + @Data | ||
28 | + public static class Mapping { | ||
29 | + private List<ColumnMapping> columns; | ||
30 | + private Character delimiter; | ||
31 | + private Boolean update; | ||
32 | + private Boolean header; | ||
33 | + } | ||
34 | + | ||
35 | + @Data | ||
36 | + public static class ColumnMapping { | ||
37 | + private BulkImportColumnType type; | ||
38 | + private String key; | ||
39 | + } | ||
40 | + | ||
41 | +} |
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.importing; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +import java.util.LinkedList; | ||
21 | +import java.util.List; | ||
22 | + | ||
23 | +@Data | ||
24 | +public class BulkImportResult<E> { | ||
25 | + private int created = 0; | ||
26 | + private int updated = 0; | ||
27 | + private int errors = 0; | ||
28 | + private List<String> errorsList = new LinkedList<>(); | ||
29 | + | ||
30 | +} |
application/src/main/java/org/thingsboard/server/service/importing/ImportedEntityInfo.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.importing; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +@Data | ||
21 | +public class ImportedEntityInfo<E> { | ||
22 | + private E entity; | ||
23 | + private boolean isUpdated; | ||
24 | + private E oldEntity; | ||
25 | + private String relatedError; | ||
26 | +} |
@@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.id.AlarmId; | @@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.id.AlarmId; | ||
26 | import org.thingsboard.server.common.data.id.TenantId; | 26 | import org.thingsboard.server.common.data.id.TenantId; |
27 | import org.thingsboard.server.common.data.page.PageData; | 27 | import org.thingsboard.server.common.data.page.PageData; |
28 | import org.thingsboard.server.common.data.page.PageLink; | 28 | import org.thingsboard.server.common.data.page.PageLink; |
29 | -import org.thingsboard.server.common.data.page.SortOrder; | ||
30 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; | 29 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
31 | import org.thingsboard.server.common.msg.queue.ServiceType; | 30 | import org.thingsboard.server.common.msg.queue.ServiceType; |
32 | import org.thingsboard.server.dao.alarm.AlarmDao; | 31 | import org.thingsboard.server.dao.alarm.AlarmDao; |
@@ -34,17 +33,12 @@ import org.thingsboard.server.dao.alarm.AlarmService; | @@ -34,17 +33,12 @@ import org.thingsboard.server.dao.alarm.AlarmService; | ||
34 | import org.thingsboard.server.dao.relation.RelationService; | 33 | import org.thingsboard.server.dao.relation.RelationService; |
35 | import org.thingsboard.server.dao.tenant.TbTenantProfileCache; | 34 | import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
36 | import org.thingsboard.server.dao.tenant.TenantDao; | 35 | import org.thingsboard.server.dao.tenant.TenantDao; |
37 | -import org.thingsboard.server.dao.util.PsqlDao; | ||
38 | import org.thingsboard.server.queue.discovery.PartitionService; | 36 | import org.thingsboard.server.queue.discovery.PartitionService; |
39 | import org.thingsboard.server.queue.util.TbCoreComponent; | 37 | import org.thingsboard.server.queue.util.TbCoreComponent; |
40 | -import org.thingsboard.server.service.action.RuleEngineEntityActionService; | ||
41 | -import org.thingsboard.server.service.ttl.AbstractCleanUpService; | 38 | +import org.thingsboard.server.service.action.EntityActionService; |
42 | 39 | ||
43 | -import java.sql.Connection; | ||
44 | -import java.sql.SQLException; | ||
45 | import java.util.Date; | 40 | import java.util.Date; |
46 | import java.util.Optional; | 41 | import java.util.Optional; |
47 | -import java.util.UUID; | ||
48 | import java.util.concurrent.TimeUnit; | 42 | import java.util.concurrent.TimeUnit; |
49 | 43 | ||
50 | @TbCoreComponent | 44 | @TbCoreComponent |
@@ -60,7 +54,7 @@ public class AlarmsCleanUpService { | @@ -60,7 +54,7 @@ public class AlarmsCleanUpService { | ||
60 | private final AlarmDao alarmDao; | 54 | private final AlarmDao alarmDao; |
61 | private final AlarmService alarmService; | 55 | private final AlarmService alarmService; |
62 | private final RelationService relationService; | 56 | private final RelationService relationService; |
63 | - private final RuleEngineEntityActionService ruleEngineEntityActionService; | 57 | + private final EntityActionService entityActionService; |
64 | private final PartitionService partitionService; | 58 | private final PartitionService partitionService; |
65 | private final TbTenantProfileCache tenantProfileCache; | 59 | private final TbTenantProfileCache tenantProfileCache; |
66 | 60 | ||
@@ -90,7 +84,7 @@ public class AlarmsCleanUpService { | @@ -90,7 +84,7 @@ public class AlarmsCleanUpService { | ||
90 | toRemove.getData().forEach(alarmId -> { | 84 | toRemove.getData().forEach(alarmId -> { |
91 | relationService.deleteEntityRelations(tenantId, alarmId); | 85 | relationService.deleteEntityRelations(tenantId, alarmId); |
92 | Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm(); | 86 | Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm(); |
93 | - ruleEngineEntityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null); | 87 | + entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null); |
94 | }); | 88 | }); |
95 | 89 | ||
96 | totalRemoved += toRemove.getTotalElements(); | 90 | totalRemoved += toRemove.getTotalElements(); |
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.utils; | ||
17 | + | ||
18 | +import lombok.AccessLevel; | ||
19 | +import lombok.NoArgsConstructor; | ||
20 | +import org.apache.commons.csv.CSVFormat; | ||
21 | +import org.apache.commons.csv.CSVRecord; | ||
22 | +import org.apache.commons.io.input.CharSequenceReader; | ||
23 | + | ||
24 | +import java.util.List; | ||
25 | +import java.util.stream.Collectors; | ||
26 | +import java.util.stream.Stream; | ||
27 | + | ||
28 | +@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
29 | +public class CsvUtils { | ||
30 | + | ||
31 | + public static List<List<String>> parseCsv(String content, Character delimiter) throws Exception { | ||
32 | + CSVFormat csvFormat = delimiter.equals(',') ? CSVFormat.DEFAULT : CSVFormat.DEFAULT.withDelimiter(delimiter); | ||
33 | + | ||
34 | + List<CSVRecord> records; | ||
35 | + try (CharSequenceReader reader = new CharSequenceReader(content)) { | ||
36 | + records = csvFormat.parse(reader).getRecords(); | ||
37 | + } | ||
38 | + | ||
39 | + return records.stream() | ||
40 | + .map(record -> Stream.iterate(0, i -> i < record.size(), i -> i + 1) | ||
41 | + .map(record::get) | ||
42 | + .collect(Collectors.toList())) | ||
43 | + .collect(Collectors.toList()); | ||
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.utils; | ||
17 | + | ||
18 | +import org.apache.commons.lang3.math.NumberUtils; | ||
19 | +import org.thingsboard.server.common.data.kv.DataType; | ||
20 | + | ||
21 | +import java.math.BigDecimal; | ||
22 | +import java.util.Map; | ||
23 | + | ||
24 | +public class TypeCastUtil { | ||
25 | + | ||
26 | + private TypeCastUtil() {} | ||
27 | + | ||
28 | + public static Map.Entry<DataType, Object> castValue(String value) { | ||
29 | + if (isNumber(value)) { | ||
30 | + String formattedValue = value.replace(',', '.'); | ||
31 | + try { | ||
32 | + BigDecimal bd = new BigDecimal(formattedValue); | ||
33 | + if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) { | ||
34 | + if (bd.scale() <= 16) { | ||
35 | + return Map.entry(DataType.DOUBLE, bd.doubleValue()); | ||
36 | + } | ||
37 | + } else { | ||
38 | + return Map.entry(DataType.LONG, bd.longValueExact()); | ||
39 | + } | ||
40 | + } catch (RuntimeException ignored) {} | ||
41 | + } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { | ||
42 | + return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value)); | ||
43 | + } | ||
44 | + return Map.entry(DataType.STRING, value); | ||
45 | + } | ||
46 | + | ||
47 | + private static boolean isNumber(String value) { | ||
48 | + return NumberUtils.isNumber(value.replace(',', '.')); | ||
49 | + } | ||
50 | + | ||
51 | + private static boolean isSimpleDouble(String valueAsString) { | ||
52 | + return valueAsString.contains(".") && !valueAsString.contains("E") && !valueAsString.contains("e"); | ||
53 | + } | ||
54 | + | ||
55 | +} |
@@ -29,5 +29,7 @@ public interface DeviceCredentialsService { | @@ -29,5 +29,7 @@ public interface DeviceCredentialsService { | ||
29 | 29 | ||
30 | DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); | 30 | DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); |
31 | 31 | ||
32 | + void formatCredentials(DeviceCredentials deviceCredentials); | ||
33 | + | ||
32 | void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); | 34 | void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials); |
33 | } | 35 | } |
@@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.validation.NoXss; | @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.validation.NoXss; | ||
29 | 29 | ||
30 | import java.io.ByteArrayInputStream; | 30 | import java.io.ByteArrayInputStream; |
31 | import java.io.IOException; | 31 | import java.io.IOException; |
32 | +import java.util.Optional; | ||
32 | 33 | ||
33 | @EqualsAndHashCode(callSuper = true) | 34 | @EqualsAndHashCode(callSuper = true) |
34 | @Slf4j | 35 | @Slf4j |
@@ -83,6 +84,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen | @@ -83,6 +84,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen | ||
83 | this.setDeviceData(device.getDeviceData()); | 84 | this.setDeviceData(device.getDeviceData()); |
84 | this.setFirmwareId(device.getFirmwareId()); | 85 | this.setFirmwareId(device.getFirmwareId()); |
85 | this.setSoftwareId(device.getSoftwareId()); | 86 | this.setSoftwareId(device.getSoftwareId()); |
87 | + Optional.ofNullable(device.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); | ||
86 | return this; | 88 | return this; |
87 | } | 89 | } |
88 | 90 |
@@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.id.CustomerId; | @@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.id.CustomerId; | ||
25 | import org.thingsboard.server.common.data.id.TenantId; | 25 | import org.thingsboard.server.common.data.id.TenantId; |
26 | import org.thingsboard.server.common.data.validation.NoXss; | 26 | import org.thingsboard.server.common.data.validation.NoXss; |
27 | 27 | ||
28 | +import java.util.Optional; | ||
29 | + | ||
28 | @EqualsAndHashCode(callSuper = true) | 30 | @EqualsAndHashCode(callSuper = true) |
29 | public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId { | 31 | public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId { |
30 | 32 | ||
@@ -56,6 +58,15 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements | @@ -56,6 +58,15 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements | ||
56 | this.label = asset.getLabel(); | 58 | this.label = asset.getLabel(); |
57 | } | 59 | } |
58 | 60 | ||
61 | + public void update(Asset asset) { | ||
62 | + this.tenantId = asset.getTenantId(); | ||
63 | + this.customerId = asset.getCustomerId(); | ||
64 | + this.name = asset.getName(); | ||
65 | + this.type = asset.getType(); | ||
66 | + this.label = asset.getLabel(); | ||
67 | + Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); | ||
68 | + } | ||
69 | + | ||
59 | public TenantId getTenantId() { | 70 | public TenantId getTenantId() { |
60 | return tenantId; | 71 | return tenantId; |
61 | } | 72 | } |
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.device.credentials.lwm2m; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.annotation.JsonIgnore; | ||
19 | +import lombok.Getter; | ||
20 | +import lombok.Setter; | ||
21 | +import lombok.SneakyThrows; | ||
22 | +import org.apache.commons.codec.binary.Hex; | ||
23 | + | ||
24 | +@Getter | ||
25 | +@Setter | ||
26 | +public abstract class AbstractLwM2MServerCredentialsWithKeys implements LwM2MServerCredentials { | ||
27 | + | ||
28 | + private String clientPublicKeyOrId; | ||
29 | + private String clientSecretKey; | ||
30 | + | ||
31 | + @JsonIgnore | ||
32 | + public byte[] getDecodedClientPublicKeyOrId() { | ||
33 | + return getDecoded(clientPublicKeyOrId); | ||
34 | + } | ||
35 | + | ||
36 | + @JsonIgnore | ||
37 | + public byte[] getDecodedClientSecretKey() { | ||
38 | + return getDecoded(clientSecretKey); | ||
39 | + } | ||
40 | + | ||
41 | + @SneakyThrows | ||
42 | + private static byte[] getDecoded(String key) { | ||
43 | + return Hex.decodeHex(key.toLowerCase().toCharArray()); | ||
44 | + } | ||
45 | +} |
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.device.credentials.lwm2m; | ||
17 | + | ||
18 | +import lombok.Getter; | ||
19 | +import lombok.Setter; | ||
20 | + | ||
21 | +@Getter | ||
22 | +@Setter | ||
23 | +public class LwM2MBootstrapCredentials { | ||
24 | + private LwM2MServerCredentials bootstrapServer; | ||
25 | + private LwM2MServerCredentials lwm2mServer; | ||
26 | +} |
@@ -16,6 +16,7 @@ | @@ -16,6 +16,7 @@ | ||
16 | package org.thingsboard.server.common.data.device.credentials.lwm2m; | 16 | package org.thingsboard.server.common.data.device.credentials.lwm2m; |
17 | 17 | ||
18 | import com.fasterxml.jackson.annotation.JsonIgnore; | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; |
19 | +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
19 | import com.fasterxml.jackson.annotation.JsonSubTypes; | 20 | import com.fasterxml.jackson.annotation.JsonSubTypes; |
20 | import com.fasterxml.jackson.annotation.JsonTypeInfo; | 21 | import com.fasterxml.jackson.annotation.JsonTypeInfo; |
21 | 22 | ||
@@ -26,7 +27,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; | @@ -26,7 +27,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
26 | @JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"), | 27 | @JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"), |
27 | @JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"), | 28 | @JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"), |
28 | @JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"), | 29 | @JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"), |
29 | - @JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509")}) | 30 | + @JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509") |
31 | +}) | ||
32 | +@JsonIgnoreProperties(ignoreUnknown = true) | ||
30 | public interface LwM2MClientCredentials { | 33 | public interface LwM2MClientCredentials { |
31 | 34 | ||
32 | @JsonIgnore | 35 | @JsonIgnore |
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.device.credentials.lwm2m; | ||
17 | + | ||
18 | +import lombok.Getter; | ||
19 | +import lombok.Setter; | ||
20 | + | ||
21 | +@Getter | ||
22 | +@Setter | ||
23 | +public class LwM2MDeviceCredentials { | ||
24 | + private LwM2MClientCredentials client; | ||
25 | + private LwM2MBootstrapCredentials bootstrap; | ||
26 | +} |
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.device.credentials.lwm2m; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.annotation.JsonIgnore; | ||
19 | +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
20 | +import com.fasterxml.jackson.annotation.JsonSubTypes; | ||
21 | +import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||
22 | + | ||
23 | +@JsonTypeInfo( | ||
24 | + use = JsonTypeInfo.Id.NAME, | ||
25 | + property = "securityMode") | ||
26 | +@JsonSubTypes({ | ||
27 | + @JsonSubTypes.Type(value = NoSecServerCredentials.class, name = "NO_SEC"), | ||
28 | + @JsonSubTypes.Type(value = PSKServerCredentials.class, name = "PSK"), | ||
29 | + @JsonSubTypes.Type(value = RPKServerCredentials.class, name = "RPK"), | ||
30 | + @JsonSubTypes.Type(value = X509ServerCredentials.class, name = "X509") | ||
31 | +}) | ||
32 | +@JsonIgnoreProperties(ignoreUnknown = true) | ||
33 | +public interface LwM2MServerCredentials { | ||
34 | + | ||
35 | + @JsonIgnore | ||
36 | + LwM2MSecurityMode getSecurityMode(); | ||
37 | +} |
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.device.credentials.lwm2m; | ||
17 | + | ||
18 | +public class NoSecServerCredentials implements LwM2MServerCredentials { | ||
19 | + | ||
20 | + @Override | ||
21 | + public LwM2MSecurityMode getSecurityMode() { | ||
22 | + return LwM2MSecurityMode.NO_SEC; | ||
23 | + } | ||
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.common.data.device.credentials.lwm2m; | ||
17 | + | ||
18 | +public class PSKServerCredentials extends AbstractLwM2MServerCredentialsWithKeys { | ||
19 | + | ||
20 | + @Override | ||
21 | + public LwM2MSecurityMode getSecurityMode() { | ||
22 | + return LwM2MSecurityMode.PSK; | ||
23 | + } | ||
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.common.data.device.credentials.lwm2m; | ||
17 | + | ||
18 | +public class RPKServerCredentials extends AbstractLwM2MServerCredentialsWithKeys { | ||
19 | + | ||
20 | + @Override | ||
21 | + public LwM2MSecurityMode getSecurityMode() { | ||
22 | + return LwM2MSecurityMode.RPK; | ||
23 | + } | ||
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.common.data.device.credentials.lwm2m; | ||
17 | + | ||
18 | +public class X509ServerCredentials extends AbstractLwM2MServerCredentialsWithKeys { | ||
19 | + | ||
20 | + @Override | ||
21 | + public LwM2MSecurityMode getSecurityMode() { | ||
22 | + return LwM2MSecurityMode.X509; | ||
23 | + } | ||
24 | +} |
@@ -15,7 +15,6 @@ | @@ -15,7 +15,6 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.data.edge; | 16 | package org.thingsboard.server.common.data.edge; |
17 | 17 | ||
18 | -import com.fasterxml.jackson.databind.JsonNode; | ||
19 | import lombok.EqualsAndHashCode; | 18 | import lombok.EqualsAndHashCode; |
20 | import lombok.Getter; | 19 | import lombok.Getter; |
21 | import lombok.Setter; | 20 | import lombok.Setter; |
@@ -70,6 +69,19 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H | @@ -70,6 +69,19 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H | ||
70 | this.cloudEndpoint = edge.getCloudEndpoint(); | 69 | this.cloudEndpoint = edge.getCloudEndpoint(); |
71 | } | 70 | } |
72 | 71 | ||
72 | + public void update(Edge edge) { | ||
73 | + this.tenantId = edge.getTenantId(); | ||
74 | + this.customerId = edge.getCustomerId(); | ||
75 | + this.rootRuleChainId = edge.getRootRuleChainId(); | ||
76 | + this.type = edge.getType(); | ||
77 | + this.label = edge.getLabel(); | ||
78 | + this.name = edge.getName(); | ||
79 | + this.routingKey = edge.getRoutingKey(); | ||
80 | + this.secret = edge.getSecret(); | ||
81 | + this.edgeLicenseKey = edge.getEdgeLicenseKey(); | ||
82 | + this.cloudEndpoint = edge.getCloudEndpoint(); | ||
83 | + } | ||
84 | + | ||
73 | @Override | 85 | @Override |
74 | public String getSearchText() { | 86 | public String getSearchText() { |
75 | return getName(); | 87 | return getName(); |
@@ -577,6 +577,14 @@ public class JsonConverter { | @@ -577,6 +577,14 @@ public class JsonConverter { | ||
577 | return GSON.toJson(element); | 577 | return GSON.toJson(element); |
578 | } | 578 | } |
579 | 579 | ||
580 | + public static JsonObject toJsonObject(Object o) { | ||
581 | + return (JsonObject) GSON.toJsonTree(o); | ||
582 | + } | ||
583 | + | ||
584 | + public static <T> T fromJson(JsonElement element, Class<T> type) { | ||
585 | + return GSON.fromJson(element, type); | ||
586 | + } | ||
587 | + | ||
580 | public static void setTypeCastEnabled(boolean enabled) { | 588 | public static void setTypeCastEnabled(boolean enabled) { |
581 | isTypeCastEnabled = enabled; | 589 | isTypeCastEnabled = enabled; |
582 | } | 590 | } |
@@ -118,4 +118,9 @@ public class JacksonUtil { | @@ -118,4 +118,9 @@ public class JacksonUtil { | ||
118 | public static <T> JsonNode valueToTree(T value) { | 118 | public static <T> JsonNode valueToTree(T value) { |
119 | return OBJECT_MAPPER.valueToTree(value); | 119 | return OBJECT_MAPPER.valueToTree(value); |
120 | } | 120 | } |
121 | + | ||
122 | + public static <T> T treeToValue(JsonNode tree, Class<T> type) throws JsonProcessingException { | ||
123 | + return OBJECT_MAPPER.treeToValue(tree, type); | ||
124 | + } | ||
125 | + | ||
121 | } | 126 | } |
@@ -227,6 +227,10 @@ | @@ -227,6 +227,10 @@ | ||
227 | <groupId>org.elasticsearch.client</groupId> | 227 | <groupId>org.elasticsearch.client</groupId> |
228 | <artifactId>rest</artifactId> | 228 | <artifactId>rest</artifactId> |
229 | </dependency> | 229 | </dependency> |
230 | + <dependency> | ||
231 | + <groupId>org.eclipse.leshan</groupId> | ||
232 | + <artifactId>leshan-core</artifactId> | ||
233 | + </dependency> | ||
230 | </dependencies> | 234 | </dependencies> |
231 | <build> | 235 | <build> |
232 | <plugins> | 236 | <plugins> |
@@ -16,20 +16,28 @@ | @@ -16,20 +16,28 @@ | ||
16 | package org.thingsboard.server.dao.device; | 16 | package org.thingsboard.server.dao.device; |
17 | 17 | ||
18 | 18 | ||
19 | -import com.fasterxml.jackson.databind.node.ObjectNode; | ||
20 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | +import org.apache.commons.codec.binary.Hex; | ||
21 | +import org.eclipse.leshan.core.util.SecurityUtil; | ||
21 | import org.hibernate.exception.ConstraintViolationException; | 22 | import org.hibernate.exception.ConstraintViolationException; |
22 | import org.springframework.beans.factory.annotation.Autowired; | 23 | import org.springframework.beans.factory.annotation.Autowired; |
23 | import org.springframework.cache.annotation.CacheEvict; | 24 | import org.springframework.cache.annotation.CacheEvict; |
24 | import org.springframework.cache.annotation.Cacheable; | 25 | import org.springframework.cache.annotation.Cacheable; |
25 | import org.springframework.stereotype.Service; | 26 | import org.springframework.stereotype.Service; |
26 | -import org.springframework.util.StringUtils; | ||
27 | import org.thingsboard.common.util.JacksonUtil; | 27 | import org.thingsboard.common.util.JacksonUtil; |
28 | import org.thingsboard.server.common.data.Device; | 28 | import org.thingsboard.server.common.data.Device; |
29 | +import org.thingsboard.server.common.data.StringUtils; | ||
29 | import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; | 30 | import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; |
31 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapCredentials; | ||
30 | import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; | 32 | import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; |
33 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; | ||
34 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MServerCredentials; | ||
31 | import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; | 35 | import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; |
36 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKServerCredentials; | ||
37 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; | ||
38 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKServerCredentials; | ||
32 | import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; | 39 | import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; |
40 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ServerCredentials; | ||
33 | import org.thingsboard.server.common.data.id.DeviceId; | 41 | import org.thingsboard.server.common.data.id.DeviceId; |
34 | import org.thingsboard.server.common.data.id.EntityId; | 42 | import org.thingsboard.server.common.data.id.EntityId; |
35 | import org.thingsboard.server.common.data.id.TenantId; | 43 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -37,6 +45,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; | @@ -37,6 +45,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
37 | import org.thingsboard.server.common.msg.EncryptionUtil; | 45 | import org.thingsboard.server.common.msg.EncryptionUtil; |
38 | import org.thingsboard.server.dao.entity.AbstractEntityService; | 46 | import org.thingsboard.server.dao.entity.AbstractEntityService; |
39 | import org.thingsboard.server.dao.exception.DataValidationException; | 47 | import org.thingsboard.server.dao.exception.DataValidationException; |
48 | +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; | ||
40 | import org.thingsboard.server.dao.service.DataValidator; | 49 | import org.thingsboard.server.dao.service.DataValidator; |
41 | 50 | ||
42 | import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; | 51 | import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; |
@@ -83,17 +92,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -83,17 +92,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
83 | if (deviceCredentials.getCredentialsType() == null) { | 92 | if (deviceCredentials.getCredentialsType() == null) { |
84 | throw new DataValidationException("Device credentials type should be specified"); | 93 | throw new DataValidationException("Device credentials type should be specified"); |
85 | } | 94 | } |
86 | - switch (deviceCredentials.getCredentialsType()) { | ||
87 | - case X509_CERTIFICATE: | ||
88 | - formatCertData(deviceCredentials); | ||
89 | - break; | ||
90 | - case MQTT_BASIC: | ||
91 | - formatSimpleMqttCredentials(deviceCredentials); | ||
92 | - break; | ||
93 | - case LWM2M_CREDENTIALS: | ||
94 | - formatSimpleLwm2mCredentials(deviceCredentials); | ||
95 | - break; | ||
96 | - } | 95 | + formatCredentials(deviceCredentials); |
97 | log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); | 96 | log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); |
98 | credentialsValidator.validate(deviceCredentials, id -> tenantId); | 97 | credentialsValidator.validate(deviceCredentials, id -> tenantId); |
99 | try { | 98 | try { |
@@ -109,6 +108,21 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -109,6 +108,21 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
109 | } | 108 | } |
110 | } | 109 | } |
111 | 110 | ||
111 | + @Override | ||
112 | + public void formatCredentials(DeviceCredentials deviceCredentials) { | ||
113 | + switch (deviceCredentials.getCredentialsType()) { | ||
114 | + case X509_CERTIFICATE: | ||
115 | + formatCertData(deviceCredentials); | ||
116 | + break; | ||
117 | + case MQTT_BASIC: | ||
118 | + formatSimpleMqttCredentials(deviceCredentials); | ||
119 | + break; | ||
120 | + case LWM2M_CREDENTIALS: | ||
121 | + formatSimpleLwm2mCredentials(deviceCredentials); | ||
122 | + break; | ||
123 | + } | ||
124 | + } | ||
125 | + | ||
112 | private void formatSimpleMqttCredentials(DeviceCredentials deviceCredentials) { | 126 | private void formatSimpleMqttCredentials(DeviceCredentials deviceCredentials) { |
113 | BasicMqttCredentials mqttCredentials; | 127 | BasicMqttCredentials mqttCredentials; |
114 | try { | 128 | try { |
@@ -117,11 +131,16 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -117,11 +131,16 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
117 | throw new IllegalArgumentException(); | 131 | throw new IllegalArgumentException(); |
118 | } | 132 | } |
119 | } catch (IllegalArgumentException e) { | 133 | } catch (IllegalArgumentException e) { |
120 | - throw new DataValidationException("Invalid credentials body for simple mqtt credentials!"); | 134 | + throw new DeviceCredentialsValidationException("Invalid credentials body for simple mqtt credentials!"); |
121 | } | 135 | } |
136 | + | ||
122 | if (StringUtils.isEmpty(mqttCredentials.getClientId()) && StringUtils.isEmpty(mqttCredentials.getUserName())) { | 137 | if (StringUtils.isEmpty(mqttCredentials.getClientId()) && StringUtils.isEmpty(mqttCredentials.getUserName())) { |
123 | - throw new DataValidationException("Both mqtt client id and user name are empty!"); | 138 | + throw new DeviceCredentialsValidationException("Both mqtt client id and user name are empty!"); |
124 | } | 139 | } |
140 | + if (StringUtils.isNotEmpty(mqttCredentials.getClientId()) && StringUtils.isNotEmpty(mqttCredentials.getPassword()) && StringUtils.isEmpty(mqttCredentials.getUserName())) { | ||
141 | + throw new DeviceCredentialsValidationException("Password cannot be specified along with client id"); | ||
142 | + } | ||
143 | + | ||
125 | if (StringUtils.isEmpty(mqttCredentials.getClientId())) { | 144 | if (StringUtils.isEmpty(mqttCredentials.getClientId())) { |
126 | deviceCredentials.setCredentialsId(mqttCredentials.getUserName()); | 145 | deviceCredentials.setCredentialsId(mqttCredentials.getUserName()); |
127 | } else if (StringUtils.isEmpty(mqttCredentials.getUserName())) { | 146 | } else if (StringUtils.isEmpty(mqttCredentials.getUserName())) { |
@@ -129,7 +148,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -129,7 +148,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
129 | } else { | 148 | } else { |
130 | deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash("|", mqttCredentials.getClientId(), mqttCredentials.getUserName())); | 149 | deviceCredentials.setCredentialsId(EncryptionUtil.getSha3Hash("|", mqttCredentials.getClientId(), mqttCredentials.getUserName())); |
131 | } | 150 | } |
132 | - if (!StringUtils.isEmpty(mqttCredentials.getPassword())) { | 151 | + if (StringUtils.isNotEmpty(mqttCredentials.getPassword())) { |
133 | mqttCredentials.setPassword(mqttCredentials.getPassword()); | 152 | mqttCredentials.setPassword(mqttCredentials.getPassword()); |
134 | } | 153 | } |
135 | deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); | 154 | deviceCredentials.setCredentialsValue(JacksonUtil.toString(mqttCredentials)); |
@@ -143,22 +162,16 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -143,22 +162,16 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
143 | } | 162 | } |
144 | 163 | ||
145 | private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { | 164 | private void formatSimpleLwm2mCredentials(DeviceCredentials deviceCredentials) { |
146 | - LwM2MClientCredentials clientCredentials; | ||
147 | - ObjectNode json; | 165 | + LwM2MDeviceCredentials lwM2MCredentials; |
148 | try { | 166 | try { |
149 | - json = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), ObjectNode.class); | ||
150 | - if (json == null) { | ||
151 | - throw new IllegalArgumentException(); | ||
152 | - } | ||
153 | - clientCredentials = JacksonUtil.convertValue(json.get("client"), LwM2MClientCredentials.class); | ||
154 | - if (clientCredentials == null) { | ||
155 | - throw new IllegalArgumentException(); | ||
156 | - } | 167 | + lwM2MCredentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), LwM2MDeviceCredentials.class); |
168 | + validateLwM2MDeviceCredentials(lwM2MCredentials); | ||
157 | } catch (IllegalArgumentException e) { | 169 | } catch (IllegalArgumentException e) { |
158 | - throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); | 170 | + throw new DeviceCredentialsValidationException("Invalid credentials body for LwM2M credentials!"); |
159 | } | 171 | } |
160 | 172 | ||
161 | String credentialsId = null; | 173 | String credentialsId = null; |
174 | + LwM2MClientCredentials clientCredentials = lwM2MCredentials.getClient(); | ||
162 | 175 | ||
163 | switch (clientCredentials.getSecurityConfigClientMode()) { | 176 | switch (clientCredentials.getSecurityConfigClientMode()) { |
164 | case NO_SEC: | 177 | case NO_SEC: |
@@ -174,8 +187,8 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -174,8 +187,8 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
174 | String cert = EncryptionUtil.trimNewLines(x509Config.getCert()); | 187 | String cert = EncryptionUtil.trimNewLines(x509Config.getCert()); |
175 | String sha3Hash = EncryptionUtil.getSha3Hash(cert); | 188 | String sha3Hash = EncryptionUtil.getSha3Hash(cert); |
176 | x509Config.setCert(cert); | 189 | x509Config.setCert(cert); |
177 | - ((ObjectNode) json.get("client")).put("cert", cert); | ||
178 | - deviceCredentials.setCredentialsValue(JacksonUtil.toString(json)); | 190 | + ((X509ClientCredentials) clientCredentials).setCert(cert); |
191 | + deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials)); | ||
179 | credentialsId = sha3Hash; | 192 | credentialsId = sha3Hash; |
180 | } else { | 193 | } else { |
181 | credentialsId = x509Config.getEndpoint(); | 194 | credentialsId = x509Config.getEndpoint(); |
@@ -183,11 +196,163 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -183,11 +196,163 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
183 | break; | 196 | break; |
184 | } | 197 | } |
185 | if (credentialsId == null) { | 198 | if (credentialsId == null) { |
186 | - throw new DataValidationException("Invalid credentials body for LwM2M credentials!"); | 199 | + throw new DeviceCredentialsValidationException("Invalid credentials body for LwM2M credentials!"); |
187 | } | 200 | } |
188 | deviceCredentials.setCredentialsId(credentialsId); | 201 | deviceCredentials.setCredentialsId(credentialsId); |
189 | } | 202 | } |
190 | 203 | ||
204 | + private void validateLwM2MDeviceCredentials(LwM2MDeviceCredentials lwM2MCredentials) { | ||
205 | + if (lwM2MCredentials == null) { | ||
206 | + throw new DeviceCredentialsValidationException("LwM2M credentials should be specified!"); | ||
207 | + } | ||
208 | + | ||
209 | + LwM2MClientCredentials clientCredentials = lwM2MCredentials.getClient(); | ||
210 | + if (clientCredentials == null) { | ||
211 | + throw new DeviceCredentialsValidationException("LwM2M client credentials should be specified!"); | ||
212 | + } | ||
213 | + validateLwM2MClientCredentials(clientCredentials); | ||
214 | + | ||
215 | + LwM2MBootstrapCredentials bootstrapCredentials = lwM2MCredentials.getBootstrap(); | ||
216 | + if (bootstrapCredentials == null) { | ||
217 | + throw new DeviceCredentialsValidationException("LwM2M bootstrap credentials should be specified!"); | ||
218 | + } | ||
219 | + | ||
220 | + LwM2MServerCredentials bootstrapServerCredentials = bootstrapCredentials.getBootstrapServer(); | ||
221 | + if (bootstrapServerCredentials == null) { | ||
222 | + throw new DeviceCredentialsValidationException("LwM2M bootstrap server credentials should be specified!"); | ||
223 | + } | ||
224 | + validateServerCredentials(bootstrapServerCredentials, "Bootstrap server"); | ||
225 | + | ||
226 | + LwM2MServerCredentials lwm2mServerCredentials = bootstrapCredentials.getLwm2mServer(); | ||
227 | + if (lwm2mServerCredentials == null) { | ||
228 | + throw new DeviceCredentialsValidationException("LwM2M lwm2m server credentials should be specified!"); | ||
229 | + } | ||
230 | + validateServerCredentials(lwm2mServerCredentials, "LwM2M server"); | ||
231 | + } | ||
232 | + | ||
233 | + private void validateLwM2MClientCredentials(LwM2MClientCredentials clientCredentials) { | ||
234 | + if (StringUtils.isEmpty(clientCredentials.getEndpoint())) { | ||
235 | + throw new DeviceCredentialsValidationException("LwM2M client endpoint should be specified!"); | ||
236 | + } | ||
237 | + | ||
238 | + switch (clientCredentials.getSecurityConfigClientMode()) { | ||
239 | + case NO_SEC: | ||
240 | + break; | ||
241 | + case PSK: | ||
242 | + PSKClientCredentials pskCredentials = (PSKClientCredentials) clientCredentials; | ||
243 | + if (StringUtils.isEmpty(pskCredentials.getIdentity())) { | ||
244 | + throw new DeviceCredentialsValidationException("LwM2M client PSK identity should be specified!"); | ||
245 | + } | ||
246 | + | ||
247 | + String pskKey = pskCredentials.getKey(); | ||
248 | + if (StringUtils.isEmpty(pskKey)) { | ||
249 | + throw new DeviceCredentialsValidationException("LwM2M client PSK key should be specified!"); | ||
250 | + } | ||
251 | + | ||
252 | + if (!pskKey.matches("-?[0-9a-fA-F]+")) { | ||
253 | + throw new DeviceCredentialsValidationException("LwM2M client PSK key should be HexDecimal format!"); | ||
254 | + } | ||
255 | + | ||
256 | + if (pskKey.length() % 32 != 0 || pskKey.length() > 128) { | ||
257 | + throw new DeviceCredentialsValidationException("LwM2M client PSK key must be 32, 64, 128 characters!"); | ||
258 | + } | ||
259 | + break; | ||
260 | + case RPK: | ||
261 | + RPKClientCredentials rpkCredentials = (RPKClientCredentials) clientCredentials; | ||
262 | + | ||
263 | + if (StringUtils.isEmpty(rpkCredentials.getKey())) { | ||
264 | + throw new DeviceCredentialsValidationException("LwM2M client RPK key should be specified!"); | ||
265 | + } | ||
266 | + | ||
267 | + try { | ||
268 | + SecurityUtil.publicKey.decode(rpkCredentials.getDecodedKey()); | ||
269 | + } catch (Exception e) { | ||
270 | + throw new DeviceCredentialsValidationException("LwM2M client RPK key should be in RFC7250 standard!"); | ||
271 | + } | ||
272 | + break; | ||
273 | + case X509: | ||
274 | + X509ClientCredentials x509CCredentials = (X509ClientCredentials) clientCredentials; | ||
275 | + if (x509CCredentials.getCert() != null) { | ||
276 | + try { | ||
277 | + SecurityUtil.certificate.decode(Hex.decodeHex(x509CCredentials.getCert().toLowerCase().toCharArray())); | ||
278 | + } catch (Exception e) { | ||
279 | + throw new DeviceCredentialsValidationException("LwM2M client X509 certificate should be in DER-encoded X.509 format!"); | ||
280 | + } | ||
281 | + } | ||
282 | + break; | ||
283 | + } | ||
284 | + } | ||
285 | + | ||
286 | + private void validateServerCredentials(LwM2MServerCredentials serverCredentials, String server) { | ||
287 | + switch (serverCredentials.getSecurityMode()) { | ||
288 | + case NO_SEC: | ||
289 | + break; | ||
290 | + case PSK: | ||
291 | + PSKServerCredentials pskCredentials = (PSKServerCredentials) serverCredentials; | ||
292 | + if (StringUtils.isEmpty(pskCredentials.getClientPublicKeyOrId())) { | ||
293 | + throw new DeviceCredentialsValidationException(server + " client PSK public key or id should be specified!"); | ||
294 | + } | ||
295 | + | ||
296 | + String pskKey = pskCredentials.getClientSecretKey(); | ||
297 | + if (StringUtils.isEmpty(pskKey)) { | ||
298 | + throw new DeviceCredentialsValidationException(server + " client PSK key should be specified!"); | ||
299 | + } | ||
300 | + | ||
301 | + if (!pskKey.matches("-?[0-9a-fA-F]+")) { | ||
302 | + throw new DeviceCredentialsValidationException(server + " client PSK key should be HexDecimal format!"); | ||
303 | + } | ||
304 | + | ||
305 | + if (pskKey.length() % 32 != 0 || pskKey.length() > 128) { | ||
306 | + throw new DeviceCredentialsValidationException(server + " client PSK key must be 32, 64, 128 characters!"); | ||
307 | + } | ||
308 | + break; | ||
309 | + case RPK: | ||
310 | + RPKServerCredentials rpkCredentials = (RPKServerCredentials) serverCredentials; | ||
311 | + | ||
312 | + if (StringUtils.isEmpty(rpkCredentials.getClientPublicKeyOrId())) { | ||
313 | + throw new DeviceCredentialsValidationException(server + " client RPK public key or id should be specified!"); | ||
314 | + } | ||
315 | + | ||
316 | + try { | ||
317 | + SecurityUtil.publicKey.decode(rpkCredentials.getDecodedClientPublicKeyOrId()); | ||
318 | + } catch (Exception e) { | ||
319 | + throw new DeviceCredentialsValidationException(server + " client RPK public key or id should be in RFC7250 standard!"); | ||
320 | + } | ||
321 | + | ||
322 | + if (StringUtils.isEmpty(rpkCredentials.getClientSecretKey())) { | ||
323 | + throw new DeviceCredentialsValidationException(server + " client RPK secret key should be specified!"); | ||
324 | + } | ||
325 | + | ||
326 | + try { | ||
327 | + SecurityUtil.privateKey.decode(rpkCredentials.getDecodedClientSecretKey()); | ||
328 | + } catch (Exception e) { | ||
329 | + throw new DeviceCredentialsValidationException(server + " client RPK secret key should be in RFC5958 standard!"); | ||
330 | + } | ||
331 | + break; | ||
332 | + case X509: | ||
333 | + X509ServerCredentials x509CCredentials = (X509ServerCredentials) serverCredentials; | ||
334 | + if (StringUtils.isEmpty(x509CCredentials.getClientPublicKeyOrId())) { | ||
335 | + throw new DeviceCredentialsValidationException(server + " client X509 public key or id should be specified!"); | ||
336 | + } | ||
337 | + | ||
338 | + try { | ||
339 | + SecurityUtil.certificate.decode(x509CCredentials.getDecodedClientPublicKeyOrId()); | ||
340 | + } catch (Exception e) { | ||
341 | + throw new DeviceCredentialsValidationException(server + " client X509 public key or id should be in DER-encoded X.509 format!"); | ||
342 | + } | ||
343 | + if (StringUtils.isEmpty(x509CCredentials.getClientSecretKey())) { | ||
344 | + throw new DeviceCredentialsValidationException(server + " client X509 secret key should be specified!"); | ||
345 | + } | ||
346 | + | ||
347 | + try { | ||
348 | + SecurityUtil.privateKey.decode(x509CCredentials.getDecodedClientSecretKey()); | ||
349 | + } catch (Exception e) { | ||
350 | + throw new DeviceCredentialsValidationException(server + " client X509 secret key should be in RFC5958 standard!"); | ||
351 | + } | ||
352 | + break; | ||
353 | + } | ||
354 | + } | ||
355 | + | ||
191 | @Override | 356 | @Override |
192 | @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId") | 357 | @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId") |
193 | public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { | 358 | public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { |
@@ -201,38 +366,38 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | @@ -201,38 +366,38 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen | ||
201 | @Override | 366 | @Override |
202 | protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) { | 367 | protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) { |
203 | if (deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()) != null) { | 368 | if (deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()) != null) { |
204 | - throw new DataValidationException("Credentials for this device are already specified!"); | 369 | + throw new DeviceCredentialsValidationException("Credentials for this device are already specified!"); |
205 | } | 370 | } |
206 | if (deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()) != null) { | 371 | if (deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()) != null) { |
207 | - throw new DataValidationException("Device credentials are already assigned to another device!"); | 372 | + throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!"); |
208 | } | 373 | } |
209 | } | 374 | } |
210 | 375 | ||
211 | @Override | 376 | @Override |
212 | protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { | 377 | protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { |
213 | if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) { | 378 | if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) { |
214 | - throw new DataValidationException("Unable to update non-existent device credentials!"); | 379 | + throw new DeviceCredentialsValidationException("Unable to update non-existent device credentials!"); |
215 | } | 380 | } |
216 | DeviceCredentials existingCredentials = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); | 381 | DeviceCredentials existingCredentials = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); |
217 | if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) { | 382 | if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) { |
218 | - throw new DataValidationException("Device credentials are already assigned to another device!"); | 383 | + throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!"); |
219 | } | 384 | } |
220 | } | 385 | } |
221 | 386 | ||
222 | @Override | 387 | @Override |
223 | protected void validateDataImpl(TenantId tenantId, DeviceCredentials deviceCredentials) { | 388 | protected void validateDataImpl(TenantId tenantId, DeviceCredentials deviceCredentials) { |
224 | if (deviceCredentials.getDeviceId() == null) { | 389 | if (deviceCredentials.getDeviceId() == null) { |
225 | - throw new DataValidationException("Device credentials should be assigned to device!"); | 390 | + throw new DeviceCredentialsValidationException("Device credentials should be assigned to device!"); |
226 | } | 391 | } |
227 | if (deviceCredentials.getCredentialsType() == null) { | 392 | if (deviceCredentials.getCredentialsType() == null) { |
228 | - throw new DataValidationException("Device credentials type should be specified!"); | 393 | + throw new DeviceCredentialsValidationException("Device credentials type should be specified!"); |
229 | } | 394 | } |
230 | if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) { | 395 | if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) { |
231 | - throw new DataValidationException("Device credentials id should be specified!"); | 396 | + throw new DeviceCredentialsValidationException("Device credentials id should be specified!"); |
232 | } | 397 | } |
233 | Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId()); | 398 | Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId()); |
234 | if (device == null) { | 399 | if (device == null) { |
235 | - throw new DataValidationException("Can't assign device credentials to non-existent device!"); | 400 | + throw new DeviceCredentialsValidationException("Can't assign device credentials to non-existent device!"); |
236 | } | 401 | } |
237 | } | 402 | } |
238 | }; | 403 | }; |
@@ -228,6 +228,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -228,6 +228,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
228 | if (foundDeviceCredentials == null) { | 228 | if (foundDeviceCredentials == null) { |
229 | deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); | 229 | deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); |
230 | } else { | 230 | } else { |
231 | + deviceCredentials.setId(foundDeviceCredentials.getId()); | ||
231 | deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials); | 232 | deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials); |
232 | } | 233 | } |
233 | } | 234 | } |
@@ -241,7 +242,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -241,7 +242,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
241 | deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); | 242 | deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); |
242 | deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); | 243 | deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
243 | deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20)); | 244 | deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20)); |
244 | - deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); | 245 | + deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); |
245 | } | 246 | } |
246 | return savedDevice; | 247 | return savedDevice; |
247 | } | 248 | } |
dao/src/main/java/org/thingsboard/server/dao/exception/DeviceCredentialsValidationException.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.dao.exception; | ||
17 | + | ||
18 | +public class DeviceCredentialsValidationException extends DataValidationException { | ||
19 | + public DeviceCredentialsValidationException(String message) { | ||
20 | + super(message); | ||
21 | + } | ||
22 | +} |
@@ -22,6 +22,7 @@ import { PageLink } from '@shared/models/page/page-link'; | @@ -22,6 +22,7 @@ import { PageLink } from '@shared/models/page/page-link'; | ||
22 | import { PageData } from '@shared/models/page/page-data'; | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | import { EntitySubtype } from '@app/shared/models/entity-type.models'; | 23 | import { EntitySubtype } from '@app/shared/models/entity-type.models'; |
24 | import { Asset, AssetInfo, AssetSearchQuery } from '@app/shared/models/asset.models'; | 24 | import { Asset, AssetInfo, AssetSearchQuery } from '@app/shared/models/asset.models'; |
25 | +import { BulkImportRequest, BulkImportResult } from '@home/components/import-export/import-export.models'; | ||
25 | 26 | ||
26 | @Injectable({ | 27 | @Injectable({ |
27 | providedIn: 'root' | 28 | providedIn: 'root' |
@@ -105,4 +106,8 @@ export class AssetService { | @@ -105,4 +106,8 @@ export class AssetService { | ||
105 | defaultHttpOptionsFromConfig(config)); | 106 | defaultHttpOptionsFromConfig(config)); |
106 | } | 107 | } |
107 | 108 | ||
109 | + public bulkImportAssets(entitiesData: BulkImportRequest, config?: RequestConfig): Observable<BulkImportResult> { | ||
110 | + return this.http.post<BulkImportResult>('/api/asset/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); | ||
111 | + } | ||
112 | + | ||
108 | } | 113 | } |
@@ -30,6 +30,7 @@ import { | @@ -30,6 +30,7 @@ import { | ||
30 | } from '@app/shared/models/device.models'; | 30 | } from '@app/shared/models/device.models'; |
31 | import { EntitySubtype } from '@app/shared/models/entity-type.models'; | 31 | import { EntitySubtype } from '@app/shared/models/entity-type.models'; |
32 | import { AuthService } from '@core/auth/auth.service'; | 32 | import { AuthService } from '@core/auth/auth.service'; |
33 | +import { BulkImportRequest, BulkImportResult } from '@home/components/import-export/import-export.models'; | ||
33 | import { PersistentRpc } from '@shared/models/rpc.models'; | 34 | import { PersistentRpc } from '@shared/models/rpc.models'; |
34 | 35 | ||
35 | @Injectable({ | 36 | @Injectable({ |
@@ -178,4 +179,8 @@ export class DeviceService { | @@ -178,4 +179,8 @@ export class DeviceService { | ||
178 | defaultHttpOptionsFromConfig(config)); | 179 | defaultHttpOptionsFromConfig(config)); |
179 | } | 180 | } |
180 | 181 | ||
182 | + public bulkImportDevices(entitiesData: BulkImportRequest, config?: RequestConfig): Observable<BulkImportResult> { | ||
183 | + return this.http.post<BulkImportResult>('/api/device/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); | ||
184 | + } | ||
185 | + | ||
181 | } | 186 | } |
@@ -23,6 +23,7 @@ import { PageData } from '@shared/models/page/page-data'; | @@ -23,6 +23,7 @@ import { PageData } from '@shared/models/page/page-data'; | ||
23 | import { EntitySubtype } from '@app/shared/models/entity-type.models'; | 23 | import { EntitySubtype } from '@app/shared/models/entity-type.models'; |
24 | import { Edge, EdgeEvent, EdgeInfo, EdgeSearchQuery } from '@shared/models/edge.models'; | 24 | import { Edge, EdgeEvent, EdgeInfo, EdgeSearchQuery } from '@shared/models/edge.models'; |
25 | import { EntityId } from '@shared/models/id/entity-id'; | 25 | import { EntityId } from '@shared/models/id/entity-id'; |
26 | +import { BulkImportRequest, BulkImportResult } from '@home/components/import-export/import-export.models'; | ||
26 | 27 | ||
27 | @Injectable({ | 28 | @Injectable({ |
28 | providedIn: 'root' | 29 | providedIn: 'root' |
@@ -59,7 +60,7 @@ export class EdgeService { | @@ -59,7 +60,7 @@ export class EdgeService { | ||
59 | } | 60 | } |
60 | 61 | ||
61 | public getCustomerEdgeInfos(customerId: string, pageLink: PageLink, type: string = '', | 62 | public getCustomerEdgeInfos(customerId: string, pageLink: PageLink, type: string = '', |
62 | - config?: RequestConfig): Observable<PageData<EdgeInfo>> { | 63 | + config?: RequestConfig): Observable<PageData<EdgeInfo>> { |
63 | return this.http.get<PageData<EdgeInfo>>(`/api/customer/${customerId}/edgeInfos${pageLink.toQuery()}&type=${type}`, | 64 | return this.http.get<PageData<EdgeInfo>>(`/api/customer/${customerId}/edgeInfos${pageLink.toQuery()}&type=${type}`, |
64 | defaultHttpOptionsFromConfig(config)); | 65 | defaultHttpOptionsFromConfig(config)); |
65 | } | 66 | } |
@@ -108,4 +109,8 @@ export class EdgeService { | @@ -108,4 +109,8 @@ export class EdgeService { | ||
108 | public findByName(edgeName: string, config?: RequestConfig): Observable<Edge> { | 109 | public findByName(edgeName: string, config?: RequestConfig): Observable<Edge> { |
109 | return this.http.get<Edge>(`/api/tenant/edges?edgeName=${edgeName}`, defaultHttpOptionsFromConfig(config)); | 110 | return this.http.get<Edge>(`/api/tenant/edges?edgeName=${edgeName}`, defaultHttpOptionsFromConfig(config)); |
110 | } | 111 | } |
112 | + | ||
113 | + public bulkImportEdges(entitiesData: BulkImportRequest, config?: RequestConfig): Observable<BulkImportResult> { | ||
114 | + return this.http.post<BulkImportResult>('/api/edge/bulk_import', entitiesData, defaultHttpOptionsFromConfig(config)); | ||
115 | + } | ||
111 | } | 116 | } |
@@ -59,7 +59,7 @@ import { | @@ -59,7 +59,7 @@ import { | ||
59 | ImportEntityData | 59 | ImportEntityData |
60 | } from '@shared/models/entity.models'; | 60 | } from '@shared/models/entity.models'; |
61 | import { EntityRelationService } from '@core/http/entity-relation.service'; | 61 | import { EntityRelationService } from '@core/http/entity-relation.service'; |
62 | -import { deepClone, generateSecret, guid, isDefined, isDefinedAndNotNull } from '@core/utils'; | 62 | +import { deepClone, generateSecret, guid, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; |
63 | import { Asset } from '@shared/models/asset.models'; | 63 | import { Asset } from '@shared/models/asset.models'; |
64 | import { Device, DeviceCredentialsType } from '@shared/models/device.models'; | 64 | import { Device, DeviceCredentialsType } from '@shared/models/device.models'; |
65 | import { AttributeService } from '@core/http/attribute.service'; | 65 | import { AttributeService } from '@core/http/attribute.service'; |
@@ -964,7 +964,12 @@ export class EntityService { | @@ -964,7 +964,12 @@ export class EntityService { | ||
964 | map(() => { | 964 | map(() => { |
965 | return { create: { entity: 1 } } as ImportEntitiesResultInfo; | 965 | return { create: { entity: 1 } } as ImportEntitiesResultInfo; |
966 | }), | 966 | }), |
967 | - catchError(err => of({ error: { entity: 1 } } as ImportEntitiesResultInfo)) | 967 | + catchError(err => of({ |
968 | + error: { | ||
969 | + entity: 1, | ||
970 | + errors: err.message | ||
971 | + } | ||
972 | + } as ImportEntitiesResultInfo)) | ||
968 | ); | 973 | ); |
969 | }), | 974 | }), |
970 | catchError(err => { | 975 | catchError(err => { |
@@ -988,13 +993,28 @@ export class EntityService { | @@ -988,13 +993,28 @@ export class EntityService { | ||
988 | map(() => { | 993 | map(() => { |
989 | return { update: { entity: 1 } } as ImportEntitiesResultInfo; | 994 | return { update: { entity: 1 } } as ImportEntitiesResultInfo; |
990 | }), | 995 | }), |
991 | - catchError(updateError => of({ error: { entity: 1 } } as ImportEntitiesResultInfo)) | 996 | + catchError(updateError => of({ |
997 | + error: { | ||
998 | + entity: 1, | ||
999 | + errors: updateError.message | ||
1000 | + } | ||
1001 | + } as ImportEntitiesResultInfo)) | ||
992 | ); | 1002 | ); |
993 | }), | 1003 | }), |
994 | - catchError(findErr => of({ error: { entity: 1 } } as ImportEntitiesResultInfo)) | 1004 | + catchError(findErr => of({ |
1005 | + error: { | ||
1006 | + entity: 1, | ||
1007 | + errors: `Line: ${entityData.lineNumber}; Error: ${findErr.error.message}` | ||
1008 | + } | ||
1009 | + } as ImportEntitiesResultInfo)) | ||
995 | ); | 1010 | ); |
996 | } else { | 1011 | } else { |
997 | - return of({ error: { entity: 1 } } as ImportEntitiesResultInfo); | 1012 | + return of({ |
1013 | + error: { | ||
1014 | + entity: 1, | ||
1015 | + errors: `Line: ${entityData.lineNumber}; Error: ${err.error.message}` | ||
1016 | + } | ||
1017 | + } as ImportEntitiesResultInfo); | ||
998 | } | 1018 | } |
999 | }) | 1019 | }) |
1000 | ); | 1020 | ); |
@@ -1050,7 +1070,6 @@ export class EntityService { | @@ -1050,7 +1070,6 @@ export class EntityService { | ||
1050 | break; | 1070 | break; |
1051 | } | 1071 | } |
1052 | return saveEntityObservable; | 1072 | return saveEntityObservable; |
1053 | - | ||
1054 | } | 1073 | } |
1055 | 1074 | ||
1056 | private getUpdateEntityTasks(entityType: EntityType, entityData: ImportEntityData | EdgeImportEntityData, | 1075 | private getUpdateEntityTasks(entityType: EntityType, entityData: ImportEntityData | EdgeImportEntityData, |
@@ -1123,15 +1142,31 @@ export class EntityService { | @@ -1123,15 +1142,31 @@ export class EntityService { | ||
1123 | public saveEntityData(entityId: EntityId, entityData: ImportEntityData, config?: RequestConfig): Observable<any> { | 1142 | public saveEntityData(entityId: EntityId, entityData: ImportEntityData, config?: RequestConfig): Observable<any> { |
1124 | const observables: Observable<string>[] = []; | 1143 | const observables: Observable<string>[] = []; |
1125 | let observable: Observable<string>; | 1144 | let observable: Observable<string>; |
1126 | - if (entityData.accessToken && entityData.accessToken !== '') { | 1145 | + if (Object.keys(entityData.credential).length) { |
1146 | + let credentialsType: DeviceCredentialsType; | ||
1147 | + let credentialsId: string = null; | ||
1148 | + let credentialsValue: string = null; | ||
1149 | + if (isDefinedAndNotNull(entityData.credential.mqtt)) { | ||
1150 | + credentialsType = DeviceCredentialsType.MQTT_BASIC; | ||
1151 | + credentialsValue = JSON.stringify(entityData.credential.mqtt); | ||
1152 | + } else if (isDefinedAndNotNull(entityData.credential.lwm2m)) { | ||
1153 | + credentialsType = DeviceCredentialsType.LWM2M_CREDENTIALS; | ||
1154 | + credentialsValue = JSON.stringify(entityData.credential.lwm2m); | ||
1155 | + } else if (isNotEmptyStr(entityData.credential.x509)) { | ||
1156 | + credentialsType = DeviceCredentialsType.X509_CERTIFICATE; | ||
1157 | + credentialsValue = entityData.credential.x509; | ||
1158 | + } else { | ||
1159 | + credentialsType = DeviceCredentialsType.ACCESS_TOKEN; | ||
1160 | + credentialsId = entityData.credential.accessToken; | ||
1161 | + } | ||
1127 | observable = this.deviceService.getDeviceCredentials(entityId.id, false, config).pipe( | 1162 | observable = this.deviceService.getDeviceCredentials(entityId.id, false, config).pipe( |
1128 | mergeMap((credentials) => { | 1163 | mergeMap((credentials) => { |
1129 | - credentials.credentialsId = entityData.accessToken; | ||
1130 | - credentials.credentialsType = DeviceCredentialsType.ACCESS_TOKEN; | ||
1131 | - credentials.credentialsValue = null; | 1164 | + credentials.credentialsId = credentialsId; |
1165 | + credentials.credentialsType = credentialsType; | ||
1166 | + credentials.credentialsValue = credentialsValue; | ||
1132 | return this.deviceService.saveDeviceCredentials(credentials, config).pipe( | 1167 | return this.deviceService.saveDeviceCredentials(credentials, config).pipe( |
1133 | map(() => 'ok'), | 1168 | map(() => 'ok'), |
1134 | - catchError(err => of('error')) | 1169 | + catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`)) |
1135 | ); | 1170 | ); |
1136 | }) | 1171 | }) |
1137 | ); | 1172 | ); |
@@ -1141,7 +1176,7 @@ export class EntityService { | @@ -1141,7 +1176,7 @@ export class EntityService { | ||
1141 | observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SHARED_SCOPE, | 1176 | observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SHARED_SCOPE, |
1142 | entityData.attributes.shared, config).pipe( | 1177 | entityData.attributes.shared, config).pipe( |
1143 | map(() => 'ok'), | 1178 | map(() => 'ok'), |
1144 | - catchError(err => of('error')) | 1179 | + catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`)) |
1145 | ); | 1180 | ); |
1146 | observables.push(observable); | 1181 | observables.push(observable); |
1147 | } | 1182 | } |
@@ -1149,23 +1184,23 @@ export class EntityService { | @@ -1149,23 +1184,23 @@ export class EntityService { | ||
1149 | observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE, | 1184 | observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE, |
1150 | entityData.attributes.server, config).pipe( | 1185 | entityData.attributes.server, config).pipe( |
1151 | map(() => 'ok'), | 1186 | map(() => 'ok'), |
1152 | - catchError(err => of('error')) | 1187 | + catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`)) |
1153 | ); | 1188 | ); |
1154 | observables.push(observable); | 1189 | observables.push(observable); |
1155 | } | 1190 | } |
1156 | if (entityData.timeseries && entityData.timeseries.length) { | 1191 | if (entityData.timeseries && entityData.timeseries.length) { |
1157 | observable = this.attributeService.saveEntityTimeseries(entityId, 'time', entityData.timeseries, config).pipe( | 1192 | observable = this.attributeService.saveEntityTimeseries(entityId, 'time', entityData.timeseries, config).pipe( |
1158 | map(() => 'ok'), | 1193 | map(() => 'ok'), |
1159 | - catchError(err => of('error')) | 1194 | + catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`)) |
1160 | ); | 1195 | ); |
1161 | observables.push(observable); | 1196 | observables.push(observable); |
1162 | } | 1197 | } |
1163 | if (observables.length) { | 1198 | if (observables.length) { |
1164 | return forkJoin(observables).pipe( | 1199 | return forkJoin(observables).pipe( |
1165 | map((response) => { | 1200 | map((response) => { |
1166 | - const hasError = response.filter((status) => status === 'error').length > 0; | ||
1167 | - if (hasError) { | ||
1168 | - throw Error(); | 1201 | + const hasError = response.filter((status) => status !== 'ok'); |
1202 | + if (hasError.length > 0) { | ||
1203 | + throw Error(hasError.join('\n')); | ||
1169 | } else { | 1204 | } else { |
1170 | return response; | 1205 | return response; |
1171 | } | 1206 | } |
@@ -94,7 +94,7 @@ | @@ -94,7 +94,7 @@ | ||
94 | <mat-step [stepControl]="columnTypesFormGroup"> | 94 | <mat-step [stepControl]="columnTypesFormGroup"> |
95 | <form [formGroup]="columnTypesFormGroup"> | 95 | <form [formGroup]="columnTypesFormGroup"> |
96 | <ng-template matStepLabel>{{ 'import.stepper-text.column-type' | translate }}</ng-template> | 96 | <ng-template matStepLabel>{{ 'import.stepper-text.column-type' | translate }}</ng-template> |
97 | - <tb-table-columns-assignment formControlName="columnsParam" [entityType]="entityType"></tb-table-columns-assignment> | 97 | + <tb-table-columns-assignment #columnsAssignmentComponent formControlName="columnsParam" [entityType]="entityType"></tb-table-columns-assignment> |
98 | </form> | 98 | </form> |
99 | <div fxLayout="row wrap" fxLayoutAlign="space-between center"> | 99 | <div fxLayout="row wrap" fxLayoutAlign="space-between center"> |
100 | <button mat-button | 100 | <button mat-button |
@@ -113,21 +113,32 @@ | @@ -113,21 +113,32 @@ | ||
113 | </mat-step> | 113 | </mat-step> |
114 | <mat-step> | 114 | <mat-step> |
115 | <ng-template matStepLabel>{{ 'import.stepper-text.creat-entities' | translate }}</ng-template> | 115 | <ng-template matStepLabel>{{ 'import.stepper-text.creat-entities' | translate }}</ng-template> |
116 | - <mat-progress-bar color="warn" class="tb-import-progress" mode="determinate" [value]="progressCreate"> | 116 | + <mat-progress-bar color="warn" class="tb-import-progress" mode="indeterminate"> |
117 | </mat-progress-bar> | 117 | </mat-progress-bar> |
118 | </mat-step> | 118 | </mat-step> |
119 | <mat-step> | 119 | <mat-step> |
120 | <ng-template matStepLabel>{{ 'import.stepper-text.done' | translate }}</ng-template> | 120 | <ng-template matStepLabel>{{ 'import.stepper-text.done' | translate }}</ng-template> |
121 | <div fxLayout="column"> | 121 | <div fxLayout="column"> |
122 | - <p class="mat-body-1" *ngIf="this.statistical?.create && this.statistical?.create.entity"> | ||
123 | - {{ translate.instant('import.message.create-entities', {count: this.statistical.create.entity}) }} | 122 | + <p class="mat-body-1" *ngIf="this.statistical?.created"> |
123 | + {{ translate.instant('import.message.create-entities', {count: this.statistical.created}) }} | ||
124 | </p> | 124 | </p> |
125 | - <p class="mat-body-1" *ngIf="this.statistical?.update && this.statistical?.update.entity"> | ||
126 | - {{ translate.instant('import.message.update-entities', {count: this.statistical.update.entity}) }} | 125 | + <p class="mat-body-1" *ngIf="this.statistical?.updated"> |
126 | + {{ translate.instant('import.message.update-entities', {count: this.statistical.updated}) }} | ||
127 | </p> | 127 | </p> |
128 | - <p class="mat-body-1" *ngIf="this.statistical?.error && this.statistical?.error.entity"> | ||
129 | - {{ translate.instant('import.message.error-entities', {count: this.statistical.error.entity}) }} | 128 | + <p class="mat-body-1" style="margin-bottom: 0.8em" *ngIf="this.statistical?.errors"> |
129 | + {{ translate.instant('import.message.error-entities', {count: this.statistical.errors}) }} | ||
130 | </p> | 130 | </p> |
131 | + <mat-expansion-panel class="advanced-logs" [expanded]="false" | ||
132 | + *ngIf="this.statistical?.errorsList?.length" | ||
133 | + (opened)="initEditor()"> | ||
134 | + <mat-expansion-panel-header [collapsedHeight]="'38px'" [expandedHeight]="'38px'"> | ||
135 | + <mat-panel-title> | ||
136 | + <div class="tb-small" translate>import.details</div> | ||
137 | + </mat-panel-title> | ||
138 | + </mat-expansion-panel-header> | ||
139 | + <mat-divider></mat-divider> | ||
140 | + <div #failureDetailsEditor class="tb-failure-details"></div> | ||
141 | + </mat-expansion-panel> | ||
131 | </div> | 142 | </div> |
132 | <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px"> | 143 | <div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px"> |
133 | <button mat-raised-button | 144 | <button mat-raised-button |
@@ -26,5 +26,30 @@ | @@ -26,5 +26,30 @@ | ||
26 | .tb-import-progress{ | 26 | .tb-import-progress{ |
27 | margin: 7px 0; | 27 | margin: 7px 0; |
28 | } | 28 | } |
29 | + | ||
30 | + .tb-failure-details { | ||
31 | + width: 100%; | ||
32 | + min-width: 300px; | ||
33 | + height: 100%; | ||
34 | + min-height: 50px; | ||
35 | + margin-top: 8px; | ||
36 | + } | ||
37 | + | ||
38 | + .mat-expansion-panel { | ||
39 | + box-shadow: none; | ||
40 | + &.advanced-logs { | ||
41 | + border: 1px groove rgba(0, 0, 0, .25); | ||
42 | + padding: 0; | ||
43 | + margin-bottom: 1.6em; | ||
44 | + | ||
45 | + .mat-expansion-panel-header { | ||
46 | + padding: 0 8px; | ||
47 | + } | ||
48 | + | ||
49 | + .mat-expansion-panel-body { | ||
50 | + padding: 0; | ||
51 | + } | ||
52 | + } | ||
53 | + } | ||
29 | } | 54 | } |
30 | } | 55 | } |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { Component, Inject, OnInit, ViewChild } from '@angular/core'; | 17 | +import { AfterViewInit, Component, ElementRef, Inject, Renderer2, ViewChild } from '@angular/core'; |
18 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | 18 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
19 | import { Store } from '@ngrx/store'; | 19 | import { Store } from '@ngrx/store'; |
20 | import { AppState } from '@core/core.state'; | 20 | import { AppState } from '@core/core.state'; |
@@ -26,14 +26,20 @@ import { TranslateService } from '@ngx-translate/core'; | @@ -26,14 +26,20 @@ import { TranslateService } from '@ngx-translate/core'; | ||
26 | import { ActionNotificationShow } from '@core/notification/notification.actions'; | 26 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
27 | import { MatVerticalStepper } from '@angular/material/stepper'; | 27 | import { MatVerticalStepper } from '@angular/material/stepper'; |
28 | import { | 28 | import { |
29 | + BulkImportRequest, | ||
30 | + BulkImportResult, | ||
31 | + ColumnMapping, | ||
29 | convertCSVToJson, | 32 | convertCSVToJson, |
30 | CsvColumnParam, | 33 | CsvColumnParam, |
34 | + CSVDelimiter, | ||
31 | CsvToJsonConfig, | 35 | CsvToJsonConfig, |
32 | CsvToJsonResult, | 36 | CsvToJsonResult, |
33 | ImportEntityColumnType | 37 | ImportEntityColumnType |
34 | } from '@home/components/import-export/import-export.models'; | 38 | } from '@home/components/import-export/import-export.models'; |
35 | -import { EdgeImportEntityData, ImportEntitiesResultInfo, ImportEntityData } from '@app/shared/models/entity.models'; | ||
36 | import { ImportExportService } from '@home/components/import-export/import-export.service'; | 39 | import { ImportExportService } from '@home/components/import-export/import-export.service'; |
40 | +import { TableColumnsAssignmentComponent } from '@home/components/import-export/table-columns-assignment.component'; | ||
41 | +import { Ace } from 'ace-builds'; | ||
42 | +import { getAce } from '@shared/models/ace/ace.models'; | ||
37 | 43 | ||
38 | export interface ImportDialogCsvData { | 44 | export interface ImportDialogCsvData { |
39 | entityType: EntityType; | 45 | entityType: EntityType; |
@@ -48,15 +54,21 @@ export interface ImportDialogCsvData { | @@ -48,15 +54,21 @@ export interface ImportDialogCsvData { | ||
48 | styleUrls: ['./import-dialog-csv.component.scss'] | 54 | styleUrls: ['./import-dialog-csv.component.scss'] |
49 | }) | 55 | }) |
50 | export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvComponent, boolean> | 56 | export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvComponent, boolean> |
51 | - implements OnInit { | 57 | + implements AfterViewInit { |
52 | 58 | ||
53 | @ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper; | 59 | @ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper; |
54 | 60 | ||
61 | + @ViewChild('columnsAssignmentComponent', {static: true}) | ||
62 | + columnsAssignmentComponent: TableColumnsAssignmentComponent; | ||
63 | + | ||
64 | + @ViewChild('failureDetailsEditor') | ||
65 | + failureDetailsEditorElmRef: ElementRef; | ||
66 | + | ||
55 | entityType: EntityType; | 67 | entityType: EntityType; |
56 | importTitle: string; | 68 | importTitle: string; |
57 | importFileLabel: string; | 69 | importFileLabel: string; |
58 | 70 | ||
59 | - delimiters: {key: string, value: string}[] = [{ | 71 | + delimiters: { key: CSVDelimiter, value: string }[] = [{ |
60 | key: ',', | 72 | key: ',', |
61 | value: ',' | 73 | value: ',' |
62 | }, { | 74 | }, { |
@@ -77,9 +89,10 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -77,9 +89,10 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
77 | columnTypesFormGroup: FormGroup; | 89 | columnTypesFormGroup: FormGroup; |
78 | 90 | ||
79 | isImportData = false; | 91 | isImportData = false; |
80 | - progressCreate = 0; | ||
81 | - statistical: ImportEntitiesResultInfo; | 92 | + statistical: BulkImportResult; |
82 | 93 | ||
94 | + private allowAssignColumn: ImportEntityColumnType[]; | ||
95 | + private initEditorComponent = false; | ||
83 | private parseData: CsvToJsonResult; | 96 | private parseData: CsvToJsonResult; |
84 | 97 | ||
85 | constructor(protected store: Store<AppState>, | 98 | constructor(protected store: Store<AppState>, |
@@ -88,7 +101,8 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -88,7 +101,8 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
88 | public dialogRef: MatDialogRef<ImportDialogCsvComponent, boolean>, | 101 | public dialogRef: MatDialogRef<ImportDialogCsvComponent, boolean>, |
89 | public translate: TranslateService, | 102 | public translate: TranslateService, |
90 | private importExport: ImportExportService, | 103 | private importExport: ImportExportService, |
91 | - private fb: FormBuilder) { | 104 | + private fb: FormBuilder, |
105 | + private renderer: Renderer2) { | ||
92 | super(store, router, dialogRef); | 106 | super(store, router, dialogRef); |
93 | this.entityType = data.entityType; | 107 | this.entityType = data.entityType; |
94 | this.importTitle = data.importTitle; | 108 | this.importTitle = data.importTitle; |
@@ -109,7 +123,12 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -109,7 +123,12 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
109 | }); | 123 | }); |
110 | } | 124 | } |
111 | 125 | ||
112 | - ngOnInit(): void { | 126 | + ngAfterViewInit() { |
127 | + let columns = this.columnsAssignmentComponent.columnTypes; | ||
128 | + if (this.entityType === EntityType.DEVICE) { | ||
129 | + columns = columns.concat(this.columnsAssignmentComponent.columnDeviceCredentials); | ||
130 | + } | ||
131 | + this.allowAssignColumn = columns.map(column => column.value); | ||
113 | } | 132 | } |
114 | 133 | ||
115 | cancel(): void { | 134 | cancel(): void { |
@@ -157,8 +176,10 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -157,8 +176,10 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
157 | return convertCSVToJson(importData, config, | 176 | return convertCSVToJson(importData, config, |
158 | (messageId, params) => { | 177 | (messageId, params) => { |
159 | this.store.dispatch(new ActionNotificationShow( | 178 | this.store.dispatch(new ActionNotificationShow( |
160 | - {message: this.translate.instant(messageId, params), | ||
161 | - type: 'error'})); | 179 | + { |
180 | + message: this.translate.instant(messageId, params), | ||
181 | + type: 'error' | ||
182 | + })); | ||
162 | } | 183 | } |
163 | ); | 184 | ); |
164 | } | 185 | } |
@@ -168,9 +189,14 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -168,9 +189,14 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
168 | const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value; | 189 | const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value; |
169 | for (let i = 0; i < this.parseData.headers.length; i++) { | 190 | for (let i = 0; i < this.parseData.headers.length; i++) { |
170 | let columnParam: CsvColumnParam; | 191 | let columnParam: CsvColumnParam; |
171 | - if (isHeader && this.parseData.headers[i].search(/^(name|type|label)$/im) === 0) { | 192 | + let findEntityColumnType: ImportEntityColumnType; |
193 | + if (isHeader) { | ||
194 | + const headerColumnName = this.parseData.headers[i].toUpperCase(); | ||
195 | + findEntityColumnType = this.allowAssignColumn.find(column => column === headerColumnName); | ||
196 | + } | ||
197 | + if (isHeader && findEntityColumnType) { | ||
172 | columnParam = { | 198 | columnParam = { |
173 | - type: ImportEntityColumnType[this.parseData.headers[i].toLowerCase()], | 199 | + type: findEntityColumnType, |
174 | key: this.parseData.headers[i].toLowerCase(), | 200 | key: this.parseData.headers[i].toLowerCase(), |
175 | sampleData: this.parseData.rows[0][i] | 201 | sampleData: this.parseData.rows[0][i] |
176 | }; | 202 | }; |
@@ -188,76 +214,16 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -188,76 +214,16 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
188 | 214 | ||
189 | 215 | ||
190 | private addEntities() { | 216 | private addEntities() { |
191 | - const importData = this.parseData; | ||
192 | - const parameterColumns: CsvColumnParam[] = this.columnTypesFormGroup.get('columnsParam').value; | ||
193 | - const entitiesData: ImportEntityData[] = []; | ||
194 | - let sentDataLength = 0; | ||
195 | - for (let row = 0; row < importData.rows.length; row++) { | ||
196 | - const entityData: ImportEntityData = this.constructDraftImportEntityData(); | ||
197 | - const i = row; | ||
198 | - for (let j = 0; j < parameterColumns.length; j++) { | ||
199 | - switch (parameterColumns[j].type) { | ||
200 | - case ImportEntityColumnType.serverAttribute: | ||
201 | - entityData.attributes.server.push({ | ||
202 | - key: parameterColumns[j].key, | ||
203 | - value: importData.rows[i][j] | ||
204 | - }); | ||
205 | - break; | ||
206 | - case ImportEntityColumnType.timeseries: | ||
207 | - entityData.timeseries.push({ | ||
208 | - key: parameterColumns[j].key, | ||
209 | - value: importData.rows[i][j] | ||
210 | - }); | ||
211 | - break; | ||
212 | - case ImportEntityColumnType.sharedAttribute: | ||
213 | - entityData.attributes.shared.push({ | ||
214 | - key: parameterColumns[j].key, | ||
215 | - value: importData.rows[i][j] | ||
216 | - }); | ||
217 | - break; | ||
218 | - case ImportEntityColumnType.accessToken: | ||
219 | - entityData.accessToken = importData.rows[i][j]; | ||
220 | - break; | ||
221 | - case ImportEntityColumnType.name: | ||
222 | - entityData.name = importData.rows[i][j]; | ||
223 | - break; | ||
224 | - case ImportEntityColumnType.type: | ||
225 | - entityData.type = importData.rows[i][j]; | ||
226 | - break; | ||
227 | - case ImportEntityColumnType.label: | ||
228 | - entityData.label = importData.rows[i][j]; | ||
229 | - break; | ||
230 | - case ImportEntityColumnType.isGateway: | ||
231 | - entityData.gateway = importData.rows[i][j]; | ||
232 | - break; | ||
233 | - case ImportEntityColumnType.description: | ||
234 | - entityData.description = importData.rows[i][j]; | ||
235 | - break; | ||
236 | - case ImportEntityColumnType.edgeLicenseKey: | ||
237 | - (entityData as EdgeImportEntityData).edgeLicenseKey = importData.rows[i][j]; | ||
238 | - break; | ||
239 | - case ImportEntityColumnType.cloudEndpoint: | ||
240 | - (entityData as EdgeImportEntityData).cloudEndpoint = importData.rows[i][j]; | ||
241 | - break; | ||
242 | - case ImportEntityColumnType.routingKey: | ||
243 | - (entityData as EdgeImportEntityData).routingKey = importData.rows[i][j]; | ||
244 | - break; | ||
245 | - case ImportEntityColumnType.secret: | ||
246 | - (entityData as EdgeImportEntityData).secret = importData.rows[i][j]; | ||
247 | - break; | ||
248 | - } | 217 | + const entitiesData: BulkImportRequest = { |
218 | + file: this.selectFileFormGroup.get('importData').value, | ||
219 | + mapping: { | ||
220 | + columns: this.processingColumnsParams(), | ||
221 | + delimiter: this.importParametersFormGroup.get('delim').value, | ||
222 | + header: this.importParametersFormGroup.get('isHeader').value, | ||
223 | + update: this.importParametersFormGroup.get('isUpdate').value | ||
249 | } | 224 | } |
250 | - entitiesData.push(entityData); | ||
251 | - } | ||
252 | - const createImportEntityCompleted = () => { | ||
253 | - sentDataLength++; | ||
254 | - this.progressCreate = Math.round((sentDataLength / importData.rows.length) * 100); | ||
255 | }; | 225 | }; |
256 | - | ||
257 | - const isUpdate: boolean = this.importParametersFormGroup.get('isUpdate').value; | ||
258 | - | ||
259 | - this.importExport.importEntities(entitiesData, this.entityType, isUpdate, | ||
260 | - createImportEntityCompleted, {ignoreErrors: true, resendRequest: true}).subscribe( | 226 | + this.importExport.bulkImportEntities(entitiesData, this.entityType, {ignoreErrors: true}).subscribe( |
261 | (result) => { | 227 | (result) => { |
262 | this.statistical = result; | 228 | this.statistical = result; |
263 | this.isImportData = false; | 229 | this.isImportData = false; |
@@ -266,31 +232,63 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | @@ -266,31 +232,63 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom | ||
266 | ); | 232 | ); |
267 | } | 233 | } |
268 | 234 | ||
269 | - private constructDraftImportEntityData(): ImportEntityData { | ||
270 | - const entityData: ImportEntityData = { | ||
271 | - name: '', | ||
272 | - type: '', | ||
273 | - description: '', | ||
274 | - gateway: null, | ||
275 | - label: '', | ||
276 | - accessToken: '', | ||
277 | - attributes: { | ||
278 | - server: [], | ||
279 | - shared: [] | ||
280 | - }, | ||
281 | - timeseries: [] | ||
282 | - }; | ||
283 | - if (this.entityType === EntityType.EDGE) { | ||
284 | - const edgeEntityData: EdgeImportEntityData = entityData as EdgeImportEntityData; | ||
285 | - edgeEntityData.edgeLicenseKey = ''; | ||
286 | - edgeEntityData.cloudEndpoint = ''; | ||
287 | - edgeEntityData.routingKey = ''; | ||
288 | - edgeEntityData.secret = ''; | ||
289 | - return edgeEntityData; | ||
290 | - } else { | ||
291 | - return entityData; | 235 | + private processingColumnsParams(): Array<ColumnMapping> { |
236 | + const parameterColumns: CsvColumnParam[] = this.columnTypesFormGroup.get('columnsParam').value; | ||
237 | + const allowKeyForTypeColumns: ImportEntityColumnType[] = [ | ||
238 | + ImportEntityColumnType.serverAttribute, | ||
239 | + ImportEntityColumnType.timeseries, | ||
240 | + ImportEntityColumnType.sharedAttribute | ||
241 | + ]; | ||
242 | + return parameterColumns.map(column => ({ | ||
243 | + type: column.type, | ||
244 | + key: allowKeyForTypeColumns.some(type => type === column.type) ? column.key : undefined | ||
245 | + })); | ||
246 | + } | ||
247 | + | ||
248 | + initEditor() { | ||
249 | + if (!this.initEditorComponent) { | ||
250 | + this.createEditor(this.failureDetailsEditorElmRef, this.statistical.errorsList); | ||
292 | } | 251 | } |
293 | } | 252 | } |
294 | 253 | ||
254 | + private createEditor(editorElementRef: ElementRef, contents: string[]): void { | ||
255 | + const editorElement = editorElementRef.nativeElement; | ||
256 | + let editorOptions: Partial<Ace.EditorOptions> = { | ||
257 | + mode: 'ace/mode/java', | ||
258 | + theme: 'ace/theme/github', | ||
259 | + showGutter: false, | ||
260 | + showPrintMargin: false, | ||
261 | + readOnly: true | ||
262 | + }; | ||
263 | + | ||
264 | + const advancedOptions = { | ||
265 | + enableSnippets: false, | ||
266 | + enableBasicAutocompletion: false, | ||
267 | + enableLiveAutocompletion: false | ||
268 | + }; | ||
269 | + | ||
270 | + editorOptions = {...editorOptions, ...advancedOptions}; | ||
271 | + const content = contents.map(error => error.replace('\n', '')).join('\n'); | ||
272 | + getAce().subscribe( | ||
273 | + (ace) => { | ||
274 | + const editor = ace.edit(editorElement, editorOptions); | ||
275 | + editor.session.setUseWrapMode(false); | ||
276 | + editor.setValue(content, -1); | ||
277 | + this.updateEditorSize(editorElement, content, editor); | ||
278 | + } | ||
279 | + ); | ||
280 | + } | ||
281 | + | ||
282 | + private updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) { | ||
283 | + let newHeight = 200; | ||
284 | + if (content && content.length > 0) { | ||
285 | + const lines = content.split('\n'); | ||
286 | + newHeight = 16 * lines.length + 24; | ||
287 | + } | ||
288 | + const minHeight = Math.min(200, newHeight); | ||
289 | + this.renderer.setStyle(editorElement, 'minHeight', minHeight.toString() + 'px'); | ||
290 | + this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); | ||
291 | + editor.resize(); | ||
292 | + } | ||
295 | 293 | ||
296 | } | 294 | } |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { Widget, WidgetType, WidgetTypeDetails } from '@app/shared/models/widget.models'; | 17 | +import { Widget, WidgetTypeDetails } from '@app/shared/models/widget.models'; |
18 | import { DashboardLayoutId } from '@shared/models/dashboard.models'; | 18 | import { DashboardLayoutId } from '@shared/models/dashboard.models'; |
19 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; | 19 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
20 | 20 | ||
@@ -38,6 +38,8 @@ export interface CsvToJsonResult { | @@ -38,6 +38,8 @@ export interface CsvToJsonResult { | ||
38 | rows?: any[][]; | 38 | rows?: any[][]; |
39 | } | 39 | } |
40 | 40 | ||
41 | +export type CSVDelimiter = ',' | ';' | '|' | '\t'; | ||
42 | + | ||
41 | export enum ImportEntityColumnType { | 43 | export enum ImportEntityColumnType { |
42 | name = 'NAME', | 44 | name = 'NAME', |
43 | type = 'TYPE', | 45 | type = 'TYPE', |
@@ -46,8 +48,22 @@ export enum ImportEntityColumnType { | @@ -46,8 +48,22 @@ export enum ImportEntityColumnType { | ||
46 | sharedAttribute = 'SHARED_ATTRIBUTE', | 48 | sharedAttribute = 'SHARED_ATTRIBUTE', |
47 | serverAttribute = 'SERVER_ATTRIBUTE', | 49 | serverAttribute = 'SERVER_ATTRIBUTE', |
48 | timeseries = 'TIMESERIES', | 50 | timeseries = 'TIMESERIES', |
49 | - entityField = 'ENTITY_FIELD', | ||
50 | accessToken = 'ACCESS_TOKEN', | 51 | accessToken = 'ACCESS_TOKEN', |
52 | + x509 = 'X509', | ||
53 | + mqttClientId = 'MQTT_CLIENT_ID', | ||
54 | + mqttUserName = 'MQTT_USER_NAME', | ||
55 | + mqttPassword = 'MQTT_PASSWORD', | ||
56 | + lwm2mClientEndpoint = 'LWM2M_CLIENT_ENDPOINT', | ||
57 | + lwm2mClientSecurityConfigMode = 'LWM2M_CLIENT_SECURITY_CONFIG_MODE', | ||
58 | + lwm2mClientIdentity = 'LWM2M_CLIENT_IDENTITY', | ||
59 | + lwm2mClientKey = 'LWM2M_CLIENT_KEY', | ||
60 | + lwm2mClientCert = 'LWM2M_CLIENT_CERT', | ||
61 | + lwm2mBootstrapServerSecurityMode = 'LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE', | ||
62 | + lwm2mBootstrapServerClientPublicKeyOrId = 'LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID', | ||
63 | + lwm2mBootstrapServerClientSecretKey = 'LWM2M_BOOTSTRAP_SERVER_SECRET_KEY', | ||
64 | + lwm2mServerSecurityMode = 'LWM2M_SERVER_SECURITY_MODE', | ||
65 | + lwm2mServerClientPublicKeyOrId = 'LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID', | ||
66 | + lwm2mServerClientSecretKey = 'LWM2M_SERVER_CLIENT_SECRET_KEY', | ||
51 | isGateway = 'IS_GATEWAY', | 67 | isGateway = 'IS_GATEWAY', |
52 | description = 'DESCRIPTION', | 68 | description = 'DESCRIPTION', |
53 | edgeLicenseKey = 'EDGE_LICENSE_KEY', | 69 | edgeLicenseKey = 'EDGE_LICENSE_KEY', |
@@ -68,8 +84,22 @@ export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType | @@ -68,8 +84,22 @@ export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType | ||
68 | [ImportEntityColumnType.sharedAttribute, 'import.column-type.shared-attribute'], | 84 | [ImportEntityColumnType.sharedAttribute, 'import.column-type.shared-attribute'], |
69 | [ImportEntityColumnType.serverAttribute, 'import.column-type.server-attribute'], | 85 | [ImportEntityColumnType.serverAttribute, 'import.column-type.server-attribute'], |
70 | [ImportEntityColumnType.timeseries, 'import.column-type.timeseries'], | 86 | [ImportEntityColumnType.timeseries, 'import.column-type.timeseries'], |
71 | - [ImportEntityColumnType.entityField, 'import.column-type.entity-field'], | ||
72 | [ImportEntityColumnType.accessToken, 'import.column-type.access-token'], | 87 | [ImportEntityColumnType.accessToken, 'import.column-type.access-token'], |
88 | + [ImportEntityColumnType.x509, 'import.column-type.x509'], | ||
89 | + [ImportEntityColumnType.mqttClientId, 'import.column-type.mqtt.client-id'], | ||
90 | + [ImportEntityColumnType.mqttUserName, 'import.column-type.mqtt.user-name'], | ||
91 | + [ImportEntityColumnType.mqttPassword, 'import.column-type.mqtt.password'], | ||
92 | + [ImportEntityColumnType.lwm2mClientEndpoint, 'import.column-type.lwm2m.client-endpoint'], | ||
93 | + [ImportEntityColumnType.lwm2mClientSecurityConfigMode, 'import.column-type.lwm2m.security-config-mode'], | ||
94 | + [ImportEntityColumnType.lwm2mClientIdentity, 'import.column-type.lwm2m.client-identity'], | ||
95 | + [ImportEntityColumnType.lwm2mClientKey, 'import.column-type.lwm2m.client-key'], | ||
96 | + [ImportEntityColumnType.lwm2mClientCert, 'import.column-type.lwm2m.client-cert'], | ||
97 | + [ImportEntityColumnType.lwm2mBootstrapServerSecurityMode, 'import.column-type.lwm2m.bootstrap-server-security-mode'], | ||
98 | + [ImportEntityColumnType.lwm2mBootstrapServerClientPublicKeyOrId, 'import.column-type.lwm2m.bootstrap-server-public-key-id'], | ||
99 | + [ImportEntityColumnType.lwm2mBootstrapServerClientSecretKey, 'import.column-type.lwm2m.bootstrap-server-secret-key'], | ||
100 | + [ImportEntityColumnType.lwm2mServerSecurityMode, 'import.column-type.lwm2m.lwm2m-server-security-mode'], | ||
101 | + [ImportEntityColumnType.lwm2mServerClientPublicKeyOrId, 'import.column-type.lwm2m.lwm2m-server-public-key-id'], | ||
102 | + [ImportEntityColumnType.lwm2mServerClientSecretKey, 'import.column-type.lwm2m.lwm2m-server-secret-key'], | ||
73 | [ImportEntityColumnType.isGateway, 'import.column-type.isgateway'], | 103 | [ImportEntityColumnType.isGateway, 'import.column-type.isgateway'], |
74 | [ImportEntityColumnType.description, 'import.column-type.description'], | 104 | [ImportEntityColumnType.description, 'import.column-type.description'], |
75 | [ImportEntityColumnType.edgeLicenseKey, 'import.column-type.edge-license-key'], | 105 | [ImportEntityColumnType.edgeLicenseKey, 'import.column-type.edge-license-key'], |
@@ -85,6 +115,28 @@ export interface CsvColumnParam { | @@ -85,6 +115,28 @@ export interface CsvColumnParam { | ||
85 | sampleData: any; | 115 | sampleData: any; |
86 | } | 116 | } |
87 | 117 | ||
118 | +export interface ColumnMapping { | ||
119 | + type: ImportEntityColumnType; | ||
120 | + key?: string; | ||
121 | +} | ||
122 | + | ||
123 | +export interface BulkImportRequest { | ||
124 | + file: string; | ||
125 | + mapping: { | ||
126 | + columns: Array<ColumnMapping>; | ||
127 | + delimiter: CSVDelimiter; | ||
128 | + header: boolean; | ||
129 | + update: boolean; | ||
130 | + }; | ||
131 | +} | ||
132 | + | ||
133 | +export interface BulkImportResult { | ||
134 | + created: number; | ||
135 | + updated: number; | ||
136 | + errors: number; | ||
137 | + errorsList: Array<string>; | ||
138 | +} | ||
139 | + | ||
88 | export interface FileType { | 140 | export interface FileType { |
89 | mimeType: string; | 141 | mimeType: string; |
90 | extension: string; | 142 | extension: string; |
@@ -21,7 +21,7 @@ import { Store } from '@ngrx/store'; | @@ -21,7 +21,7 @@ import { Store } from '@ngrx/store'; | ||
21 | import { AppState } from '@core/core.state'; | 21 | import { AppState } from '@core/core.state'; |
22 | import { ActionNotificationShow } from '@core/notification/notification.actions'; | 22 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
23 | import { Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models'; | 23 | import { Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models'; |
24 | -import { deepClone, isDefined, isObject, isUndefined } from '@core/utils'; | 24 | +import { deepClone, isDefined, isObject, isString, isUndefined } from '@core/utils'; |
25 | import { WINDOW } from '@core/services/window.service'; | 25 | import { WINDOW } from '@core/services/window.service'; |
26 | import { DOCUMENT } from '@angular/common'; | 26 | import { DOCUMENT } from '@angular/common'; |
27 | import { | 27 | import { |
@@ -44,7 +44,7 @@ import { | @@ -44,7 +44,7 @@ import { | ||
44 | EntityAliasesDialogData | 44 | EntityAliasesDialogData |
45 | } from '@home/components/alias/entity-aliases-dialog.component'; | 45 | } from '@home/components/alias/entity-aliases-dialog.component'; |
46 | import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; | 46 | import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; |
47 | -import { FileType, ImportWidgetResult, JSON_TYPE, WidgetsBundleItem, ZIP_TYPE } from './import-export.models'; | 47 | +import { FileType, ImportWidgetResult, JSON_TYPE, WidgetsBundleItem, ZIP_TYPE, BulkImportRequest, BulkImportResult } from './import-export.models'; |
48 | import { EntityType } from '@shared/models/entity-type.models'; | 48 | import { EntityType } from '@shared/models/entity-type.models'; |
49 | import { UtilsService } from '@core/services/utils.service'; | 49 | import { UtilsService } from '@core/services/utils.service'; |
50 | import { WidgetService } from '@core/http/widget.service'; | 50 | import { WidgetService } from '@core/http/widget.service'; |
@@ -59,6 +59,9 @@ import { DeviceProfileService } from '@core/http/device-profile.service'; | @@ -59,6 +59,9 @@ import { DeviceProfileService } from '@core/http/device-profile.service'; | ||
59 | import { DeviceProfile } from '@shared/models/device.models'; | 59 | import { DeviceProfile } from '@shared/models/device.models'; |
60 | import { TenantProfile } from '@shared/models/tenant.model'; | 60 | import { TenantProfile } from '@shared/models/tenant.model'; |
61 | import { TenantProfileService } from '@core/http/tenant-profile.service'; | 61 | import { TenantProfileService } from '@core/http/tenant-profile.service'; |
62 | +import { DeviceService } from '@core/http/device.service'; | ||
63 | +import { AssetService } from '@core/http/asset.service'; | ||
64 | +import { EdgeService } from '@core/http/edge.service'; | ||
62 | 65 | ||
63 | // @dynamic | 66 | // @dynamic |
64 | @Injectable() | 67 | @Injectable() |
@@ -75,6 +78,9 @@ export class ImportExportService { | @@ -75,6 +78,9 @@ export class ImportExportService { | ||
75 | private tenantProfileService: TenantProfileService, | 78 | private tenantProfileService: TenantProfileService, |
76 | private entityService: EntityService, | 79 | private entityService: EntityService, |
77 | private ruleChainService: RuleChainService, | 80 | private ruleChainService: RuleChainService, |
81 | + private deviceService: DeviceService, | ||
82 | + private assetService: AssetService, | ||
83 | + private edgeService: EdgeService, | ||
78 | private utils: UtilsService, | 84 | private utils: UtilsService, |
79 | private itembuffer: ItemBufferService, | 85 | private itembuffer: ItemBufferService, |
80 | private dialog: MatDialog) { | 86 | private dialog: MatDialog) { |
@@ -342,6 +348,17 @@ export class ImportExportService { | @@ -342,6 +348,17 @@ export class ImportExportService { | ||
342 | ); | 348 | ); |
343 | } | 349 | } |
344 | 350 | ||
351 | + public bulkImportEntities(entitiesData: BulkImportRequest, entityType: EntityType, config?: RequestConfig): Observable<BulkImportResult> { | ||
352 | + switch (entityType) { | ||
353 | + case EntityType.DEVICE: | ||
354 | + return this.deviceService.bulkImportDevices(entitiesData, config); | ||
355 | + case EntityType.ASSET: | ||
356 | + return this.assetService.bulkImportAssets(entitiesData, config); | ||
357 | + case EntityType.EDGE: | ||
358 | + return this.edgeService.bulkImportEdges(entitiesData, config); | ||
359 | + } | ||
360 | + } | ||
361 | + | ||
345 | public importEntities(entitiesData: ImportEntityData[], entityType: EntityType, updateData: boolean, | 362 | public importEntities(entitiesData: ImportEntityData[], entityType: EntityType, updateData: boolean, |
346 | importEntityCompleted?: () => void, config?: RequestConfig): Observable<ImportEntitiesResultInfo> { | 363 | importEntityCompleted?: () => void, config?: RequestConfig): Observable<ImportEntitiesResultInfo> { |
347 | let partSize = 100; | 364 | let partSize = 100; |
@@ -563,6 +580,8 @@ export class ImportExportService { | @@ -563,6 +580,8 @@ export class ImportExportService { | ||
563 | if (isObject(obj2[key])) { | 580 | if (isObject(obj2[key])) { |
564 | obj1[key] = obj1[key] || {}; | 581 | obj1[key] = obj1[key] || {}; |
565 | obj1[key] = {...obj1[key], ...this.sumObject(obj1[key], obj2[key])}; | 582 | obj1[key] = {...obj1[key], ...this.sumObject(obj1[key], obj2[key])}; |
583 | + } else if (isString(obj2[key])) { | ||
584 | + obj1[key] = (obj1[key] || '') + `${obj2[key]}\n`; | ||
566 | } else { | 585 | } else { |
567 | obj1[key] = (obj1[key] || 0) + obj2[key]; | 586 | obj1[key] = (obj1[key] || 0) + obj2[key]; |
568 | } | 587 | } |
@@ -23,23 +23,28 @@ | @@ -23,23 +23,28 @@ | ||
23 | </mat-cell> | 23 | </mat-cell> |
24 | </ng-container> | 24 | </ng-container> |
25 | <ng-container matColumnDef="sampleData"> | 25 | <ng-container matColumnDef="sampleData"> |
26 | - <mat-header-cell *matHeaderCellDef style="flex: 0 0 30%;" class="mat-column-sampleData"> {{ 'import.column-example' | translate }} </mat-header-cell> | 26 | + <mat-header-cell *matHeaderCellDef class="mat-column-sampleData"> {{ 'import.column-example' | translate }} </mat-header-cell> |
27 | <mat-cell *matCellDef="let column"> | 27 | <mat-cell *matCellDef="let column"> |
28 | {{column.sampleData}} | 28 | {{column.sampleData}} |
29 | </mat-cell> | 29 | </mat-cell> |
30 | </ng-container> | 30 | </ng-container> |
31 | <ng-container matColumnDef="type"> | 31 | <ng-container matColumnDef="type"> |
32 | - <mat-header-cell *matHeaderCellDef style="flex: 0 0 40%" class="mat-column-type"> {{ 'import.column-type.column-type' | translate }} </mat-header-cell> | 32 | + <mat-header-cell *matHeaderCellDef class="mat-column-type"> {{ 'import.column-type.column-type' | translate }} </mat-header-cell> |
33 | <mat-cell *matCellDef="let column"> | 33 | <mat-cell *matCellDef="let column"> |
34 | - <mat-select matInput [(ngModel)]="column.type" (ngModelChange)="columnsUpdated()"> | 34 | + <mat-select [(ngModel)]="column.type" (ngModelChange)="columnsUpdated()"> |
35 | <mat-option *ngFor="let type of columnTypes" [value]="type.value" [disabled]="type.disabled"> | 35 | <mat-option *ngFor="let type of columnTypes" [value]="type.value" [disabled]="type.disabled"> |
36 | {{ columnTypesTranslations.get(type.value) | translate }} | 36 | {{ columnTypesTranslations.get(type.value) | translate }} |
37 | </mat-option> | 37 | </mat-option> |
38 | + <mat-optgroup label="{{ 'import.credentials' | translate }}" *ngIf="entityType === entityTypeDevice"> | ||
39 | + <mat-option *ngFor="let credential of columnDeviceCredentials" [value]="credential.value" [disabled]="credential.disabled"> | ||
40 | + {{ columnTypesTranslations.get(credential.value) | translate }} | ||
41 | + </mat-option> | ||
42 | + </mat-optgroup> | ||
38 | </mat-select> | 43 | </mat-select> |
39 | </mat-cell> | 44 | </mat-cell> |
40 | </ng-container> | 45 | </ng-container> |
41 | <ng-container matColumnDef="key"> | 46 | <ng-container matColumnDef="key"> |
42 | - <mat-header-cell *matHeaderCellDef style="flex: 0 0 30%"> {{ 'import.column-key' | translate }} </mat-header-cell> | 47 | + <mat-header-cell *matHeaderCellDef class="mat-column-key"> {{ 'import.column-key' | translate }} </mat-header-cell> |
43 | <mat-cell *matCellDef="let column"> | 48 | <mat-cell *matCellDef="let column"> |
44 | <mat-form-field floatLabel="always" hideRequiredMarker | 49 | <mat-form-field floatLabel="always" hideRequiredMarker |
45 | *ngIf="isColumnTypeDiffers(column.type)"> | 50 | *ngIf="isColumnTypeDiffers(column.type)"> |
@@ -14,14 +14,21 @@ | @@ -14,14 +14,21 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | :host { | 16 | :host { |
17 | + overflow-x: auto; | ||
18 | + | ||
17 | .mat-column-order { | 19 | .mat-column-order { |
18 | flex: 0 0 40px; | 20 | flex: 0 0 40px; |
19 | } | 21 | } |
20 | .mat-column-sampleData { | 22 | .mat-column-sampleData { |
21 | flex: 0 0 120px; | 23 | flex: 0 0 120px; |
22 | min-width: 120px; | 24 | min-width: 120px; |
25 | + max-width: 230px; | ||
23 | } | 26 | } |
24 | .mat-column-type { | 27 | .mat-column-type { |
28 | + flex: 0 0 180px; | ||
29 | + min-width: 180px; | ||
30 | + } | ||
31 | + .mat-column-key { | ||
25 | flex: 0 0 120px; | 32 | flex: 0 0 120px; |
26 | min-width: 120px; | 33 | min-width: 120px; |
27 | } | 34 | } |
@@ -57,8 +57,12 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | @@ -57,8 +57,12 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | ||
57 | 57 | ||
58 | columnTypes: AssignmentColumnType[] = []; | 58 | columnTypes: AssignmentColumnType[] = []; |
59 | 59 | ||
60 | + columnDeviceCredentials: AssignmentColumnType[] = []; | ||
61 | + | ||
60 | columnTypesTranslations = importEntityColumnTypeTranslations; | 62 | columnTypesTranslations = importEntityColumnTypeTranslations; |
61 | 63 | ||
64 | + readonly entityTypeDevice = EntityType.DEVICE; | ||
65 | + | ||
62 | private columns: CsvColumnParam[]; | 66 | private columns: CsvColumnParam[]; |
63 | 67 | ||
64 | private valid = true; | 68 | private valid = true; |
@@ -83,9 +87,26 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | @@ -83,9 +87,26 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | ||
83 | { value: ImportEntityColumnType.sharedAttribute }, | 87 | { value: ImportEntityColumnType.sharedAttribute }, |
84 | { value: ImportEntityColumnType.serverAttribute }, | 88 | { value: ImportEntityColumnType.serverAttribute }, |
85 | { value: ImportEntityColumnType.timeseries }, | 89 | { value: ImportEntityColumnType.timeseries }, |
86 | - { value: ImportEntityColumnType.accessToken }, | ||
87 | { value: ImportEntityColumnType.isGateway } | 90 | { value: ImportEntityColumnType.isGateway } |
88 | ); | 91 | ); |
92 | + this.columnDeviceCredentials.push( | ||
93 | + { value: ImportEntityColumnType.accessToken }, | ||
94 | + { value: ImportEntityColumnType.x509 }, | ||
95 | + { value: ImportEntityColumnType.mqttClientId }, | ||
96 | + { value: ImportEntityColumnType.mqttUserName }, | ||
97 | + { value: ImportEntityColumnType.mqttPassword }, | ||
98 | + { value: ImportEntityColumnType.lwm2mClientEndpoint }, | ||
99 | + { value: ImportEntityColumnType.lwm2mClientSecurityConfigMode }, | ||
100 | + { value: ImportEntityColumnType.lwm2mClientIdentity }, | ||
101 | + { value: ImportEntityColumnType.lwm2mClientKey }, | ||
102 | + { value: ImportEntityColumnType.lwm2mClientCert }, | ||
103 | + { value: ImportEntityColumnType.lwm2mBootstrapServerSecurityMode }, | ||
104 | + { value: ImportEntityColumnType.lwm2mBootstrapServerClientPublicKeyOrId }, | ||
105 | + { value: ImportEntityColumnType.lwm2mBootstrapServerClientSecretKey }, | ||
106 | + { value: ImportEntityColumnType.lwm2mServerSecurityMode }, | ||
107 | + { value: ImportEntityColumnType.lwm2mServerClientPublicKeyOrId }, | ||
108 | + { value: ImportEntityColumnType.lwm2mServerClientSecretKey }, | ||
109 | + ); | ||
89 | break; | 110 | break; |
90 | case EntityType.ASSET: | 111 | case EntityType.ASSET: |
91 | this.columnTypes.push( | 112 | this.columnTypes.push( |
@@ -123,8 +144,6 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | @@ -123,8 +144,6 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | ||
123 | const isSelectName = this.columns.findIndex((column) => column.type === ImportEntityColumnType.name) > -1; | 144 | const isSelectName = this.columns.findIndex((column) => column.type === ImportEntityColumnType.name) > -1; |
124 | const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1; | 145 | const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1; |
125 | const isSelectLabel = this.columns.findIndex((column) => column.type === ImportEntityColumnType.label) > -1; | 146 | const isSelectLabel = this.columns.findIndex((column) => column.type === ImportEntityColumnType.label) > -1; |
126 | - const isSelectCredentials = this.columns.findIndex((column) => column.type === ImportEntityColumnType.accessToken) > -1; | ||
127 | - const isSelectGateway = this.columns.findIndex((column) => column.type === ImportEntityColumnType.isGateway) > -1; | ||
128 | const isSelectDescription = this.columns.findIndex((column) => column.type === ImportEntityColumnType.description) > -1; | 147 | const isSelectDescription = this.columns.findIndex((column) => column.type === ImportEntityColumnType.description) > -1; |
129 | const isSelectEdgeLicenseKey = this.columns.findIndex((column) => column.type === ImportEntityColumnType.edgeLicenseKey) > -1; | 148 | const isSelectEdgeLicenseKey = this.columns.findIndex((column) => column.type === ImportEntityColumnType.edgeLicenseKey) > -1; |
130 | const isSelectCloudEndpoint = this.columns.findIndex((column) => column.type === ImportEntityColumnType.cloudEndpoint) > -1; | 149 | const isSelectCloudEndpoint = this.columns.findIndex((column) => column.type === ImportEntityColumnType.cloudEndpoint) > -1; |
@@ -139,14 +158,19 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | @@ -139,14 +158,19 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce | ||
139 | this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.label).disabled = isSelectLabel; | 158 | this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.label).disabled = isSelectLabel; |
140 | this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.description).disabled = isSelectDescription; | 159 | this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.description).disabled = isSelectDescription; |
141 | 160 | ||
142 | - const isGatewayColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.isGateway); | ||
143 | - if (isGatewayColumnType) { | ||
144 | - isGatewayColumnType.disabled = isSelectGateway; | ||
145 | - } | ||
146 | - const accessTokenColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.accessToken); | ||
147 | - if (accessTokenColumnType) { | ||
148 | - accessTokenColumnType.disabled = isSelectCredentials; | 161 | + if (this.entityType === EntityType.DEVICE) { |
162 | + const isSelectGateway = this.columns.findIndex((column) => column.type === ImportEntityColumnType.isGateway) > -1; | ||
163 | + | ||
164 | + const isGatewayColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.isGateway); | ||
165 | + if (isGatewayColumnType) { | ||
166 | + isGatewayColumnType.disabled = isSelectGateway; | ||
167 | + } | ||
168 | + | ||
169 | + this.columnDeviceCredentials.forEach((columnCredential) => { | ||
170 | + columnCredential.disabled = this.columns.findIndex(column => column.type === columnCredential.value) > -1; | ||
171 | + }); | ||
149 | } | 172 | } |
173 | + | ||
150 | const edgeLicenseKeyColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.edgeLicenseKey); | 174 | const edgeLicenseKeyColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.edgeLicenseKey); |
151 | if (edgeLicenseKeyColumnType) { | 175 | if (edgeLicenseKeyColumnType) { |
152 | edgeLicenseKeyColumnType.disabled = isSelectEdgeLicenseKey; | 176 | edgeLicenseKeyColumnType.disabled = isSelectEdgeLicenseKey; |
@@ -743,6 +743,14 @@ export interface DeviceCredentialMQTTBasic { | @@ -743,6 +743,14 @@ export interface DeviceCredentialMQTTBasic { | ||
743 | password: string; | 743 | password: string; |
744 | } | 744 | } |
745 | 745 | ||
746 | +export function getDeviceCredentialMQTTDefault(): DeviceCredentialMQTTBasic { | ||
747 | + return { | ||
748 | + clientId: '', | ||
749 | + userName: '', | ||
750 | + password: '' | ||
751 | + }; | ||
752 | +} | ||
753 | + | ||
746 | export interface DeviceSearchQuery extends EntitySearchQuery { | 754 | export interface DeviceSearchQuery extends EntitySearchQuery { |
747 | deviceTypes: Array<string>; | 755 | deviceTypes: Array<string>; |
748 | } | 756 | } |
@@ -17,6 +17,8 @@ | @@ -17,6 +17,8 @@ | ||
17 | import { EntityType } from '@shared/models/entity-type.models'; | 17 | import { EntityType } from '@shared/models/entity-type.models'; |
18 | import { AttributeData } from './telemetry/telemetry.models'; | 18 | import { AttributeData } from './telemetry/telemetry.models'; |
19 | import { EntityId } from '@shared/models/id/entity-id'; | 19 | import { EntityId } from '@shared/models/id/entity-id'; |
20 | +import { DeviceCredentialMQTTBasic } from '@shared/models/device.models'; | ||
21 | +import { Lwm2mSecurityConfigModels } from '@shared/models/lwm2m-security-config.models'; | ||
20 | 22 | ||
21 | export interface EntityInfo { | 23 | export interface EntityInfo { |
22 | name?: string; | 24 | name?: string; |
@@ -32,12 +34,18 @@ export interface EntityInfoData { | @@ -32,12 +34,18 @@ export interface EntityInfoData { | ||
32 | } | 34 | } |
33 | 35 | ||
34 | export interface ImportEntityData { | 36 | export interface ImportEntityData { |
37 | + lineNumber: number; | ||
35 | name: string; | 38 | name: string; |
36 | type: string; | 39 | type: string; |
37 | label: string; | 40 | label: string; |
38 | gateway: boolean; | 41 | gateway: boolean; |
39 | description: string; | 42 | description: string; |
40 | - accessToken: string; | 43 | + credential: { |
44 | + accessToken?: string; | ||
45 | + x509?: string; | ||
46 | + mqtt?: DeviceCredentialMQTTBasic; | ||
47 | + lwm2m?: Lwm2mSecurityConfigModels; | ||
48 | + }; | ||
41 | attributes: { | 49 | attributes: { |
42 | server: AttributeData[], | 50 | server: AttributeData[], |
43 | shared: AttributeData[] | 51 | shared: AttributeData[] |
@@ -61,6 +69,7 @@ export interface ImportEntitiesResultInfo { | @@ -61,6 +69,7 @@ export interface ImportEntitiesResultInfo { | ||
61 | }; | 69 | }; |
62 | error?: { | 70 | error?: { |
63 | entity: number; | 71 | entity: number; |
72 | + errors?: string; | ||
64 | }; | 73 | }; |
65 | } | 74 | } |
66 | 75 |
@@ -58,6 +58,20 @@ export interface Lwm2mSecurityConfigModels { | @@ -58,6 +58,20 @@ export interface Lwm2mSecurityConfigModels { | ||
58 | bootstrap: BootstrapSecurityConfig; | 58 | bootstrap: BootstrapSecurityConfig; |
59 | } | 59 | } |
60 | 60 | ||
61 | + | ||
62 | +export function getLwm2mSecurityConfigModelsDefault(): Lwm2mSecurityConfigModels { | ||
63 | + return { | ||
64 | + client: { | ||
65 | + securityConfigClientMode: Lwm2mSecurityType.NO_SEC, | ||
66 | + endpoint: '' | ||
67 | + }, | ||
68 | + bootstrap: { | ||
69 | + bootstrapServer: getDefaultServerSecurityConfig(), | ||
70 | + lwm2mServer: getDefaultServerSecurityConfig() | ||
71 | + } | ||
72 | + }; | ||
73 | +} | ||
74 | + | ||
61 | export function getDefaultClientSecurityConfig(securityConfigMode: Lwm2mSecurityType, endPoint = ''): ClientSecurityConfig { | 75 | export function getDefaultClientSecurityConfig(securityConfigMode: Lwm2mSecurityType, endPoint = ''): ClientSecurityConfig { |
62 | let security = { | 76 | let security = { |
63 | securityConfigClientMode: securityConfigMode, | 77 | securityConfigClientMode: securityConfigMode, |
@@ -2187,9 +2187,11 @@ | @@ -2187,9 +2187,11 @@ | ||
2187 | "column-title": "Title", | 2187 | "column-title": "Title", |
2188 | "column-example": "Example value data", | 2188 | "column-example": "Example value data", |
2189 | "column-key": "Attribute/telemetry key", | 2189 | "column-key": "Attribute/telemetry key", |
2190 | + "credentials": "Credentials", | ||
2190 | "csv-delimiter": "CSV delimiter", | 2191 | "csv-delimiter": "CSV delimiter", |
2191 | "csv-first-line-header": "First line contains column names", | 2192 | "csv-first-line-header": "First line contains column names", |
2192 | "csv-update-data": "Update attributes/telemetry", | 2193 | "csv-update-data": "Update attributes/telemetry", |
2194 | + "details": "Details", | ||
2193 | "import-csv-number-columns-error": "A file should contain at least two columns", | 2195 | "import-csv-number-columns-error": "A file should contain at least two columns", |
2194 | "import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'", | 2196 | "import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'", |
2195 | "column-type": { | 2197 | "column-type": { |
@@ -2203,6 +2205,25 @@ | @@ -2203,6 +2205,25 @@ | ||
2203 | "timeseries": "Timeseries", | 2205 | "timeseries": "Timeseries", |
2204 | "entity-field": "Entity field", | 2206 | "entity-field": "Entity field", |
2205 | "access-token": "Access token", | 2207 | "access-token": "Access token", |
2208 | + "x509": "X.509", | ||
2209 | + "mqtt": { | ||
2210 | + "client-id": "MQTT client ID", | ||
2211 | + "user-name": "MQTT user name", | ||
2212 | + "password": "MQTT password" | ||
2213 | + }, | ||
2214 | + "lwm2m": { | ||
2215 | + "client-endpoint": "LwM2M endpoint client name", | ||
2216 | + "security-config-mode": "LwM2M security config mode", | ||
2217 | + "client-identity": "LwM2M client identity", | ||
2218 | + "client-key": "LwM2M client key", | ||
2219 | + "client-cert": "LwM2M client public key", | ||
2220 | + "bootstrap-server-security-mode": "LwM2M bootstrap server security mode", | ||
2221 | + "bootstrap-server-secret-key": "LwM2M bootstrap server secret key", | ||
2222 | + "bootstrap-server-public-key-id": "LwM2M bootstrap server public key or id", | ||
2223 | + "lwm2m-server-security-mode": "LwM2M server security mode", | ||
2224 | + "lwm2m-server-secret-key": "LwM2M server secret key", | ||
2225 | + "lwm2m-server-public-key-id": "LwM2M server public key or id" | ||
2226 | + }, | ||
2206 | "isgateway": "Is Gateway", | 2227 | "isgateway": "Is Gateway", |
2207 | "activity-time-from-gateway-device": "Activity time from gateway device", | 2228 | "activity-time-from-gateway-device": "Activity time from gateway device", |
2208 | "description": "Description", | 2229 | "description": "Description", |