Commit 33592ca0724dd1005390dbd3068f8beb32b57089

Authored by Viacheslav Klimov
1 parent 940c81b0

Bulk import for devices, assets and edges

Showing 22 changed files with 1025 additions and 61 deletions
... ... @@ -16,9 +16,12 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.RequiredArgsConstructor;
  20 +import lombok.extern.slf4j.Slf4j;
19 21 import org.springframework.http.HttpStatus;
20 22 import org.springframework.security.access.prepost.PreAuthorize;
21 23 import org.springframework.web.bind.annotation.PathVariable;
  24 +import org.springframework.web.bind.annotation.PostMapping;
22 25 import org.springframework.web.bind.annotation.RequestBody;
23 26 import org.springframework.web.bind.annotation.RequestMapping;
24 27 import org.springframework.web.bind.annotation.RequestMethod;
... ... @@ -34,7 +37,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
34 37 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
35 38 import org.thingsboard.server.common.data.audit.ActionType;
36 39 import org.thingsboard.server.common.data.edge.Edge;
37   -import org.thingsboard.server.common.data.edge.EdgeEventType;
38 40 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
39 41 import org.thingsboard.server.common.data.edge.EdgeEventActionType;
40 42 import org.thingsboard.server.common.data.exception.ThingsboardException;
... ... @@ -48,6 +50,9 @@ import org.thingsboard.server.common.data.page.TimePageLink;
48 50 import org.thingsboard.server.dao.exception.IncorrectParameterException;
49 51 import org.thingsboard.server.dao.model.ModelConstants;
50 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 56 import org.thingsboard.server.service.security.model.SecurityUser;
52 57 import org.thingsboard.server.service.security.permission.Operation;
53 58 import org.thingsboard.server.service.security.permission.Resource;
... ... @@ -63,7 +68,10 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
63 68 @RestController
64 69 @TbCoreComponent
65 70 @RequestMapping("/api")
  71 +@RequiredArgsConstructor
  72 +@Slf4j
