Commit 6eac10d60cdd293d68675ce767d67498e1fb2d8e

Authored by zbeacon
2 parents dd0ab346 17053118

Merge branch 'develop/3.2' of https://github.com/thingsboard/thingsboard into fe…

…ature/device-provision-3.2-onlyProfileVersion
... ... @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.beans.factory.annotation.Value;
25 25 import org.springframework.http.HttpStatus;
26 26 import org.springframework.security.access.prepost.PreAuthorize;
  27 +import org.springframework.util.CollectionUtils;
27 28 import org.springframework.util.StringUtils;
28 29 import org.springframework.web.bind.annotation.PathVariable;
29 30 import org.springframework.web.bind.annotation.RequestBody;
... ... @@ -49,6 +50,8 @@ import org.thingsboard.server.common.data.page.PageLink;
49 50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
50 51 import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
51 52 import org.thingsboard.server.common.data.rule.RuleChain;
  53 +import org.thingsboard.server.common.data.rule.RuleChainData;
  54 +import org.thingsboard.server.common.data.rule.RuleChainImportResult;
52 55 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
53 56 import org.thingsboard.server.common.data.rule.RuleNode;
54 57 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -386,6 +389,36 @@ public class RuleChainController extends BaseController {
386 389 }
387 390 }
388 391
  392 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  393 + @RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET)
  394 + @ResponseBody
  395 + public RuleChainData exportRuleChains(@RequestParam("limit") int limit) throws ThingsboardException {
  396 + try {
  397 + TenantId tenantId = getCurrentUser().getTenantId();
  398 + PageLink pageLink = new PageLink(limit);
  399 + return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink));
  400 + } catch (Exception e) {
  401 + throw handleException(e);
  402 + }
  403 + }
  404 +
  405 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  406 + @RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST)
  407 + @ResponseBody
  408 + public void importRuleChains(@RequestBody RuleChainData ruleChainData, @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException {
  409 + try {
  410 + TenantId tenantId = getCurrentUser().getTenantId();
  411 + List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite);
  412 + if (!CollectionUtils.isEmpty(importResults)) {
  413 + for (RuleChainImportResult importResult : importResults) {
  414 + tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
  415 + }
  416 + }
  417 + } catch (Exception e) {
  418 + throw handleException(e);
  419 + }
  420 + }
  421 +
