Commit 17053118e1ba250c90834561c182f12d69ea9422
Committed by
Andrew Shvayka
1 parent
9fef6b02
rule chain bulk import export
Showing
5 changed files
with
246 additions
and
0 deletions
... | ... | @@ -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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainImportResult.java
0 → 100644
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()); | ... | ... |