66 73 public class AssetController extends BaseController {
  74 + private final AssetBulkImportService assetBulkImportService;
67 75
68 76 public static final String ASSET_ID = "assetId";
69 77
... ... @@ -108,13 +116,7 @@ public class AssetController extends BaseController {
108 116
109 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 121 return savedAsset;
120 122 } catch (Exception e) {
... ... @@ -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 143 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
128 144 @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE)
129 145 @ResponseStatus(value = HttpStatus.OK)
... ... @@ -258,7 +274,7 @@ public class AssetController extends BaseController {
258 274 try {
259 275 TenantId tenantId = getCurrentUser().getTenantId();
260 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 278 return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
263 279 } else {
264 280 return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
... ... @@ -321,7 +337,7 @@ public class AssetController extends BaseController {
321 337 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
322 338 checkCustomerId(customerId, Operation.READ);
323 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 341 return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
326 342 } else {
327 343 return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
... ... @@ -426,7 +442,7 @@ public class AssetController extends BaseController {
426 442 @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
427 443 @ResponseBody
428 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 446 checkParameter(EDGE_ID, strEdgeId);
431 447 checkParameter(ASSET_ID, strAssetId);
432 448 try {
... ... @@ -444,7 +460,7 @@ public class AssetController extends BaseController {
444 460
445 461 sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE);
446 462
447   - return savedAsset;
  463 + return savedAsset;
448 464 } catch (Exception e) {
449 465
450 466 logEntityAction(emptyId(EntityType.ASSET), null,
... ... @@ -530,4 +546,13 @@ public class AssetController extends BaseController {
530 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 }
... ...
... ... @@ -122,7 +122,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
122 122 import org.thingsboard.server.queue.discovery.PartitionService;
123 123 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
124 124 import org.thingsboard.server.queue.util.TbCoreComponent;
125   -import org.thingsboard.server.service.action.RuleEngineEntityActionService;
  125 +import org.thingsboard.server.service.action.EntityActionService;
126 126 import org.thingsboard.server.service.component.ComponentDiscoveryService;
127 127 import org.thingsboard.server.service.edge.EdgeNotificationService;
128 128 import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
... ... @@ -277,7 +277,7 @@ public abstract class BaseController {
277 277 protected EdgeRpcService edgeGrpcService;
278 278
279 279 @Autowired
280   - protected RuleEngineEntityActionService ruleEngineEntityActionService;
  280 + protected EntityActionService entityActionService;
281 281
282 282 @Value("${server.log_controller_error_stack_trace}")
283 283 @Getter
... ... @@ -817,13 +817,7 @@ public abstract class BaseController {
817 817
818 818 protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
819 819 ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
820   - if (customerId == null || customerId.isNullUid()) {
821   - customerId = user.getCustomerId();
822   - }
823   - if (e == null) {
824   - ruleEngineEntityActionService.pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo);
825   - }
826   - auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
  820 + entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo);
827 821 }
828 822
829 823
... ...
... ... @@ -19,10 +19,13 @@ import com.google.common.util.concurrent.FutureCallback;
19 19 import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import com.google.common.util.concurrent.MoreExecutors;
  22 +import lombok.RequiredArgsConstructor;
  23 +import lombok.extern.slf4j.Slf4j;
22 24 import org.springframework.http.HttpStatus;
23 25 import org.springframework.http.ResponseEntity;
24 26 import org.springframework.security.access.prepost.PreAuthorize;
25 27 import org.springframework.web.bind.annotation.PathVariable;
  28 +import org.springframework.web.bind.annotation.PostMapping;
26 29 import org.springframework.web.bind.annotation.RequestBody;
27 30 import org.springframework.web.bind.annotation.RequestMapping;
28 31 import org.springframework.web.bind.annotation.RequestMethod;
... ... @@ -68,6 +71,9 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult;
68 71 import org.thingsboard.server.dao.exception.IncorrectParameterException;
69 72 import org.thingsboard.server.dao.model.ModelConstants;
70 73 import org.thingsboard.server.queue.util.TbCoreComponent;
  74 +import org.thingsboard.server.service.device.DeviceBulkImportService;
  75 +import org.thingsboard.server.service.importing.BulkImportRequest;
  76 +import org.thingsboard.server.service.importing.BulkImportResult;
71 77 import org.thingsboard.server.service.security.model.SecurityUser;
72 78 import org.thingsboard.server.service.security.permission.Operation;
73 79 import org.thingsboard.server.service.security.permission.Resource;
... ... @@ -84,7 +90,10 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
84 90 @RestController
85 91 @TbCoreComponent
86 92 @RequestMapping("/api")
  93 +@RequiredArgsConstructor
  94 +@Slf4j
87 95 public class DeviceController extends BaseController {
  96 + private final DeviceBulkImportService deviceBulkImportService;
88 97
89 98 private static final String DEVICE_ID = "deviceId";
90 99 private static final String DEVICE_NAME = "deviceName";
... ... @@ -136,24 +145,7 @@ public class DeviceController extends BaseController {
136 145
137 146 Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
138 147
139   - tbClusterService.onDeviceChange(savedDevice, null);
140   - tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
141   - savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
142   - tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
143   -
144   - if (!created) {
145   - sendEntityNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), EdgeEventActionType.UPDATED);
146   - }
147   -
148   - logEntityAction(savedDevice.getId(), savedDevice,
149   - savedDevice.getCustomerId(),
150   - created ? ActionType.ADDED : ActionType.UPDATED, null);
151   -
152   - if (device.getId() == null) {
153   - deviceStateService.onDeviceAdded(savedDevice);
154   - } else {
155   - deviceStateService.onDeviceUpdated(savedDevice);
156   - }
  148 + onDeviceCreatedOrUpdated(savedDevice, !created);
157 149
158 150 otaPackageStateService.update(savedDevice, oldDevice);
159 151
... ... @@ -166,6 +158,31 @@ public class DeviceController extends BaseController {
166 158
167 159 }
168 160
  161 + private void onDeviceCreatedOrUpdated(Device device, boolean updated) {
  162 + tbClusterService.onDeviceChange(device, null);
  163 + tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(device.getTenantId(),
  164 + device.getId(), device.getName(), device.getType()), null);
  165 + tbClusterService.onEntityStateChange(device.getTenantId(), device.getId(), updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED);
  166 +
  167 + if (updated) {
  168 + sendEntityNotificationMsg(device.getTenantId(), device.getId(), EdgeEventActionType.UPDATED);
  169 + }
  170 +
  171 + try {
  172 + logEntityAction(device.getId(), device,
  173 + device.getCustomerId(),
  174 + updated ? ActionType.UPDATED : ActionType.ADDED, null);
  175 + } catch (ThingsboardException e) {
  176 + log.error("Failed to log entity action", e);
  177 + }
  178 +
  179 + if (updated) {
  180 + deviceStateService.onDeviceUpdated(device);
  181 + } else {
  182 + deviceStateService.onDeviceAdded(device);
  183 + }
  184 + }
  185 +
169 186 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
170 187 @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE)
171 188 @ResponseStatus(value = HttpStatus.OK)
... ... @@ -797,4 +814,14 @@ public class DeviceController extends BaseController {
797 814 throw handleException(e);
798 815 }
799 816 }
  817 +
  818 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  819 + @PostMapping("/device/bulk_import")
  820 + public BulkImportResult<Device> processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception {
  821 + return deviceBulkImportService.processBulkImport(request, getCurrentUser(), importedDeviceInfo -> {
  822 + onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.isUpdated());
  823 + otaPackageStateService.update(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity());
  824 + });
  825 + }
  826 +
800 827 }
... ...
... ... @@ -16,9 +16,11 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.RequiredArgsConstructor;
19 20 import org.springframework.http.HttpStatus;
20 21 import org.springframework.security.access.prepost.PreAuthorize;
21 22 import org.springframework.web.bind.annotation.PathVariable;
  23 +import org.springframework.web.bind.annotation.PostMapping;
22 24 import org.springframework.web.bind.annotation.RequestBody;
23 25 import org.springframework.web.bind.annotation.RequestMapping;
24 26 import org.springframework.web.bind.annotation.RequestMethod;
... ... @@ -49,10 +51,14 @@ import org.thingsboard.server.dao.exception.DataValidationException;
49 51 import org.thingsboard.server.dao.exception.IncorrectParameterException;
50 52 import org.thingsboard.server.dao.model.ModelConstants;
51 53 import org.thingsboard.server.queue.util.TbCoreComponent;
  54 +import org.thingsboard.server.service.edge.EdgeBulkImportService;
  55 +import org.thingsboard.server.service.importing.BulkImportRequest;
  56 +import org.thingsboard.server.service.importing.BulkImportResult;
52 57 import org.thingsboard.server.service.security.model.SecurityUser;
53 58 import org.thingsboard.server.service.security.permission.Operation;
54 59 import org.thingsboard.server.service.security.permission.Resource;
55 60
  61 +import java.io.IOException;
56 62 import java.util.ArrayList;
57 63 import java.util.List;
58 64 import java.util.stream.Collectors;
... ... @@ -60,7 +66,9 @@ import java.util.stream.Collectors;
60 66 @RestController
61 67 @TbCoreComponent
62 68 @RequestMapping("/api")
  69 +@RequiredArgsConstructor
63 70 public class EdgeController extends BaseController {
  71 + private final EdgeBulkImportService edgeBulkImportService;
64 72
65 73 public static final String EDGE_ID = "edgeId";
66 74
... ... @@ -128,17 +136,8 @@ public class EdgeController extends BaseController {
128 136 edge.getId(), edge);
129 137
130 138 Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true));
  139 + onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created);
