9 changed files with 502 additions and 1 deletions
@ -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<RuleNode> nodes; |
||||
|
|
||||
|
private List<NodeConnectionInfo> connections; |
||||
|
|
||||
|
private List<RuleChainConnectionInfo> 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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,343 @@ |
|||||
|
/** |
||||
|
* 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.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. |
||||
|
*/ |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class BaseRuleChainService extends AbstractEntityService implements RuleChainService { |
||||
|
|
||||
|
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); |
||||
|
|
||||
|
@Autowired |
||||
|
private RuleChainDao ruleChainDao; |
||||
|
|
||||
|
@Autowired |
||||
|
private RuleNodeDao ruleNodeDao; |
||||
|
|
||||
|
@Autowired |
||||
|
private TenantDao tenantDao; |
||||
|
|
||||
|
@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); |
||||
|
} |
||||
|
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<RuleNode> nodes = ruleChainMetaData.getNodes(); |
||||
|
List<RuleNode> toAdd = new ArrayList<>(); |
||||
|
List<RuleNode> toUpdate = new ArrayList<>(); |
||||
|
List<RuleNode> toDelete = new ArrayList<>(); |
||||
|
|
||||
|
Map<RuleNodeId, Integer> 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<RuleNode> 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<RuleNode> ruleNodes = getRuleChainNodes(ruleChainId); |
||||
|
Map<RuleNodeId, Integer> 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<EntityRelation> 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 |
||||
|
public RuleChain findRuleChainById(RuleChainId ruleChainId) { |
||||
|
Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); |
||||
|
return ruleChainDao.findById(ruleChainId.getId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleChain getRootTenantRuleChain(TenantId tenantId) { |
||||
|
Validator.validateId(tenantId, "Incorrect tenant id for search request."); |
||||
|
List<EntityRelation> 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<RuleNode> getRuleChainNodes(RuleChainId ruleChainId) { |
||||
|
Validator.validateId(ruleChainId, "Incorrect rule chain id for search request."); |
||||
|
List<EntityRelation> relations = getRuleChainToNodeRelations(ruleChainId); |
||||
|
List<RuleNode> ruleNodes = relations.stream().map(relation -> ruleNodeDao.findById(relation.getTo().getId())).collect(Collectors.toList()); |
||||
|
return ruleNodes; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<EntityRelation> getRuleNodeRelations(RuleNodeId ruleNodeId) { |
||||
|
Validator.validateId(ruleNodeId, "Incorrect rule node id for search request."); |
||||
|
return relationService.findByFrom(ruleNodeId, RelationTypeGroup.RULE_NODE); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TextPageData<RuleChain> findSystemRuleChains(TextPageLink pageLink) { |
||||
|
Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system rule chain request."); |
||||
|
List<RuleChain> ruleChains = ruleChainDao.findRuleChainsByTenantId(SYSTEM_TENANT.getId(), pageLink); |
||||
|
return new TextPageData<>(ruleChains, pageLink); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TextPageData<RuleChain> 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<RuleChain> ruleChains = ruleChainDao.findRuleChainsByTenantId(tenantId.getId(), pageLink); |
||||
|
return new TextPageData<>(ruleChains, pageLink); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TextPageData<RuleChain> 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<RuleChain> 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); |
||||
|
} |
||||
|
|
||||
|
@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<EntityRelation> nodeRelations = getRuleChainToNodeRelations(ruleChainId); |
||||
|
for (EntityRelation relation : nodeRelations) { |
||||
|
deleteRuleNode(relation.getTo()); |
||||
|
} |
||||
|
deleteEntityRelations(ruleChainId); |
||||
|
ruleChainDao.removeById(ruleChainId.getId()); |
||||
|
} |
||||
|
|
||||
|
private List<EntityRelation> 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<RuleChain> ruleChainValidator = |
||||
|
new DataValidator<RuleChain>() { |
||||
|
@Override |
||||
|
protected void validateDataImpl(RuleChain ruleChain) { |
||||
|
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!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private PaginatedRemover<TenantId, RuleChain> tenantRuleChainsRemover = |
||||
|
new PaginatedRemover<TenantId, RuleChain>() { |
||||
|
|
||||
|
@Override |
||||
|
protected List<RuleChain> findEntities(TenantId id, TextPageLink pageLink) { |
||||
|
return ruleChainDao.findRuleChainsByTenantId(id.getId(), pageLink); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void removeEntity(RuleChain entity) { |
||||
|
checkRuleNodesAndDelete(entity.getId()); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
Loading…
Reference in new issue