Merge branch 'feature/rule-chain-relation' of github.com:YevhenBondarenko/thingsboard into feature/rule-chain-relation

This commit is contained in:
ViacheslavKlimov 2025-05-07 11:14:15 +03:00
commit ac41220a6d
4 changed files with 165 additions and 6 deletions

View File

@ -24,22 +24,33 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
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.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.JpaExecutorService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
import org.thingsboard.server.service.install.DbUpgradeExecutorService;
import org.thingsboard.server.utils.TbNodeUpgradeUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import static org.thingsboard.server.dao.rule.BaseRuleChainService.TB_RULE_CHAIN_INPUT_NODE;
@Service
@Profile("install")
@Slf4j
@ -51,19 +62,69 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private RuleChainService ruleChainService;
@Autowired
private RelationService relationService;
@Autowired
private TenantService tenantService;
@Autowired
private ComponentDiscoveryService componentDiscoveryService;
@Autowired
JpaExecutorService jpaExecutorService;
private DbUpgradeExecutorService executorService;
@Override
public void updateData() throws Exception {
log.info("Updating data ...");
//TODO: should be cleaned after each release
inputNodesUpdater.updateEntities();
log.info("Data updated.");
}
//TODO: should be removed after release
private final PaginatedUpdater<String, Tenant> inputNodesUpdater = new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Input nodes updater";
}
@Override
protected PageData<Tenant> findEntities(String type, PageLink pageLink) {
return tenantService.findTenants(pageLink);
}
@Override
protected void updateEntity(Tenant tenant) {
TenantId tenantId = tenant.getId();
try {
var inputNodes = ruleChainService.findRuleNodesByTenantIdAndType(tenantId, TB_RULE_CHAIN_INPUT_NODE);
var resultFutures = inputNodes.stream().map(ruleNode -> {
try {
JsonNode id = ruleNode.getConfiguration().get("ruleChainId");
if (id != null) {
RuleChainId toRuleChainId = new RuleChainId(UUID.fromString(id.asText()));
RuleChainId fromRuleChainId = ruleNode.getRuleChainId();
EntityRelation relation = new EntityRelation();
relation.setFrom(fromRuleChainId);
relation.setTo(toRuleChainId);
relation.setType(EntityRelation.USES_TYPE);
relation.setTypeGroup(RelationTypeGroup.COMMON);
return relationService.saveRelationAsync(tenantId, relation);
}
} catch (Exception e) {
log.error("[{}] Failed to save relation for input node: [{}]", tenantId, ruleNode, e);
}
return Futures.immediateFuture(null);
}).toList();
Futures.allAsList(resultFutures).get();
} catch (Exception e) {
log.error("[{}] Unable to update Tenant input nodes", tenantId, e);
}
}
};
@Override
public void upgradeRuleNodes() {
int totalRuleNodesUpgraded = 0;
@ -107,7 +168,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
ruleNodeId, ruleNodeType, fromVersion, toVersion);
try {
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(ruleNode, ruleNodeClassInfo);
saveFutures.add(jpaExecutorService.submit(() -> {
saveFutures.add(executorService.submit(() -> {
ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode);
log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, fromVersion, toVersion);

View File

@ -43,6 +43,7 @@ public class EntityRelation implements HasVersion, Serializable, EdqsObject {
public static final String EDGE_TYPE = "ManagedByEdge";
public static final String CONTAINS_TYPE = "Contains";
public static final String MANAGES_TYPE = "Manages";
public static final String USES_TYPE = "Uses";
@Setter
private EntityId from;

View File

@ -76,6 +76,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -204,11 +205,18 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
}
}
RuleChainId ruleChainId = ruleChain.getId();
List<RuleNodeUpdateResult> updatedRuleNodes = new ArrayList<>();
List<RuleNode> existingRuleNodes = getRuleChainNodes(tenantId, ruleChainMetaData.getRuleChainId());
for (RuleNode existingNode : existingRuleNodes) {
relationService.deleteEntityRelations(tenantId, existingNode.getId());
if (existingNode.getType().equals(TB_RULE_CHAIN_INPUT_NODE)) {
if (existingNode.getConfiguration().has("ruleChainId")) {
RuleChainId targetRuleChainId = extractRuleChainIdFromInputNode(existingNode);
var relation = createRuleChainInputRelation(ruleChainId, targetRuleChainId);
relationService.deleteRelation(tenantId, relation);
}
}
Integer index = ruleNodeIndexMap.get(existingNode.getId());
RuleNode newRuleNode = null;
if (index != null) {
@ -220,7 +228,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
updatedRuleNodes.add(new RuleNodeUpdateResult(existingNode, newRuleNode));
}
RuleChainId ruleChainId = ruleChain.getId();
if (nodes != null) {
long now = System.currentTimeMillis();
for (RuleNode node : toAddOrUpdate) {
@ -233,6 +241,13 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
RuleNode savedNode = ruleNodeDao.save(tenantId, node);
relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
if (node.getType().equals(TB_RULE_CHAIN_INPUT_NODE)) {
if (node.getConfiguration().has("ruleChainId")) {
RuleChainId targetRuleChainId = extractRuleChainIdFromInputNode(node);
var relation = createRuleChainInputRelation(ruleChainId, targetRuleChainId);
relations.add(relation);
}
}
int index = nodes.indexOf(node);
nodes.set(index, savedNode);
ruleNodeIndexMap.put(savedNode.getId(), index);
@ -265,7 +280,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
RuleNode targetNode = new RuleNode();
targetNode.setName(targetRuleChain != null ? targetRuleChain.getName() : "Rule Chain Input");
targetNode.setRuleChainId(ruleChainId);
targetNode.setType("org.thingsboard.rule.engine.flow.TbRuleChainInputNode");
targetNode.setType(TB_RULE_CHAIN_INPUT_NODE);
var configuration = JacksonUtil.newObjectNode();
configuration.put("ruleChainId", targetRuleChainId.getId().toString());
targetNode.setConfiguration(configuration);
@ -302,6 +317,21 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
return RuleChainUpdateResult.successful(updatedRuleNodes);
}
private EntityRelation createRuleChainInputRelation(RuleChainId ruleChainId, RuleChainId targetRuleChainId) {
EntityRelation relation = new EntityRelation();
relation.setFrom(ruleChainId);
relation.setTo(targetRuleChainId);
relation.setType(EntityRelation.USES_TYPE);
relation.setTypeGroup(RelationTypeGroup.COMMON);
return relation;
}
private RuleChainId extractRuleChainIdFromInputNode(RuleNode node) {
JsonNode configuration = node.getConfiguration();
UUID targetUuid = UUID.fromString(configuration.get("ruleChainId").asText());
return new RuleChainId(targetUuid);
}
@Override
public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id.");

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
@ -28,12 +29,14 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
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.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.io.IOException;
@ -44,6 +47,8 @@ import java.util.UUID;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.thingsboard.server.common.data.relation.EntityRelation.USES_TYPE;
import static org.thingsboard.server.dao.rule.BaseRuleChainService.TB_RULE_CHAIN_INPUT_NODE;
/**
* Created by igor on 3/13/18.
@ -55,6 +60,8 @@ public class RuleChainServiceTest extends AbstractServiceTest {
EdgeService edgeService;
@Autowired
RuleChainService ruleChainService;
@Autowired
RelationService relationService;
private IdComparator<RuleChain> idComparator = new IdComparator<>();
private IdComparator<RuleNode> ruleNodeIdComparator = new IdComparator<>();
@ -354,6 +361,66 @@ public class RuleChainServiceTest extends AbstractServiceTest {
Assert.assertTrue(ruleChainById.isRoot());
}
@Test
public void testSaveRuleChainWithInputNode() {
RuleChain toRuleChain = new RuleChain();
toRuleChain.setName("To Rule Chain");
toRuleChain.setTenantId(tenantId);
RuleChain savedToRuleChain = ruleChainService.saveRuleChain(toRuleChain);
RuleChain fromRuleChain = new RuleChain();
fromRuleChain.setName("From RuleChain");
fromRuleChain.setTenantId(tenantId);
RuleChain savedFromRuleChain = ruleChainService.saveRuleChain(fromRuleChain);
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
ruleChainMetaData.setRuleChainId(savedFromRuleChain.getId());
RuleNode ruleNode = new RuleNode();
ruleNode.setName("Input node");
ruleNode.setType(TB_RULE_CHAIN_INPUT_NODE);
ObjectNode configuration = JacksonUtil.newObjectNode();
configuration.put("ruleChainId", savedToRuleChain.getId().toString());
ruleNode.setConfiguration(configuration);
List<RuleNode> ruleNodes = new ArrayList<>();
ruleNodes.add(ruleNode);
ruleChainMetaData.setFirstNodeIndex(0);
ruleChainMetaData.setNodes(ruleNodes);
ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData, Function.identity());
List<EntityRelation> relations = relationService.findByFromAndType(tenantId, savedFromRuleChain.getId(), USES_TYPE, RelationTypeGroup.COMMON);
Assert.assertEquals(1, relations.size());
EntityRelation usesRelation = relations.get(0);
Assert.assertEquals(savedFromRuleChain.getId(), usesRelation.getFrom());
Assert.assertEquals(savedToRuleChain.getId(), usesRelation.getTo());
RuleChain newToRuleChain = new RuleChain();
newToRuleChain.setName("New To Rule Chain");
newToRuleChain.setTenantId(tenantId);
RuleChain savedNewToRuleChain = ruleChainService.saveRuleChain(newToRuleChain);
RuleNode newRuleNode = new RuleNode();
newRuleNode.setName("Input node");
newRuleNode.setType(TB_RULE_CHAIN_INPUT_NODE);
ObjectNode newConfiguration = JacksonUtil.newObjectNode();
configuration.put("ruleChainId", savedNewToRuleChain.getId().toString());
newRuleNode.setConfiguration(newConfiguration);
List<RuleNode> newRuleNodes = new ArrayList<>();
newRuleNodes.add(newRuleNode);
RuleChainMetaData foundRuleChainMetaData = ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId());
foundRuleChainMetaData.setNodes(newRuleNodes);
ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData, Function.identity());
List<EntityRelation> newRelations = relationService.findByFromAndType(tenantId, savedFromRuleChain.getId(), USES_TYPE, RelationTypeGroup.COMMON);
Assert.assertEquals(1, relations.size());
EntityRelation newUsesRelation = newRelations.get(0);
Assert.assertEquals(savedFromRuleChain.getId(), newUsesRelation.getFrom());
Assert.assertEquals(savedNewToRuleChain.getId(), newUsesRelation.getTo());
}
private RuleChainId saveRuleChainAndSetAutoAssignToEdge(String name) {
RuleChain edgeRuleChain = new RuleChain();
edgeRuleChain.setTenantId(tenantId);