131 140
132   - if (created) {
133   - ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), savedEdge.getId());
134   - edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId());
135   - edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId());
136   - }
137   -
138   - tbClusterService.onEntityStateChange(savedEdge.getTenantId(), savedEdge.getId(),
139   - created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
140   -
141   - logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
142 141 return savedEdge;
143 142 } catch (Exception e) {
144 143 logEntityAction(emptyId(EntityType.EDGE), edge,
... ... @@ -147,6 +146,19 @@ public class EdgeController extends BaseController {
147 146 }
148 147 }
149 148
  149 + private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated) throws IOException, ThingsboardException {
  150 + if (!updated) {
  151 + ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edge.getId());
  152 + edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, edgeTemplateRootRuleChain.getId());
  153 + edgeService.assignDefaultRuleChainsToEdge(tenantId, edge.getId());
  154 + }
  155 +
  156 + tbClusterService.onEntityStateChange(edge.getTenantId(), edge.getId(),
  157 + updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED);
  158 +
  159 + logEntityAction(edge.getId(), edge, null, updated ? ActionType.UPDATED : ActionType.ADDED, null);
  160 + }
  161 +
150 162 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
151 163 @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE)
152 164 @ResponseStatus(value = HttpStatus.OK)
... ... @@ -580,6 +592,24 @@ public class EdgeController extends BaseController {
580 592 }
581 593 }
582 594
  595 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  596 + @PostMapping("/edge/bulk_import")
  597 + public BulkImportResult<Edge> processEdgeBulkImport(@RequestBody BulkImportRequest request) throws Exception {
  598 + SecurityUser user = getCurrentUser();
  599 + RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(user.getTenantId());
  600 + if (edgeTemplateRootRuleChain == null) {
  601 + throw new DataValidationException("Root edge rule chain is not available!");
  602 + }
  603 +
  604 + return edgeBulkImportService.processBulkImport(request, user, importedAssetInfo -> {
  605 + try {
  606 + onEdgeCreatedOrUpdated(user.getTenantId(), importedAssetInfo.getEntity(), edgeTemplateRootRuleChain, importedAssetInfo.isUpdated());
  607 + } catch (Exception e) {
  608 + throw new RuntimeException(e);
  609 + }
  610 + });
  611 + }
  612 +
583 613 private void cleanUpLicenseKey(Edge edge) {
584 614 edge.setEdgeLicenseKey(null);
585 615 }
... ...
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 28 import org.thingsboard.server.common.data.HasTenantId;
29 29 import org.thingsboard.server.common.data.User;
30 30 import org.thingsboard.server.common.data.audit.ActionType;
  31 +import org.thingsboard.server.common.data.exception.ThingsboardException;
31 32 import org.thingsboard.server.common.data.id.CustomerId;
32 33 import org.thingsboard.server.common.data.id.EntityId;
33 34 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
38 39 import org.thingsboard.server.common.msg.TbMsg;
39 40 import org.thingsboard.server.common.msg.TbMsgDataType;
40 41 import org.thingsboard.server.common.msg.TbMsgMetaData;
  42 +import org.thingsboard.server.dao.audit.AuditLogService;
