From 5bfdf9decf760bed5f67672890ba52e9c7c4e826 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Mar 2018 10:46:12 +0200 Subject: [PATCH 1/2] Rule Chain Service --- .../server/dao/rule/BaseRuleChainService.java | 130 ++++++++++++++++++ .../dao/rule/CassandraRuleChainDao.java | 14 ++ .../server/dao/rule/RuleChainDao.java | 9 ++ .../server/dao/rule/RuleChainService.java | 20 +++ .../server/dao/sql/rule/JpaRuleChainDao.java | 11 ++ .../dao/sql/rule/RuleChainRepository.java | 8 ++ 6 files changed, 192 insertions(+) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java new file mode 100644 index 0000000000..eb6be43fcf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -0,0 +1,130 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thingsboard.server.dao.rule; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.service.Validator; + +import java.util.List; + +/** + * Created by igor on 3/12/18. + */ +@Service +@Slf4j +public class BaseRuleChainService extends AbstractEntityService implements RuleChainService { + + public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + + @Autowired + private RuleChainDao ruleChainDao; + + @Override + public RuleChain saveRuleChain(RuleChain ruleChain) { + ruleChainValidator.validate(ruleChain); + if (ruleChain.getTenantId() == null) { + log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT); + ruleChain.setTenantId(SYSTEM_TENANT); + } + return ruleChainDao.save(ruleChain); + } + + @Override + public RuleChain findRuleChainById(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); + return ruleChainDao.findById(ruleChainId.getId()); + } + + @Override + public TextPageData findSystemRuleChains(TextPageLink pageLink) { + Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request."); + List ruleChains = ruleChainDao.findRuleChainsByTenantId(SYSTEM_TENANT.getId(), pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + + @Override + public TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink) { + Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request."); + Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request."); + List ruleChains = ruleChainDao.findRuleChainsByTenantId(tenantId.getId(), pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + + @Override + public TextPageData findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) { + log.trace("Executing findAllTenantRuleChainsByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink); + Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); + Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); + List ruleChains = ruleChainDao.findAllRuleChainsByTenantId(tenantId.getId(), pageLink); + return new TextPageData<>(ruleChains, pageLink); + } + + @Override + public void deleteRuleChainById(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); + checkRuleNodesAndDelete(ruleChainId); + } + + private void checkRuleNodesAndDelete(RuleChainId ruleChainId) { + //TODO: + deleteEntityRelations(ruleChainId); + ruleChainDao.removeById(ruleChainId.getId()); + } + + @Override + public void deleteRuleChainsByTenantId(TenantId tenantId) { + Validator.validateId(tenantId, "Incorrect tenant id for delete rule chains request."); + tenantRuleChainsRemover.removeEntities(tenantId); + } + + private DataValidator ruleChainValidator = + new DataValidator() { + @Override + protected void validateDataImpl(RuleChain ruleChain) { + if (StringUtils.isEmpty(ruleChain.getName())) { + throw new DataValidationException("Rule chain name should be specified!."); + } + } + }; + + private PaginatedRemover tenantRuleChainsRemover = + new PaginatedRemover() { + + @Override + protected List findEntities(TenantId id, TextPageLink pageLink) { + return ruleChainDao.findRuleChainsByTenantId(id.getId(), pageLink); + } + + @Override + protected void removeEntity(RuleChain entity) { + checkRuleNodesAndDelete(entity.getId()); + } + }; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java index 863904be65..47c8e86d61 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java @@ -20,15 +20,18 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.nosql.RuleChainEntity; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.util.NoSqlDao; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.in; import static org.thingsboard.server.dao.model.ModelConstants.*; @Component @@ -57,4 +60,15 @@ public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + log.debug("Try to find all rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink); + List ruleChainEntities = findPageWithTextSearch(RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, + Arrays.asList(in(ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))), + pageLink); + + log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink); + return DaoUtil.convertDataList(ruleChainEntities); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index 4a7cfaea7c..b9a9932ad3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -37,4 +37,13 @@ public interface RuleChainDao extends Dao { */ List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + /** + * Find all rule chains by tenantId and page link. + * + * @param tenantId the tenantId + * @param pageLink the page link + * @return the list of rule chain objects + */ + List findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index c6c947ba9f..00dc97315e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -16,8 +16,16 @@ package org.thingsboard.server.dao.rule; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.rule.RuleChain; +import java.util.List; + /** * Created by igor on 3/12/18. */ @@ -25,4 +33,16 @@ public interface RuleChainService { RuleChain saveRuleChain(RuleChain ruleChain); + RuleChain findRuleChainById(RuleChainId ruleChainId); + + TextPageData findSystemRuleChains(TextPageLink pageLink); + + TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); + + TextPageData findAllTenantRuleChainsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink); + + void deleteRuleChainById(RuleChainId ruleChainId); + + void deleteRuleChainsByTenantId(TenantId tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index fb5d20a702..425369fa2e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -63,4 +63,15 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findAllRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { + return DaoUtil.convertDataList(ruleChainRepository + .findAllTenantRuleChainsByTenantId( + UUIDConverter.fromTimeUUID(tenantId), + NULL_UUID_STR, + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index 58d4aac70b..3b5e4dbcd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -35,4 +35,12 @@ public interface RuleChainRepository extends CrudRepository :idOffset ORDER BY rc.id") + List findAllTenantRuleChainsByTenantId(@Param("tenantId") String tenantId, + @Param("nullTenantId") String nullTenantId, + @Param("searchText") String searchText, + @Param("idOffset") String idOffset, + Pageable pageable); } From 4956c0c573fbeba976f5fe5c6d9c138358439bed Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Mar 2018 13:12:00 +0200 Subject: [PATCH 2/2] Rule Chain service implementation. --- .../data/relation/RelationTypeGroup.java | 4 +- .../common/data/rule/RuleChainMetaData.java | 75 ++++++ .../server/dao/rule/BaseRuleChainService.java | 227 +++++++++++++++++- .../server/dao/rule/RuleChainService.java | 14 ++ .../server/dao/tenant/TenantServiceImpl.java | 5 + 5 files changed, 317 insertions(+), 8 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java index 90e0253370..55990552b0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java @@ -19,6 +19,8 @@ public enum RelationTypeGroup { COMMON, ALARM, - DASHBOARD + DASHBOARD, + RULE_CHAIN, + RULE_NODE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java new file mode 100644 index 0000000000..af141d6142 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.rule; + +import lombok.Data; +import org.thingsboard.server.common.data.id.RuleChainId; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by igor on 3/13/18. + */ +@Data +public class RuleChainMetaData { + + private RuleChainId ruleChainId; + + private Integer firstNodeIndex; + + private List nodes; + + private List connections; + + private List ruleChainConnections; + + public void addConnectionInfo(int fromIndex, int toIndex, String type) { + NodeConnectionInfo connectionInfo = new NodeConnectionInfo(); + connectionInfo.setFromIndex(fromIndex); + connectionInfo.setToIndex(toIndex); + connectionInfo.setType(type); + if (connections == null) { + connections = new ArrayList<>(); + } + connections.add(connectionInfo); + } + public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type) { + RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo(); + connectionInfo.setFromIndex(fromIndex); + connectionInfo.setTargetRuleChainId(targetRuleChainId); + connectionInfo.setType(type); + if (ruleChainConnections == null) { + ruleChainConnections = new ArrayList<>(); + } + ruleChainConnections.add(connectionInfo); + } + + @Data + public class NodeConnectionInfo { + private int fromIndex; + private int toIndex; + private String type; + } + + @Data + public class RuleChainConnectionInfo { + private int fromIndex; + private RuleChainId targetRuleChainId; + private String type; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index eb6be43fcf..073ccb912e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -20,19 +20,33 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.tenant.TenantDao; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; /** * Created by igor on 3/12/18. @@ -46,6 +60,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Autowired private RuleChainDao ruleChainDao; + @Autowired + private RuleNodeDao ruleNodeDao; + + @Autowired + private TenantDao tenantDao; + @Override public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain); @@ -53,7 +73,144 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC log.trace("Save system rule chain with predefined id {}", SYSTEM_TENANT); ruleChain.setTenantId(SYSTEM_TENANT); } - return ruleChainDao.save(ruleChain); + RuleChain savedRuleChain = ruleChainDao.save(ruleChain); + if (ruleChain.isRoot() && ruleChain.getTenantId() != null && ruleChain.getId() == null) { + try { + createRelation(new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create tenant to root rule chain relation. from: [{}], to: [{}]", + savedRuleChain.getTenantId(), savedRuleChain.getId()); + throw new RuntimeException(e); + } + } + return savedRuleChain; + } + + @Override + public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) { + Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); + RuleChain ruleChain = findRuleChainById(ruleChainMetaData.getRuleChainId()); + if (ruleChain == null) { + return null; + } + + List nodes = ruleChainMetaData.getNodes(); + List toAdd = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + List toDelete = new ArrayList<>(); + + Map ruleNodeIndexMap = new HashMap<>(); + if (nodes != null) { + for (RuleNode node : nodes) { + if (node.getId() != null) { + ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node)); + } else { + toAdd.add(node); + } + } + } + + List existingRuleNodes = getRuleChainNodes(ruleChainMetaData.getRuleChainId()); + for (RuleNode existingNode : existingRuleNodes) { + deleteEntityRelations(existingNode.getId()); + Integer index = ruleNodeIndexMap.get(existingNode.getId()); + if (index != null) { + toUpdate.add(ruleChainMetaData.getNodes().get(index)); + } else { + toDelete.add(existingNode); + } + } + for (RuleNode node : toAdd) { + RuleNode savedNode = ruleNodeDao.save(node); + try { + createRelation(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create rule chain to rule node relation. from: [{}], to: [{}]", + ruleChainMetaData.getRuleChainId(), savedNode.getId()); + throw new RuntimeException(e); + } + int index = nodes.indexOf(node); + nodes.set(index, savedNode); + ruleNodeIndexMap.put(savedNode.getId(), index); + } + for (RuleNode node: toDelete) { + deleteRuleNode(node.getId()); + } + RuleNodeId firstRuleNodeId = null; + if (ruleChainMetaData.getFirstNodeIndex() != null) { + firstRuleNodeId = nodes.get(ruleChainMetaData.getFirstNodeIndex()).getId(); + } + if ((ruleChain.getFirstRuleNodeId() != null && !ruleChain.getFirstRuleNodeId().equals(firstRuleNodeId)) + || (ruleChain.getFirstRuleNodeId() == null && firstRuleNodeId != null)) { + ruleChain.setFirstRuleNodeId(firstRuleNodeId); + ruleChainDao.save(ruleChain); + } + if (ruleChainMetaData.getConnections() != null) { + for (RuleChainMetaData.NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) { + EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); + EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); + String type = nodeConnection.getType(); + try { + createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create rule node relation. from: [{}], to: [{}]", from, to); + throw new RuntimeException(e); + } + } + } + if (ruleChainMetaData.getRuleChainConnections() != null) { + for (RuleChainMetaData.RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { + EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); + EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); + String type = nodeToRuleChainConnection.getType(); + try { + createRelation(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + } catch (ExecutionException | InterruptedException e) { + log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to); + throw new RuntimeException(e); + } + } + } + + return loadRuleChainMetaData(ruleChainMetaData.getRuleChainId()); + } + + @Override + public RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id."); + RuleChain ruleChain = findRuleChainById(ruleChainId); + if (ruleChain == null) { + return null; + } + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(ruleChainId); + List ruleNodes = getRuleChainNodes(ruleChainId); + Map ruleNodeIndexMap = new HashMap<>(); + for (RuleNode node : ruleNodes) { + ruleNodeIndexMap.put(node.getId(), ruleNodes.indexOf(node)); + } + ruleChainMetaData.setNodes(ruleNodes); + if (ruleChain.getFirstRuleNodeId() != null) { + ruleChainMetaData.setFirstNodeIndex(ruleNodeIndexMap.get(ruleChain.getFirstRuleNodeId())); + } + for (RuleNode node : ruleNodes) { + int fromIndex = ruleNodeIndexMap.get(node.getId()); + List nodeRelations = getRuleNodeRelations(node.getId()); + for (EntityRelation nodeRelation : nodeRelations) { + String type = nodeRelation.getType(); + if (nodeRelation.getTo().getEntityType() == EntityType.RULE_NODE) { + RuleNodeId toNodeId = new RuleNodeId(nodeRelation.getTo().getId()); + int toIndex = ruleNodeIndexMap.get(toNodeId); + ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); + } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { + RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); + ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type); + } + } + } + return ruleChainMetaData; } @Override @@ -62,6 +219,33 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleChainDao.findById(ruleChainId.getId()); } + @Override + public RuleChain getRootTenantRuleChain(TenantId tenantId) { + Validator.validateId(tenantId, "Incorrect tenant id for search request."); + List relations = relationService.findByFrom(tenantId, RelationTypeGroup.RULE_CHAIN); + if (relations != null && !relations.isEmpty()) { + EntityRelation relation = relations.get(0); + RuleChainId ruleChainId = new RuleChainId(relation.getTo().getId()); + return findRuleChainById(ruleChainId); + } else { + return null; + } + } + + @Override + public List getRuleChainNodes(RuleChainId ruleChainId) { + Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); + List relations = getRuleChainToNodeRelations(ruleChainId); + List ruleNodes = relations.stream().map(relation -> ruleNodeDao.findById(relation.getTo().getId())).collect(Collectors.toList()); + return ruleNodes; + } + + @Override + public List getRuleNodeRelations(RuleNodeId ruleNodeId) { + Validator.validateId(ruleNodeId, "Incorrect rule node id for search request."); + return relationService.findByFrom(ruleNodeId, RelationTypeGroup.RULE_NODE); + } + @Override public TextPageData findSystemRuleChains(TextPageLink pageLink) { Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request."); @@ -92,18 +276,35 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC checkRuleNodesAndDelete(ruleChainId); } - private void checkRuleNodesAndDelete(RuleChainId ruleChainId) { - //TODO: - deleteEntityRelations(ruleChainId); - ruleChainDao.removeById(ruleChainId.getId()); - } - @Override public void deleteRuleChainsByTenantId(TenantId tenantId) { Validator.validateId(tenantId, "Incorrect tenant id for delete rule chains request."); tenantRuleChainsRemover.removeEntities(tenantId); } + private void checkRuleNodesAndDelete(RuleChainId ruleChainId) { + List nodeRelations = getRuleChainToNodeRelations(ruleChainId); + for (EntityRelation relation : nodeRelations) { + deleteRuleNode(relation.getTo()); + } + deleteEntityRelations(ruleChainId); + ruleChainDao.removeById(ruleChainId.getId()); + } + + private List getRuleChainToNodeRelations(RuleChainId ruleChainId) { + return relationService.findByFrom(ruleChainId, RelationTypeGroup.RULE_CHAIN); + } + + private void deleteRuleNode(EntityId entityId) { + deleteEntityRelations(entityId); + ruleNodeDao.removeById(entityId.getId()); + } + + private void createRelation(EntityRelation relation) throws ExecutionException, InterruptedException { + log.debug("Creating relation: {}", relation); + relationService.saveRelationAsync(relation).get(); + } + private DataValidator ruleChainValidator = new DataValidator() { @Override @@ -111,6 +312,18 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (StringUtils.isEmpty(ruleChain.getName())) { throw new DataValidationException("Rule chain name should be specified!."); } + if (ruleChain.getTenantId() != null && !ruleChain.getTenantId().isNullUid()) { + Tenant tenant = tenantDao.findById(ruleChain.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); + } + if (ruleChain.isRoot()) { + RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); + if (ruleChain.getId() == null || !ruleChain.getId().equals(rootRuleChain.getId())) { + throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); + } + } + } } }; diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 00dc97315e..c6a2941938 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -18,11 +18,15 @@ package org.thingsboard.server.dao.rule; import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; import java.util.List; @@ -33,8 +37,18 @@ public interface RuleChainService { RuleChain saveRuleChain(RuleChain ruleChain); + RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData); + + RuleChainMetaData loadRuleChainMetaData(RuleChainId ruleChainId); + RuleChain findRuleChainById(RuleChainId ruleChainId); + RuleChain getRootTenantRuleChain(TenantId tenantId); + + List getRuleChainNodes(RuleChainId ruleChainId); + + List getRuleNodeRelations(RuleNodeId ruleNodeId); + TextPageData findSystemRuleChains(TextPageLink pageLink); TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 24abe52802..3a607b623e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -31,6 +31,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -76,6 +77,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private PluginService pluginService; + @Autowired + private RuleChainService ruleChainService; + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); @@ -108,6 +112,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe assetService.deleteAssetsByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); + ruleChainService.deleteRuleChainsByTenantId(tenantId); ruleService.deleteRulesByTenantId(tenantId); pluginService.deletePluginsByTenantId(tenantId); tenantDao.removeById(tenantId.getId());