From 5387f545d60377540ec31aade2a6c35f8fe2a0eb Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 8 Sep 2023 16:47:44 +0300 Subject: [PATCH 1/3] edge downlink table - fixed error in case unknown entity type or action. added missing types/actions --- .../components/edge/edge-downlink-table-config.ts | 10 ++++++++-- ui-ngx/src/app/shared/models/edge.models.ts | 14 ++++++++++++-- .../src/assets/locale/locale.constant-en_US.json | 5 +++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts b/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts index 5889ebcdf2..f970ad80f4 100644 --- a/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts @@ -107,9 +107,15 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig('createdTime', 'event.event-time', this.datePipe, '120px'), new EntityTableColumn('type', 'event.type', '25%', - entity => this.translate.instant(edgeEventTypeTranslations.get(entity.type)), entity => ({}), false), + entity => { + let key = edgeEventTypeTranslations.get(entity.type); + return key ? this.translate.instant(key) : entity.type; + }, entity => ({}), false), new EntityTableColumn('action', 'edge.event-action', '25%', - entity => this.translate.instant(edgeEventActionTypeTranslations.get(entity.action)), entity => ({}), false), + entity => { + let key = edgeEventActionTypeTranslations.get(entity.action); + return key ? this.translate.instant(key) : entity.action; + }, entity => ({}), false), new EntityTableColumn('entityId', 'edge.entity-id', '40%', (entity) => entity.entityId ? entity.entityId : '', () => ({}), false), new EntityTableColumn('status', 'event.status', '10%', diff --git a/ui-ngx/src/app/shared/models/edge.models.ts b/ui-ngx/src/app/shared/models/edge.models.ts index 372a910402..1acfa2ca28 100644 --- a/ui-ngx/src/app/shared/models/edge.models.ts +++ b/ui-ngx/src/app/shared/models/edge.models.ts @@ -59,9 +59,12 @@ export enum EdgeEventType { CUSTOMER = 'CUSTOMER', RELATION = 'RELATION', TENANT = 'TENANT', + TENANT_PROFILE = 'TENANT_PROFILE', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', WIDGET_TYPE = 'WIDGET_TYPE', - ADMIN_SETTINGS = 'ADMIN_SETTINGS' + ADMIN_SETTINGS = 'ADMIN_SETTINGS', + OTA_PACKAGE = 'OTA_PACKAGE', + QUEUE = 'QUEUE' } export enum EdgeEventActionType { @@ -80,6 +83,8 @@ export enum EdgeEventActionType { RPC_CALL = 'RPC_CALL', ALARM_ACK = 'ALARM_ACK', ALARM_CLEAR = 'ALARM_CLEAR', + ALARM_ASSIGNED = 'ALARM_ASSIGNED', + ALARM_UNASSIGNED = 'ALARM_UNASSIGNED', ASSIGNED_TO_EDGE = 'ASSIGNED_TO_EDGE', UNASSIGNED_FROM_EDGE = 'UNASSIGNED_FROM_EDGE', CREDENTIALS_REQUEST = 'CREDENTIALS_REQUEST', @@ -107,9 +112,12 @@ export const edgeEventTypeTranslations = new Map( [EdgeEventType.CUSTOMER, 'edge-event.type-customer'], [EdgeEventType.RELATION, 'edge-event.type-relation'], [EdgeEventType.TENANT, 'edge-event.type-tenant'], + [EdgeEventType.TENANT_PROFILE, 'edge-event.type-tenant-profile'], [EdgeEventType.WIDGETS_BUNDLE, 'edge-event.type-widgets-bundle'], [EdgeEventType.WIDGET_TYPE, 'edge-event.type-widgets-type'], - [EdgeEventType.ADMIN_SETTINGS, 'edge-event.type-admin-settings'] + [EdgeEventType.ADMIN_SETTINGS, 'edge-event.type-admin-settings'], + [EdgeEventType.OTA_PACKAGE, 'edge-event.type-ota-package'], + [EdgeEventType.QUEUE, 'edge-event.type-queue'] ] ); @@ -130,6 +138,8 @@ export const edgeEventActionTypeTranslations = new Map Date: Tue, 12 Sep 2023 12:16:31 +0300 Subject: [PATCH 2/3] added executor to speed up rule nodes upgrade & added SQL upgrade for TbMsgAttributesNode & updated logback.xml with new debug entry to view the rule node upgrade logs (#9224) * added executor to speed up rule nodes upgrade & added SQL upgrade for TbMsgAttributesNode & updated logback.xml with new debug entry to view the rule node upgrade logs * refactoring after review * create index before upsert * added missing logic to await futures to be completed with max pending futures size set to 100 * added better logging * refacotring after review --- .../install/SqlDatabaseUpgradeService.java | 7 +++ .../update/DefaultDataUpdateService.java | 44 ++++++++++++++----- application/src/main/resources/logback.xml | 2 + 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 4bb29426c9..9e942b481e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -756,6 +756,13 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_rule_node_type_configuration_version ON rule_node(type, configuration_version);"); } catch (Exception e) { } + try { + conn.createStatement().execute("UPDATE rule_node SET " + + "configuration = (configuration::jsonb || '{\"updateAttributesOnlyOnValueChange\": \"false\"}'::jsonb)::varchar, " + + "configuration_version = 1 " + + "WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version < 1;"); + } catch (Exception e) { + } try { conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';"); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index a1048d6a37..8c3b78040b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -78,6 +78,7 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.queue.QueueService; 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.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -101,6 +102,8 @@ import static org.thingsboard.server.common.data.StringUtils.isBlank; @Slf4j public class DefaultDataUpdateService implements DataUpdateService { + private static final int MAX_PENDING_SAVE_RULE_NODE_FUTURES = 100; + @Autowired private TenantService tenantService; @@ -153,6 +156,9 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private EdgeEventDao edgeEventDao; + @Autowired + JpaExecutorService jpaExecutorService; + @Override public void updateData(String fromVersion) throws Exception { switch (fromVersion) { @@ -225,13 +231,15 @@ public class DefaultDataUpdateService implements DataUpdateService { @Override public void upgradeRuleNodes() { try { + var futures = new ArrayList>(100); + int totalRuleNodesUpgraded = 0; log.info("Starting rule nodes upgrade ..."); var nodeClassToVersionMap = componentDiscoveryService.getVersionedNodes(); log.debug("Found {} versioned nodes to check for upgrade!", nodeClassToVersionMap.size()); - nodeClassToVersionMap.forEach(clazz -> { - var ruleNodeType = clazz.getClassName(); - var ruleNodeTypeForLogs = clazz.getSimpleName(); - var toVersion = clazz.getCurrentVersion(); + for (var ruleNodeClassInfo : nodeClassToVersionMap) { + var ruleNodeType = ruleNodeClassInfo.getClassName(); + var ruleNodeTypeForLogs = ruleNodeClassInfo.getSimpleName(); + var toVersion = ruleNodeClassInfo.getCurrentVersion(); log.debug("Going to check for nodes with type: {} to upgrade to version: {}.", ruleNodeTypeForLogs, toVersion); var ruleNodesToUpdate = new PageDataIterable<>( pageLink -> ruleChainService.findAllRuleNodesByTypeAndVersionLessThan(ruleNodeType, toVersion, pageLink), 1024 @@ -246,28 +254,44 @@ public class DefaultDataUpdateService implements DataUpdateService { log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion); try { - var tbVersionedNode = (TbNode) clazz.getClazz().getDeclaredConstructor().newInstance(); + var tbVersionedNode = (TbNode) ruleNodeClassInfo.getClazz().getDeclaredConstructor().newInstance(); TbPair upgradeRuleNodeConfigurationResult = tbVersionedNode.upgrade(fromVersion, oldConfiguration); if (upgradeRuleNodeConfigurationResult.getFirst()) { ruleNode.setConfiguration(upgradeRuleNodeConfigurationResult.getSecond()); } ruleNode.setConfigurationVersion(toVersion); - ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode); - log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", - ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion); + futures.add(jpaExecutorService.submit(() -> { + ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode); + log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", + ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion); + })); + if (futures.size() >= MAX_PENDING_SAVE_RULE_NODE_FUTURES) { + log.info("{} upgraded rule nodes so far ...", + totalRuleNodesUpgraded += awaitFuturesToCompleteAndGetCount(futures)); + futures.clear(); + } } catch (Exception e) { log.warn("Failed to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {} due to: ", ruleNodeId, ruleNodeTypeForLogs, fromVersion, toVersion, e); } } } - }); - log.info("Finished rule nodes upgrade!"); + } + log.info("Finished rule nodes upgrade. Upgraded rule nodes count: {}", + totalRuleNodesUpgraded + awaitFuturesToCompleteAndGetCount(futures)); } catch (Exception e) { log.error("Unexpected error during rule nodes upgrade: ", e); } } + private int awaitFuturesToCompleteAndGetCount(List> futures) { + try { + return Futures.allAsList(futures).get().size(); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException("Failed to process save rule nodes requests due to: ", e); + } + } + private final PaginatedUpdater deviceProfileEntityDynamicConditionsUpdater = new PaginatedUpdater<>() { diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml index eaf6ace29e..b0a4174012 100644 --- a/application/src/main/resources/logback.xml +++ b/application/src/main/resources/logback.xml @@ -30,6 +30,8 @@ + + From e369218a1fe386b153530bf9af4744d88c58c0bc Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 12 Sep 2023 12:17:54 +0300 Subject: [PATCH 3/3] Fix rule node query performance --- .../server/dao/rule/RuleNodeDao.java | 2 +- .../server/dao/sql/rule/JpaRuleNodeDao.java | 4 +- .../dao/sql/rule/RuleNodeRepository.java | 13 +- .../dao/sql/rule/JpaRuleNodeDaoTest.java | 158 ++++++++++++++++++ 4 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java index 7df394ddd4..cd8fbb0a3c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java @@ -30,7 +30,7 @@ import java.util.List; */ public interface RuleNodeDao extends Dao { - List findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search); + List findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String configurationSearch); PageData findAllRuleNodesByType(String type, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java index 7adf8aa0c8..827b73a0c1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java @@ -56,8 +56,8 @@ public class JpaRuleNodeDao extends JpaAbstractDao imp } @Override - public List findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search) { - return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByTenantIdAndType(tenantId.getId(), type, search)); + public List findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String configurationSearch) { + return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByTenantIdAndType(tenantId.getId(), type, configurationSearch)); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java index e016462255..c8ecb8000b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java @@ -29,19 +29,22 @@ import java.util.UUID; public interface RuleNodeRepository extends JpaRepository { - @Query("SELECT r FROM RuleNodeEntity r WHERE r.ruleChainId in " + - "(select id from RuleChainEntity rc WHERE rc.tenantId = :tenantId) " + - "AND r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") + @Query(nativeQuery = true, value = "SELECT * FROM rule_node r WHERE r.rule_chain_id in " + + "(select id from rule_chain rc WHERE rc.tenant_id = :tenantId) AND r.type = :ruleType " + + " AND (:searchText IS NULL OR r.configuration ILIKE CONCAT('%', :searchText, '%'))") List findRuleNodesByTenantIdAndType(@Param("tenantId") UUID tenantId, @Param("ruleType") String ruleType, @Param("searchText") String searchText); - @Query("SELECT r FROM RuleNodeEntity r WHERE r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") + @Query(nativeQuery = true, value = "SELECT * FROM rule_node r WHERE r.type = :ruleType " + + " AND (:searchText IS NULL OR r.configuration ILIKE CONCAT('%', :searchText, '%'))") Page findAllRuleNodesByType(@Param("ruleType") String ruleType, @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT r FROM RuleNodeEntity r WHERE r.type = :ruleType AND r.configurationVersion < :version AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") + @Query(nativeQuery = true, value = "SELECT * FROM rule_node r WHERE r.type = :ruleType " + + " AND configuration_version < :version " + + " AND (:searchText IS NULL OR r.configuration ILIKE CONCAT('%', :searchText, '%'))") Page findAllRuleNodesByTypeAndVersionLessThan(@Param("ruleType") String ruleType, @Param("version") int version, @Param("searchText") String searchText, diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java new file mode 100644 index 0000000000..6e77927233 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDaoTest.java @@ -0,0 +1,158 @@ +/** + * Copyright © 2016-2023 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.sql.rule; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.google.common.util.concurrent.ListeningExecutorService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +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.PageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.AbstractJpaDaoTest; +import org.thingsboard.server.dao.rule.RuleChainDao; +import org.thingsboard.server.dao.rule.RuleNodeDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class JpaRuleNodeDaoTest extends AbstractJpaDaoTest { + + public static final int COUNT = 40; + public static final String PREFIX_FOR_RULE_NODE_NAME = "SEARCH_TEXT_"; + List ruleNodeIds; + TenantId tenantId1; + TenantId tenantId2; + RuleChainId ruleChainId1; + RuleChainId ruleChainId2; + + @Autowired + private RuleChainDao ruleChainDao; + + @Autowired + private RuleNodeDao ruleNodeDao; + + ListeningExecutorService executor; + + @Before + public void setUp() { + tenantId1 = TenantId.fromUUID(Uuids.timeBased()); + ruleChainId1 = new RuleChainId(UUID.randomUUID()); + tenantId2 = TenantId.fromUUID(Uuids.timeBased()); + ruleChainId2 = new RuleChainId(UUID.randomUUID()); + + ruleNodeIds = createRuleNodes(tenantId1, tenantId2, ruleChainId1, ruleChainId2, COUNT); + } + + @After + public void tearDown() throws Exception { + ruleNodeDao.removeAllByIds(ruleNodeIds); + if (executor != null) { + executor.shutdownNow(); + } + } + + @Test + public void testSaveRuleName0x00_thenSomeDatabaseException() { + RuleNode ruleNode = getRuleNode(ruleChainId1, "T", "\u0000"); + assertThatThrownBy(() -> ruleNodeIds.add(ruleNodeDao.save(tenantId1, ruleNode).getUuidId())); + } + + @Test + public void testFindRuleNodesByTenantIdAndType() { + List ruleNodes1 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId1, "A", PREFIX_FOR_RULE_NODE_NAME); + assertEquals(20, ruleNodes1.size()); + + List ruleNodes2 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId2, "B", PREFIX_FOR_RULE_NODE_NAME); + assertEquals(20, ruleNodes2.size()); + + ruleNodes1 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId1, "A", null); + assertEquals(20, ruleNodes1.size()); + + ruleNodes2 = ruleNodeDao.findRuleNodesByTenantIdAndType(tenantId2, "B", null); + assertEquals(20, ruleNodes2.size()); + } + + @Test + public void testFindRuleNodesByType() { + PageData ruleNodes = ruleNodeDao.findAllRuleNodesByType( "A", new PageLink(10, 0, PREFIX_FOR_RULE_NODE_NAME)); + assertEquals(20, ruleNodes.getTotalElements()); + assertEquals(2, ruleNodes.getTotalPages()); + assertEquals(10, ruleNodes.getData().size()); + + ruleNodes = ruleNodeDao.findAllRuleNodesByType( "A", new PageLink(10, 0)); + assertEquals(20, ruleNodes.getTotalElements()); + assertEquals(2, ruleNodes.getTotalPages()); + assertEquals(10, ruleNodes.getData().size()); + } + + @Test + public void testFindRuleNodesByTypeAndVersionLessThan() { + PageData ruleNodes = ruleNodeDao.findAllRuleNodesByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0, PREFIX_FOR_RULE_NODE_NAME)); + assertEquals(20, ruleNodes.getTotalElements()); + assertEquals(2, ruleNodes.getTotalPages()); + assertEquals(10, ruleNodes.getData().size()); + + ruleNodes = ruleNodeDao.findAllRuleNodesByTypeAndVersionLessThan( "A", 1, new PageLink(10, 0)); + assertEquals(20, ruleNodes.getTotalElements()); + assertEquals(2, ruleNodes.getTotalPages()); + assertEquals(10, ruleNodes.getData().size()); + } + + private List createRuleNodes(TenantId tenantId1, TenantId tenantId2, RuleChainId ruleChainId1, RuleChainId ruleChainId2, int count) { + var chain1 = new RuleChain(ruleChainId1); + chain1.setTenantId(tenantId1); + chain1.setName(ruleChainId1.toString()); + ruleChainDao.save(tenantId1, chain1); + var chain2 = new RuleChain(ruleChainId2); + chain2.setTenantId(tenantId2); + chain2.setName(ruleChainId2.toString()); + ruleChainDao.save(tenantId2, chain2); + List savedRuleNodeIds = new ArrayList<>(); + for (int i = 0; i < count / 2; i++) { + savedRuleNodeIds.add(ruleNodeDao.save(tenantId1, getRuleNode(ruleChainId1, "A", Integer.toString(i))).getUuidId()); + savedRuleNodeIds.add(ruleNodeDao.save(tenantId2, getRuleNode(ruleChainId2, "B", Integer.toString(i + count / 2))).getUuidId()); + } + return savedRuleNodeIds; + } + + private RuleNode getRuleNode(RuleChainId ruleChainId, String type, String nameSuffix) { + return getRuleNode(ruleChainId, Uuids.timeBased(), type, nameSuffix); + } + + private RuleNode getRuleNode(RuleChainId ruleChainId, UUID ruleNodeId, String type, String nameSuffix) { + RuleNode ruleNode = new RuleNode(); + ruleNode.setId(new RuleNodeId(ruleNodeId)); + ruleNode.setRuleChainId(ruleChainId); + ruleNode.setName(nameSuffix); + ruleNode.setType(type); + ruleNode.setConfiguration(JacksonUtil.newObjectNode().put("searchHint", PREFIX_FOR_RULE_NODE_NAME + nameSuffix)); + ruleNode.setConfigurationVersion(0); + return ruleNode; + } +}