41 43 import org.thingsboard.server.queue.util.TbCoreComponent;
42 44 import org.thingsboard.server.service.queue.TbClusterService;
43 45
... ... @@ -49,8 +51,9 @@ import java.util.stream.Collectors;
49 51 @Service
50 52 @RequiredArgsConstructor
51 53 @Slf4j
52   -public class RuleEngineEntityActionService {
  54 +public class EntityActionService {
53 55 private final TbClusterService tbClusterService;
  56 + private final AuditLogService auditLogService;
54 57
55 58 private static final ObjectMapper json = new ObjectMapper();
56 59
... ... @@ -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 227 private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) {
214 228 T result = null;
... ...
  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.common.data.asset.Asset;
  23 +import org.thingsboard.server.dao.asset.AssetService;
  24 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  25 +import org.thingsboard.server.queue.util.TbCoreComponent;
  26 +import org.thingsboard.server.service.action.EntityActionService;
  27 +import org.thingsboard.server.service.importing.AbstractBulkImportService;
  28 +import org.thingsboard.server.service.importing.BulkImportRequest;
  29 +import org.thingsboard.server.service.importing.ImportedEntityInfo;
  30 +import org.thingsboard.server.service.queue.TbClusterService;
  31 +import org.thingsboard.server.service.security.AccessValidator;
  32 +import org.thingsboard.server.service.security.model.SecurityUser;
  33 +import org.thingsboard.server.service.security.permission.AccessControlService;
  34 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  35 +
  36 +import java.util.Map;
  37 +import java.util.Optional;
  38 +
  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<BulkImportRequest.ColumnMapping, String> entityData, SecurityUser user) {
  54 + ImportedEntityInfo<Asset> importedEntityInfo = new ImportedEntityInfo<>();
  55 +
  56 + Asset asset = new Asset();
  57 + asset.setTenantId(user.getTenantId());
  58 + setAssetFields(asset, entityData);
  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<BulkImportRequest.ColumnMapping, String> data) {
  74 + ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(asset.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
  75 + data.forEach((columnMapping, value) -> {
  76 + switch (columnMapping.getType()) {
  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 +}
... ...
  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 com.google.gson.JsonObject;
  22 +import com.google.gson.JsonPrimitive;
  23 +import lombok.SneakyThrows;
  24 +import org.apache.commons.collections.CollectionUtils;
  25 +import org.springframework.stereotype.Service;
  26 +import org.thingsboard.common.util.JacksonUtil;
  27 +import org.thingsboard.server.common.data.Device;
  28 +import org.thingsboard.server.common.data.DeviceProfile;
  29 +import org.thingsboard.server.common.data.DeviceProfileProvisionType;
  30 +import org.thingsboard.server.common.data.DeviceProfileType;
  31 +import org.thingsboard.server.common.data.DeviceTransportType;
  32 +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
  33 +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials;
  34 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  35 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  36 +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
  37 +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
  38 +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
  39 +import org.thingsboard.server.common.data.id.TenantId;
  40 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  41 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  42 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  43 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  44 +import org.thingsboard.server.dao.device.DeviceProfileService;
  45 +import org.thingsboard.server.dao.device.DeviceService;
  46 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  47 +import org.thingsboard.server.queue.util.TbCoreComponent;
  48 +import org.thingsboard.server.service.action.EntityActionService;
  49 +import org.thingsboard.server.service.importing.AbstractBulkImportService;
  50 +import org.thingsboard.server.service.importing.BulkImportColumnType;
  51 +import org.thingsboard.server.service.importing.BulkImportRequest;
  52 +import org.thingsboard.server.service.importing.ImportedEntityInfo;
  53 +import org.thingsboard.server.service.queue.TbClusterService;
  54 +import org.thingsboard.server.service.security.AccessValidator;
  55 +import org.thingsboard.server.service.security.model.SecurityUser;
  56 +import org.thingsboard.server.service.security.permission.AccessControlService;
  57 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  58 +
  59 +import java.util.EnumSet;
  60 +import java.util.Map;
  61 +import java.util.Optional;
  62 +import java.util.Set;
  63 +import java.util.stream.Collectors;
  64 +import java.util.stream.Stream;
  65 +
  66 +import static org.thingsboard.server.dao.service.Validator.validateId;
  67 +
  68 +@Service
  69 +@TbCoreComponent
  70 +public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
  71 + protected final DeviceService deviceService;
  72 + protected final DeviceCredentialsService deviceCredentialsService;
  73 + protected final DeviceProfileService deviceProfileService;
  74 +
  75 + public DeviceBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache,
  76 + AccessControlService accessControlService, AccessValidator accessValidator,
  77 + EntityActionService entityActionService, TbClusterService clusterService,
  78 + DeviceService deviceService, DeviceCredentialsService deviceCredentialsService,
  79 + DeviceProfileService deviceProfileService) {
  80 + super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService);
  81 + this.deviceService = deviceService;
  82 + this.deviceCredentialsService = deviceCredentialsService;
  83 + this.deviceProfileService = deviceProfileService;
  84 + }
  85 +
  86 + @Override
  87 + protected ImportedEntityInfo<Device> saveEntity(BulkImportRequest importRequest, Map<BulkImportRequest.ColumnMapping, String> entityData, SecurityUser user) {
  88 + ImportedEntityInfo<Device> importedEntityInfo = new ImportedEntityInfo<>();
  89 +
  90 + Device device = new Device();
  91 + device.setTenantId(user.getTenantId());
  92 + setDeviceFields(device, entityData);
  93 +
  94 + Device existingDevice = deviceService.findDeviceByTenantIdAndName(user.getTenantId(), device.getName());
  95 + if (existingDevice != null && importRequest.getMapping().getUpdate()) {
  96 + importedEntityInfo.setOldEntity(new Device(existingDevice));
  97 + importedEntityInfo.setUpdated(true);
  98 + existingDevice.updateDevice(device);
  99 + device = existingDevice;
  100 + }
  101 +
  102 + DeviceCredentials deviceCredentials = createDeviceCredentials(entityData);
  103 + if (deviceCredentials.getCredentialsType() != null) {
  104 + if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
  105 + setUpLwM2mDeviceProfile(user.getTenantId(), device);
  106 + }
  107 + device = deviceService.saveDeviceWithCredentials(device, deviceCredentials);
  108 + } else {
  109 + device = deviceService.saveDevice(device);
  110 + }
  111 +
  112 + importedEntityInfo.setEntity(device);
  113 +
  114 + return importedEntityInfo;
  115 + }
  116 +
  117 + private void setDeviceFields(Device device, Map<BulkImportRequest.ColumnMapping, String> data) {
  118 + ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(device.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
  119 + data.forEach((columnMapping, value) -> {
  120 + switch (columnMapping.getType()) {
  121 + case NAME:
  122 + device.setName(value);
  123 + break;
  124 + case TYPE:
  125 + device.setType(value);
  126 + break;
  127 + case LABEL:
  128 + device.setLabel(value);
  129 + break;
  130 + case DESCRIPTION:
  131 + additionalInfo.set("description", new TextNode(value));
  132 + break;
  133 + case IS_GATEWAY:
  134 + additionalInfo.set("gateway", BooleanNode.valueOf(Boolean.parseBoolean(value)));
  135 + break;
  136 + }
  137 + device.setAdditionalInfo(additionalInfo);
  138 + });
  139 + }
  140 +
  141 + @SneakyThrows
  142 + private DeviceCredentials createDeviceCredentials(Map<BulkImportRequest.ColumnMapping, String> data) {
  143 + Set<BulkImportColumnType> columns = data.keySet().stream().map(BulkImportRequest.ColumnMapping::getType).collect(Collectors.toSet());
  144 +
  145 + DeviceCredentials credentials = new DeviceCredentials();
  146 +
  147 + if (columns.contains(BulkImportColumnType.ACCESS_TOKEN)) {
  148 + credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
  149 + credentials.setCredentialsId(getByColumnType(BulkImportColumnType.ACCESS_TOKEN, data));
  150 + } else if (CollectionUtils.containsAny(columns, EnumSet.of(BulkImportColumnType.MQTT_CLIENT_ID, BulkImportColumnType.MQTT_USER_NAME, BulkImportColumnType.MQTT_PASSWORD))) {
  151 + credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC);
  152 +
  153 + BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials();
  154 + basicMqttCredentials.setClientId(getByColumnType(BulkImportColumnType.MQTT_CLIENT_ID, data));
  155 + basicMqttCredentials.setUserName(getByColumnType(BulkImportColumnType.MQTT_USER_NAME, data));
  156 + basicMqttCredentials.setPassword(getByColumnType(BulkImportColumnType.MQTT_PASSWORD, data));
  157 + credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials));
  158 + } else if (columns.contains(BulkImportColumnType.X509)) {
  159 + credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE);
  160 + credentials.setCredentialsValue(getByColumnType(BulkImportColumnType.X509, data));
  161 + } else if (columns.contains(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT)) {
  162 + credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
  163 + ObjectNode lwm2mCredentials = JacksonUtil.newObjectNode();
  164 +
  165 + ObjectNode client = JacksonUtil.newObjectNode();
  166 + Stream.of(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE,
  167 + BulkImportColumnType.LWM2M_CLIENT_IDENTITY, BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT)
  168 + .forEach(lwm2mClientProperty -> {
  169 + String value = getByColumnType(lwm2mClientProperty, data);
  170 + if (value != null) {
  171 + client.set(lwm2mClientProperty.getKey(), new TextNode(value));
  172 + }
  173 + });
  174 +
  175 + LwM2MClientCredentials lwM2MClientCredentials = JacksonUtil.treeToValue(client, LwM2MClientCredentials.class);
  176 + // so that only fields needed for specific type of lwM2MClientCredentials were saved in json
  177 + lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredentials));
  178 +
  179 + ObjectNode bootstrapServer = JacksonUtil.newObjectNode();
  180 + Stream.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID,
  181 + BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECRET_KEY)
  182 + .forEach(lwm2mBootstrapServerProperty -> {
  183 + String value = getByColumnType(lwm2mBootstrapServerProperty, data);
  184 + if (value != null) {
  185 + bootstrapServer.set(lwm2mBootstrapServerProperty.getKey(), new TextNode(value));
  186 + }
  187 + });
  188 +
  189 + ObjectNode lwm2mServer = JacksonUtil.newObjectNode();
  190 + Stream.of(BulkImportColumnType.LWM2M_SERVER_SECURITY_MODE, BulkImportColumnType.LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID,
  191 + BulkImportColumnType.LWM2M_SERVER_CLIENT_SECRET_KEY)
  192 + .forEach(lwm2mServerProperty -> {
  193 + String value = getByColumnType(lwm2mServerProperty, data);
  194 + if (value != null) {
  195 + lwm2mServer.set(lwm2mServerProperty.getKey(), new TextNode(value));
  196 + }
  197 + });
  198 +
  199 + ObjectNode bootstrap = JacksonUtil.newObjectNode();
  200 + bootstrap.set("bootstrapServer", bootstrapServer);
  201 + bootstrap.set("lwm2mServer", lwm2mServer);
  202 + lwm2mCredentials.set("bootstrap", bootstrap);
  203 +
  204 + credentials.setCredentialsValue(lwm2mCredentials.toString());
  205 + }
  206 +
  207 + return credentials;
  208 + }
  209 +
  210 + private void setUpLwM2mDeviceProfile(TenantId tenantId, Device device) {
  211 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileByName(tenantId, device.getType());
  212 + if (deviceProfile != null) {
  213 + if (deviceProfile.getTransportType() != DeviceTransportType.LWM2M) {
  214 + deviceProfile.setTransportType(DeviceTransportType.LWM2M);
  215 + deviceProfile.getProfileData().setTransportConfiguration(new Lwm2mDeviceProfileTransportConfiguration());
  216 + deviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
  217 + device.setDeviceProfileId(deviceProfile.getId());
  218 + }
  219 + } else {
  220 + deviceProfile = new DeviceProfile();
  221 + deviceProfile.setTenantId(tenantId);
  222 + deviceProfile.setType(DeviceProfileType.DEFAULT);
  223 + deviceProfile.setName(device.getType());
  224 + deviceProfile.setTransportType(DeviceTransportType.LWM2M);
  225 + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
  226 +
  227 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  228 + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
  229 + DeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
  230 + DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
  231 +
  232 + deviceProfileData.setConfiguration(configuration);
  233 + deviceProfileData.setTransportConfiguration(transportConfiguration);
  234 + deviceProfileData.setProvisionConfiguration(provisionConfiguration);
  235 + deviceProfile.setProfileData(deviceProfileData);
  236 +
  237 + deviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
  238 + device.setDeviceProfileId(deviceProfile.getId());
  239 + }
  240 + }
  241 +
  242 +}