389 422 private String msgToOutput(TbMsg msg) throws Exception {
390 423 ObjectNode msgData = objectMapper.createObjectNode();
391 424 if (!StringUtils.isEmpty(msg.getData())) {
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.rule;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.exception.ThingsboardException;
19 20 import org.thingsboard.server.common.data.id.RuleChainId;
20 21 import org.thingsboard.server.common.data.id.RuleNodeId;
21 22 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -23,6 +24,8 @@ import org.thingsboard.server.common.data.page.PageData;
23 24 import org.thingsboard.server.common.data.page.PageLink;
24 25 import org.thingsboard.server.common.data.relation.EntityRelation;
25 26 import org.thingsboard.server.common.data.rule.RuleChain;
  27 +import org.thingsboard.server.common.data.rule.RuleChainData;
  28 +import org.thingsboard.server.common.data.rule.RuleChainImportResult;
26 29 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
27 30 import org.thingsboard.server.common.data.rule.RuleNode;
28 31
... ... @@ -63,4 +66,8 @@ public interface RuleChainService {
63 66
64 67 void deleteRuleChainsByTenantId(TenantId tenantId);
65 68
  69 + RuleChainData exportTenantRuleChains(TenantId tenantId, PageLink pageLink) throws ThingsboardException;
  70 +
  71 + List<RuleChainImportResult> importTenantRuleChains(TenantId tenantId, RuleChainData ruleChainData, boolean overwrite);
  72 +
66 73 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 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.rule;
  17 +
  18 +import lombok.Data;
  19 +
  20 +import java.util.List;
  21 +
  22 +@Data
  23 +public class RuleChainData {
  24 +
  25 + List<RuleChain> ruleChains;
  26 + List<RuleChainMetaData> metadata;
  27 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 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.rule;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.id.RuleChainId;
  21 +import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  23 +
  24 +@Data
  25 +@AllArgsConstructor
  26 +public class RuleChainImportResult {
  27 +
  28 + private TenantId tenantId;
  29 + private RuleChainId ruleChainId;
  30 + private ComponentLifecycleEvent lifecycleEvent;
  31 +}
... ...
... ... @@ -15,12 +15,16 @@
15 15 */
16 16 package org.thingsboard.server.dao.rule;
17 17
  18 +import com.datastax.oss.driver.api.core.uuid.Uuids;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 21 import com.google.common.util.concurrent.ListenableFuture;
19 22 import lombok.extern.slf4j.Slf4j;
20 23 import org.apache.commons.lang3.StringUtils;
21 24 import org.hibernate.exception.ConstraintViolationException;
22 25 import org.springframework.beans.factory.annotation.Autowired;
23 26 import org.springframework.stereotype.Service;
  27 +import org.springframework.util.CollectionUtils;
24 28 import org.thingsboard.server.common.data.BaseData;
25 29 import org.thingsboard.server.common.data.EntityType;
26 30 import org.thingsboard.server.common.data.Tenant;
... ... @@ -30,11 +34,14 @@ import org.thingsboard.server.common.data.id.RuleNodeId;
30 34 import org.thingsboard.server.common.data.id.TenantId;
31 35 import org.thingsboard.server.common.data.page.PageData;
32 36 import org.thingsboard.server.common.data.page.PageLink;
  37 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
33 38 import org.thingsboard.server.common.data.relation.EntityRelation;
34 39 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
35 40 import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
36 41 import org.thingsboard.server.common.data.rule.RuleChain;
37 42 import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
  43 +import org.thingsboard.server.common.data.rule.RuleChainData;
  44 +import org.thingsboard.server.common.data.rule.RuleChainImportResult;
38 45 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
39 46 import org.thingsboard.server.common.data.rule.RuleNode;
40 47 import org.thingsboard.server.dao.entity.AbstractEntityService;
... ... @@ -46,9 +53,14 @@ import org.thingsboard.server.dao.tenant.TenantDao;
46 53
47 54 import java.util.ArrayList;
48 55 import java.util.HashMap;
  56 +import java.util.Iterator;
49 57 import java.util.List;
50 58 import java.util.Map;
  59 +import java.util.Optional;
51 60 import java.util.concurrent.ExecutionException;
  61 +import java.util.stream.Collectors;
  62 +
  63 +import static org.thingsboard.server.common.data.DataConstants.TENANT;
52 64
53 65 /**
54 66 * Created by igor on 3/12/18.
... ... @@ -57,6 +69,7 @@ import java.util.concurrent.ExecutionException;
57 69 @Slf4j
58 70 public class BaseRuleChainService extends AbstractEntityService implements RuleChainService {
59 71
  72 + private static final int DEFAULT_PAGE_SIZE = 1000;
60 73 @Autowired
61 74 private RuleChainDao ruleChainDao;
62 75
... ... @@ -358,6 +371,141 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
358 371 tenantRuleChainsRemover.removeEntities(tenantId, tenantId);
359 372 }
360 373
  374 + @Override
  375 + public RuleChainData exportTenantRuleChains(TenantId tenantId, PageLink pageLink) {
  376 + Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request.");
  377 + Validator.validatePageLink(pageLink);
  378 + PageData<RuleChain> ruleChainData = ruleChainDao.findRuleChainsByTenantId(tenantId.getId(), pageLink);
  379 + List<RuleChain> ruleChains = ruleChainData.getData();
  380 + List<RuleChainMetaData> metadata = ruleChains.stream().map(rc -> loadRuleChainMetaData(tenantId, rc.getId())).collect(Collectors.toList());
  381 + RuleChainData rcData = new RuleChainData();
  382 + rcData.setRuleChains(ruleChains);
  383 + rcData.setMetadata(metadata);
  384 + setRandomRuleChainIds(rcData);
  385 + resetRuleNodeIds(metadata);
  386 + return rcData;
  387 + }
  388 +
  389 + @Override
  390 + public List<RuleChainImportResult> importTenantRuleChains(TenantId tenantId, RuleChainData ruleChainData, boolean overwrite) {
  391 + List<RuleChainImportResult> importResults = new ArrayList<>();
  392 + setRandomRuleChainIds(ruleChainData);
  393 + resetRuleNodeIds(ruleChainData.getMetadata());
  394 + resetRuleChainMetadataTenantIds(tenantId, ruleChainData.getMetadata());
  395 + if (overwrite) {
  396 + List<RuleChain> persistentRuleChains = findAllTenantRuleChains(tenantId);
  397 + for (RuleChain ruleChain : ruleChainData.getRuleChains()) {
  398 + ComponentLifecycleEvent lifecycleEvent;
  399 + Optional<RuleChain> persistentRuleChainOpt = persistentRuleChains.stream().filter(rc -> rc.getName().equals(ruleChain.getName())).findFirst();
  400 + if (persistentRuleChainOpt.isPresent()) {
  401 + setNewRuleChainId(ruleChain, ruleChainData.getMetadata(), ruleChain.getId(), persistentRuleChainOpt.get().getId());
  402 + ruleChain.setRoot(persistentRuleChainOpt.get().isRoot());
  403 + lifecycleEvent = ComponentLifecycleEvent.UPDATED;
  404 + } else {
  405 + ruleChain.setRoot(false);
  406 + lifecycleEvent = ComponentLifecycleEvent.CREATED;
  407 + }
  408 + ruleChain.setTenantId(tenantId);
  409 + ruleChainDao.save(tenantId, ruleChain);
  410 + importResults.add(new RuleChainImportResult(tenantId, ruleChain.getId(), lifecycleEvent));
  411 + }
  412 + } else {
  413 + if (!CollectionUtils.isEmpty(ruleChainData.getRuleChains())) {
  414 + ruleChainData.getRuleChains().forEach(rc -> {
  415 + rc.setTenantId(tenantId);
  416 + rc.setRoot(false);
  417 + RuleChain savedRc = ruleChainDao.save(tenantId, rc);
  418 + importResults.add(new RuleChainImportResult(tenantId, savedRc.getId(), ComponentLifecycleEvent.CREATED));
  419 + });
  420 + }
  421 + }
  422 + if (!CollectionUtils.isEmpty(ruleChainData.getMetadata())) {
  423 + ruleChainData.getMetadata().forEach(md -> saveRuleChainMetaData(tenantId, md));
  424 + }
  425 + return importResults;
  426 + }
  427 +
  428 + private void resetRuleChainMetadataTenantIds(TenantId tenantId, List<RuleChainMetaData> metaData) {
  429 + for (RuleChainMetaData md : metaData) {
  430 + for (RuleNode node : md.getNodes()) {
  431 + JsonNode nodeConfiguration = node.getConfiguration();
  432 + searchTenantIdRecursive(tenantId, nodeConfiguration);
  433 + }
  434 + }
  435 + }
  436 +
  437 + private void searchTenantIdRecursive(TenantId tenantId, JsonNode node) {
  438 + Iterator<String> iter = node.fieldNames();
  439 + boolean isTenantId = false;
  440 + while (iter.hasNext()) {
  441 + String field = iter.next();
  442 + if ("entityType".equals(field) && TENANT.equals(node.get(field).asText())) {
  443 + isTenantId = true;
  444 + break;
  445 + }
  446 + }
  447 + if (isTenantId) {
  448 + ObjectNode objNode = (ObjectNode) node;
  449 + objNode.put("id", tenantId.getId().toString());
  450 + } else {
  451 + Iterator<JsonNode> childIter = node.iterator();
  452 + while (childIter.hasNext()) {
  453 + searchTenantIdRecursive(tenantId, childIter.next());
  454 + }
  455 + }
  456 + }
  457 +
  458 + private void setRandomRuleChainIds(RuleChainData ruleChainData) {
  459 + for (RuleChain ruleChain : ruleChainData.getRuleChains()) {
  460 + RuleChainId oldRuleChainId = ruleChain.getId();
  461 + RuleChainId newRuleChainId = new RuleChainId(Uuids.timeBased());
  462 + setNewRuleChainId(ruleChain, ruleChainData.getMetadata(), oldRuleChainId, newRuleChainId);
  463 + ruleChain.setTenantId(null);
  464 + }
  465 + }
  466 +
  467 + private void resetRuleNodeIds(List<RuleChainMetaData> metaData) {
  468 + for (RuleChainMetaData md : metaData) {
  469 + for (RuleNode node : md.getNodes()) {
  470 + node.setId(null);
  471 + node.setRuleChainId(null);
  472 + }
  473 + }
  474 + }
  475 +
  476 + private List<RuleChain> findAllTenantRuleChains(TenantId tenantId) {
  477 + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
  478 + return findAllTenantRuleChainsRecursive(tenantId, new ArrayList<>(), pageLink);
  479 + }
  480 +
  481 + private List<RuleChain> findAllTenantRuleChainsRecursive(TenantId tenantId, List<RuleChain> accumulator, PageLink pageLink) {
  482 + PageData<RuleChain> persistentRuleChainData = findTenantRuleChains(tenantId, pageLink);
  483 + List<RuleChain> ruleChains = persistentRuleChainData.getData();
  484 + if (!CollectionUtils.isEmpty(ruleChains)) {
  485 + accumulator.addAll(ruleChains);
  486 + }
  487 + if (persistentRuleChainData.hasNext()) {
  488 + return findAllTenantRuleChainsRecursive(tenantId, accumulator, pageLink.nextPageLink());
  489 + }
  490 + return accumulator;
  491 + }
  492 +
  493 + private void setNewRuleChainId(RuleChain ruleChain, List<RuleChainMetaData> metadata, RuleChainId oldRuleChainId, RuleChainId newRuleChainId) {
  494 + ruleChain.setId(newRuleChainId);
  495 + for (RuleChainMetaData metaData : metadata) {
  496 + if (metaData.getRuleChainId().equals(oldRuleChainId)) {
  497 + metaData.setRuleChainId(newRuleChainId);
  498 + }
  499 + if (!CollectionUtils.isEmpty(metaData.getRuleChainConnections())) {
  500 + for (RuleChainConnectionInfo rcConnInfo : metaData.getRuleChainConnections()) {
  501 + if (rcConnInfo.getTargetRuleChainId().equals(oldRuleChainId)) {
  502 + rcConnInfo.setTargetRuleChainId(newRuleChainId);
  503 + }
  504 + }
  505 + }
  506 + }
  507 + }
  508 +
361 509 private void checkRuleNodesAndDelete(TenantId tenantId, RuleChainId ruleChainId) {
362 510 try{
363 511 ruleChainDao.removeById(tenantId, ruleChainId.getId());
... ...