... ...
  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.common.data.edge.Edge;
  23 +import org.thingsboard.server.dao.edge.EdgeService;
  24 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  25 +import org.thingsboard.server.queue.util.TbCoreComponent;
  26 +import org.thingsboard.server.service.action.EntityActionService;
  27 +import org.thingsboard.server.service.importing.AbstractBulkImportService;
  28 +import org.thingsboard.server.service.importing.BulkImportRequest;
  29 +import org.thingsboard.server.service.importing.ImportedEntityInfo;
  30 +import org.thingsboard.server.service.queue.TbClusterService;
  31 +import org.thingsboard.server.service.security.AccessValidator;
  32 +import org.thingsboard.server.service.security.model.SecurityUser;
  33 +import org.thingsboard.server.service.security.permission.AccessControlService;
  34 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  35 +
  36 +import java.util.Map;
  37 +import java.util.Optional;
  38 +
  39 +@Service
  40 +@TbCoreComponent
  41 +public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
  42 + private final EdgeService edgeService;
  43 +
  44 + public EdgeBulkImportService(TelemetrySubscriptionService tsSubscriptionService, TbTenantProfileCache tenantProfileCache,
  45 + AccessControlService accessControlService, AccessValidator accessValidator,
  46 + EntityActionService entityActionService, TbClusterService clusterService, EdgeService edgeService) {
  47 + super(tsSubscriptionService, tenantProfileCache, accessControlService, accessValidator, entityActionService, clusterService);
  48 + this.edgeService = edgeService;
  49 + }
  50 +
  51 + @Override
  52 + protected ImportedEntityInfo<Edge> saveEntity(BulkImportRequest importRequest, Map<BulkImportRequest.ColumnMapping, String> entityData, SecurityUser user) {
  53 + ImportedEntityInfo<Edge> importedEntityInfo = new ImportedEntityInfo<>();
  54 +
  55 + Edge edge = new Edge();
  56 + edge.setTenantId(user.getTenantId());
  57 + setEdgeFields(edge, entityData);
  58 +
  59 + Edge existingEdge = edgeService.findEdgeByTenantIdAndName(user.getTenantId(), edge.getName());
  60 + if (existingEdge != null && importRequest.getMapping().getUpdate()) {
  61 + importedEntityInfo.setOldEntity(new Edge(existingEdge));
  62 + importedEntityInfo.setUpdated(true);
  63 + existingEdge.update(edge);
  64 + edge = existingEdge;
  65 + }
  66 + edge = edgeService.saveEdge(edge, true);
  67 +
  68 + importedEntityInfo.setEntity(edge);
  69 + return importedEntityInfo;
  70 + }
  71 +
  72 + private void setEdgeFields(Edge edge, Map<BulkImportRequest.ColumnMapping, String> data) {
  73 + ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(edge.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode);
  74 + data.forEach((columnMapping, value) -> {
  75 + switch (columnMapping.getType()) {
  76 + case NAME:
  77 + edge.setName(value);
  78 + break;
  79 + case TYPE:
  80 + edge.setType(value);
  81 + break;
  82 + case LABEL:
  83 + edge.setLabel(value);
  84 + break;
  85 + case DESCRIPTION:
  86 + additionalInfo.set("description", new TextNode(value));
  87 + break;
  88 + case EDGE_LICENSE_KEY:
  89 + edge.setEdgeLicenseKey(value);
  90 + break;
  91 + case CLOUD_ENDPOINT:
  92 + edge.setCloudEndpoint(value);
  93 + break;
  94 + case ROUTING_KEY:
  95 + edge.setRoutingKey(value);
  96 + break;
  97 + case SECRET:
  98 + edge.setSecret(value);
  99 + break;
  100 + }
  101 + });
  102 + edge.setAdditionalInfo(additionalInfo);
  103 + }
  104 +
  105 +}
... ...
  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.RequiredArgsConstructor;
  22 +import lombok.SneakyThrows;
  23 +import org.apache.commons.lang3.StringUtils;
  24 +import org.thingsboard.server.common.data.BaseData;
  25 +import org.thingsboard.server.common.data.TenantProfile;
  26 +import org.thingsboard.server.common.data.audit.ActionType;
  27 +import org.thingsboard.server.common.data.id.EntityId;
  28 +import org.thingsboard.server.common.data.id.UUIDBased;
  29 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  30 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  31 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  32 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
  33 +import org.thingsboard.server.common.transport.adaptor.JsonConverter;
  34 +import org.thingsboard.server.controller.BaseController;
  35 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
  36 +import org.thingsboard.server.service.action.EntityActionService;
  37 +import org.thingsboard.server.service.importing.BulkImportRequest.ColumnMapping;
  38 +import org.thingsboard.server.service.queue.TbClusterService;
  39 +import org.thingsboard.server.service.security.AccessValidator;
  40 +import org.thingsboard.server.service.security.model.SecurityUser;
  41 +import org.thingsboard.server.service.security.permission.AccessControlService;
  42 +import org.thingsboard.server.service.security.permission.Operation;
  43 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  44 +import org.thingsboard.server.utils.CsvUtils;
  45 +
  46 +import javax.annotation.Nullable;
  47 +import java.util.ArrayList;
  48 +import java.util.List;
  49 +import java.util.Map;
  50 +import java.util.concurrent.TimeUnit;
  51 +import java.util.concurrent.atomic.AtomicInteger;
  52 +import java.util.function.Consumer;
  53 +import java.util.stream.Collectors;
  54 +import java.util.stream.Stream;
  55 +
  56 +@RequiredArgsConstructor
  57 +public abstract class AbstractBulkImportService<E extends BaseData<? extends EntityId>> {
  58 + protected final TelemetrySubscriptionService tsSubscriptionService;
  59 + protected final TbTenantProfileCache tenantProfileCache;
  60 + protected final AccessControlService accessControlService;
  61 + protected final AccessValidator accessValidator;
  62 + protected final EntityActionService entityActionService;
  63 + protected final TbClusterService clusterService;
  64 +
  65 + public final BulkImportResult<E> processBulkImport(BulkImportRequest request, SecurityUser user, Consumer<ImportedEntityInfo<E>> onEntityImported) throws Exception {
  66 + BulkImportResult<E> result = new BulkImportResult<>();
  67 +
  68 + AtomicInteger i = new AtomicInteger(0);
  69 + if (request.getMapping().getHeader()) {
  70 + i.incrementAndGet();
  71 + }
  72 +
  73 + parseData(request).forEach(entityData -> {
  74 + i.incrementAndGet();
  75 + try {
  76 + ImportedEntityInfo<E> importedEntityInfo = saveEntity(request, entityData, user);
  77 + onEntityImported.accept(importedEntityInfo);
  78 +
  79 + E entity = importedEntityInfo.getEntity();
  80 +
  81 + saveKvs(user, entity, entityData);
  82 +
  83 + if (importedEntityInfo.isUpdated()) {
  84 + result.setUpdated(result.getUpdated() + 1);
  85 + } else {
  86 + result.setCreated(result.getCreated() + 1);
  87 + }
  88 + } catch (Exception e) {
  89 + result.setErrors(result.getErrors() + 1);
  90 + result.getErrorsList().add(String.format("Line %d: %s", i.get(), e.getMessage()));
  91 + }
  92 + });
  93 +
  94 + return result;
  95 + }
  96 +
  97 + protected abstract ImportedEntityInfo<E> saveEntity(BulkImportRequest importRequest, Map<ColumnMapping, String> entityData, SecurityUser user);
  98 +
  99 + /*
  100 + * Attributes' values are firstly added to JsonObject in order to then make some type cast,
  101 + * because we get all values as strings from CSV
  102 + * */
  103 + private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, String> data) {
  104 + Stream.of(BulkImportColumnType.SHARED_ATTRIBUTE, BulkImportColumnType.SERVER_ATTRIBUTE, BulkImportColumnType.TIMESERIES)
  105 + .map(kvType -> {
  106 + JsonObject kvs = new JsonObject();
  107 + data.entrySet().stream()
  108 + .filter(dataEntry -> dataEntry.getKey().getType() == kvType &&
  109 + StringUtils.isNotEmpty(dataEntry.getKey().getKey()))
  110 + .forEach(dataEntry -> kvs.add(dataEntry.getKey().getKey(), new JsonPrimitive(dataEntry.getValue())));
  111 + return Map.entry(kvType, kvs);
  112 + })
  113 + .filter(kvsEntry -> kvsEntry.getValue().entrySet().size() > 0)
  114 + .forEach(kvsEntry -> {
  115 + BulkImportColumnType kvType = kvsEntry.getKey();
  116 + if (kvType == BulkImportColumnType.SHARED_ATTRIBUTE || kvType == BulkImportColumnType.SERVER_ATTRIBUTE) {
  117 + saveAttributes(user, entity, kvsEntry, kvType);
  118 + } else {
  119 + saveTelemetry(user, entity, kvsEntry);
  120 + }
  121 + });
  122 + }
  123 +
  124 + @SneakyThrows
  125 + private void saveTelemetry(SecurityUser user, E entity, Map.Entry<BulkImportColumnType, JsonObject> kvsEntry) {
  126 + List<TsKvEntry> timeseries = JsonConverter.convertToTelemetry(kvsEntry.getValue(), System.currentTimeMillis()).entrySet().stream()
  127 + .flatMap(entry -> entry.getValue().stream().map(kvEntry -> new BasicTsKvEntry(entry.getKey(), kvEntry)))
  128 + .collect(Collectors.toList());
  129 +
  130 + accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entity.getId(), (result, tenantId, entityId) -> {
  131 + TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
  132 + long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
  133 + tsSubscriptionService.saveAndNotify(tenantId, user.getCustomerId(), entityId, timeseries, tenantTtl, new FutureCallback<Void>() {
  134 + @Override
  135 + public void onSuccess(@Nullable Void tmp) {
  136 + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
  137 + ActionType.TIMESERIES_UPDATED, null, timeseries);
  138 + }
  139 +
  140 + @Override
  141 + public void onFailure(Throwable t) {
  142 + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
  143 + ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries);
  144 + throw new RuntimeException(t);
  145 + }
  146 + });
  147 + });
  148 + }
  149 +
  150 + @SneakyThrows
  151 + private void saveAttributes(SecurityUser user, E entity, Map.Entry<BulkImportColumnType, JsonObject> kvsEntry, BulkImportColumnType kvType) {
  152 + String scope = kvType.getKey();
  153 + List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue()));
  154 +
  155 + accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> {
  156 + tsSubscriptionService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<>() {
  157 +
  158 + @Override
  159 + public void onSuccess(Void unused) {
  160 + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
  161 + null, ActionType.ATTRIBUTES_UPDATED, null, scope, attributes);
  162 + }
  163 +
  164 + @Override
  165 + public void onFailure(Throwable throwable) {
  166 + entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
  167 + null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
  168 + scope, attributes);
  169 + throw new RuntimeException(throwable);
  170 + }
  171 +
  172 + });
  173 + });
  174 + }
  175 +
  176 + protected final String getByColumnType(BulkImportColumnType bulkImportColumnType, Map<ColumnMapping, String> data) {
  177 + return data.entrySet().stream().filter(entry -> entry.getKey().getType() == bulkImportColumnType).findFirst().map(Map.Entry::getValue).orElse(null);
  178 + }
  179 +
  180 + private List<Map<ColumnMapping, String>> parseData(BulkImportRequest request) throws Exception {
  181 + List<List<String>> records = CsvUtils.parseCsv(request.getFile(), request.getMapping().getDelimiter());
  182 + if (request.getMapping().getHeader()) {
  183 + records.remove(0);
  184 + }
  185 +
  186 + List<ColumnMapping> columnsMappings = request.getMapping().getColumns();
  187 +
  188 + return records.stream()
  189 + .map(record -> Stream.iterate(0, i -> i < record.size(), i -> i + 1)
  190 + .map(i -> Map.entry(columnsMappings.get(i), record.get(i)))
  191 + .filter(entry -> StringUtils.isNotEmpty(entry.getValue()))
  192 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
  193 + .collect(Collectors.toList());
  194 + }
  195 +
  196 +}
... ...
  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 +
  21 +public enum BulkImportColumnType {
  22 + NAME,
  23 + TYPE,
  24 + LABEL,
  25 + SHARED_ATTRIBUTE(DataConstants.SHARED_SCOPE),
  26 + SERVER_ATTRIBUTE(DataConstants.SERVER_SCOPE),
  27 + TIMESERIES,
  28 + ACCESS_TOKEN,
  29 + X509,
  30 + MQTT_CLIENT_ID,
  31 + MQTT_USER_NAME,
  32 + MQTT_PASSWORD,
  33 + LWM2M_CLIENT_ENDPOINT("endpoint"),
  34 + LWM2M_CLIENT_SECURITY_CONFIG_MODE("securityConfigClientMode"),
  35 + LWM2M_CLIENT_IDENTITY("identity"),
  36 + LWM2M_CLIENT_KEY("key"),
  37 + LWM2M_CLIENT_CERT("cert"),
  38 + LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE("securityMode"),
  39 + LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"),
  40 + LWM2M_BOOTSTRAP_SERVER_SECRET_KEY("clientSecretKey"),
  41 + LWM2M_SERVER_SECURITY_MODE("securityMode"),
  42 + LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID("clientPublicKeyOrId"),
  43 + LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"),
  44 + IS_GATEWAY,
  45 + DESCRIPTION,
  46 + EDGE_LICENSE_KEY,
  47 + CLOUD_ENDPOINT,
  48 + ROUTING_KEY,
  49 + SECRET;
  50 +
  51 + @Getter
  52 + private String key;
  53 +
  54 + BulkImportColumnType() {
  55 + }
  56 +
  57 + BulkImportColumnType(String key) {
  58 + this.key = key;
  59 + }
  60 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.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 +}
... ...
  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 +}
... ...
... ... @@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.id.AlarmId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
27 27 import org.thingsboard.server.common.data.page.PageData;
28 28 import org.thingsboard.server.common.data.page.PageLink;
29   -import org.thingsboard.server.common.data.page.SortOrder;
30 29 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
31 30 import org.thingsboard.server.common.msg.queue.ServiceType;
32 31 import org.thingsboard.server.dao.alarm.AlarmDao;
... ... @@ -34,17 +33,12 @@ import org.thingsboard.server.dao.alarm.AlarmService;
34 33 import org.thingsboard.server.dao.relation.RelationService;
35 34 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
36 35 import org.thingsboard.server.dao.tenant.TenantDao;
37   -import org.thingsboard.server.dao.util.PsqlDao;
38 36 import org.thingsboard.server.queue.discovery.PartitionService;
39 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 40 import java.util.Date;
46 41 import java.util.Optional;
47   -import java.util.UUID;
48 42 import java.util.concurrent.TimeUnit;
49 43
50 44 @TbCoreComponent
... ... @@ -60,7 +54,7 @@ public class AlarmsCleanUpService {
60 54 private final AlarmDao alarmDao;
61 55 private final AlarmService alarmService;
62 56 private final RelationService relationService;
63   - private final RuleEngineEntityActionService ruleEngineEntityActionService;
  57 + private final EntityActionService entityActionService;
64 58 private final PartitionService partitionService;
65 59 private final TbTenantProfileCache tenantProfileCache;
66 60
... ... @@ -90,7 +84,7 @@ public class AlarmsCleanUpService {
90 84 toRemove.getData().forEach(alarmId -> {
91 85 relationService.deleteEntityRelations(tenantId, alarmId);
92 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 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 +}
... ...
... ... @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
29 29
30 30 import java.io.ByteArrayInputStream;
31 31 import java.io.IOException;
  32 +import java.util.Optional;
32 33
33 34 @EqualsAndHashCode(callSuper = true)
34 35 @Slf4j
... ... @@ -83,6 +84,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
83 84 this.setDeviceData(device.getDeviceData());
84 85 this.setFirmwareId(device.getFirmwareId());
85 86 this.setSoftwareId(device.getSoftwareId());
  87 + Optional.ofNullable(device.getAdditionalInfo()).ifPresent(this::setAdditionalInfo);
86 88 return this;
87 89 }
88 90
... ...
... ... @@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
25 25 import org.thingsboard.server.common.data.id.TenantId;
26 26 import org.thingsboard.server.common.data.validation.NoXss;
27 27
  28 +import java.util.Optional;
  29 +
28 30 @EqualsAndHashCode(callSuper = true)
29 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 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 70 public TenantId getTenantId() {
60 71 return tenantId;
61 72 }
... ...
... ... @@ -26,7 +26,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
26 26 @JsonSubTypes.Type(value = NoSecClientCredentials.class, name = "NO_SEC"),
27 27 @JsonSubTypes.Type(value = PSKClientCredentials.class, name = "PSK"),
28 28 @JsonSubTypes.Type(value = RPKClientCredentials.class, name = "RPK"),
29   - @JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509")})
  29 + @JsonSubTypes.Type(value = X509ClientCredentials.class, name = "X509")
  30 +})
30 31 public interface LwM2MClientCredentials {
31 32
32 33 @JsonIgnore
... ...
... ... @@ -15,7 +15,6 @@
15 15 */
16 16 package org.thingsboard.server.common.data.edge;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19 18 import lombok.EqualsAndHashCode;
20 19 import lombok.Getter;
21 20 import lombok.Setter;
... ... @@ -70,6 +69,19 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
70 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 85 @Override
74 86 public String getSearchText() {
75 87 return getName();
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.transport.adaptor;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
18 19 import com.google.gson.Gson;
19 20 import com.google.gson.JsonArray;
20 21 import com.google.gson.JsonElement;
... ... @@ -577,6 +578,14 @@ public class JsonConverter {
577 578 return GSON.toJson(element);
578 579 }
579 580
  581 + public static JsonObject toJsonObject(Object o) {
  582 + return (JsonObject) GSON.toJsonTree(o);
  583 + }
  584 +
  585 + public static <T> T fromJson(JsonElement element, Class<T> type) {
  586 + return GSON.fromJson(element, type);
  587 + }
  588 +
580 589 public static void setTypeCastEnabled(boolean enabled) {
581 590 isTypeCastEnabled = enabled;
582 591 }
... ...
... ... @@ -118,4 +118,9 @@ public class JacksonUtil {
118 118 public static <T> JsonNode valueToTree(T value) {
119 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 }
... ...
... ... @@ -220,6 +220,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
220 220 if (foundDeviceCredentials == null) {
221 221 deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
222 222 } else {
  223 + deviceCredentials.setId(foundDeviceCredentials.getId());
223 224 deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials);
224 225 }
225 226 }
... ...