From 40c957aabbc79808fa6114922b174b4e24da7a6c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 15 Sep 2023 17:54:47 +0300 Subject: [PATCH] init commit for transformation nodes && improved error handling on init for fetchTo based nodes --- .../metadata/TbAbstractNodeWithFetchTo.java | 6 +- ...bAbstractTransformNodeWithTbMsgSource.java | 57 ++++++++++++ .../transform/TbChangeOriginatorNode.java | 10 ++- .../rule/engine/transform/TbCopyKeysNode.java | 89 +++++++++++-------- .../TbCopyKeysNodeConfiguration.java | 5 +- .../engine/transform/TbDeleteKeysNode.java | 88 ++++++++++-------- .../TbDeleteKeysNodeConfiguration.java | 5 +- .../rule/engine/transform/TbJsonPathNode.java | 8 +- .../engine/transform/TbRenameKeysNode.java | 83 ++++++++++------- .../TbRenameKeysNodeConfiguration.java | 7 +- .../engine/transform/TbSplitArrayMsgNode.java | 9 +- .../engine/transform/TbTransformMsgNode.java | 3 +- .../TbGetCustomerAttributeNodeTest.java | 3 +- .../TbGetCustomerDetailsNodeTest.java | 3 +- .../TbGetOriginatorFieldsNodeTest.java | 3 +- .../TbGetRelatedAttributeNodeTest.java | 2 +- .../TbGetTenantAttributeNodeTest.java | 3 +- .../metadata/TbGetTenantDetailsNodeTest.java | 3 +- .../engine/transform/TbCopyKeysNodeTest.java | 25 +++++- .../transform/TbDeleteKeysNodeTest.java | 25 +++++- .../transform/TbRenameKeysNodeTest.java | 25 +++++- 21 files changed, 315 insertions(+), 147 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNodeWithTbMsgSource.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java index a3b7fead3f..ef149aa4ad 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractNodeWithFetchTo.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.Arrays; import java.util.NoSuchElementException; @Slf4j @@ -46,10 +47,9 @@ public abstract class TbAbstractNodeWithFetchTo upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException { + return fromVersion == 0 ? + upgradeToUseTbMsgSource((ObjectNode) oldConfiguration, getKeyToUpgradeFromVersionZero()) : + new TbPair<>(false, oldConfiguration); + } + + private TbPair upgradeToUseTbMsgSource(ObjectNode configToUpdate, String newProperty) throws TbNodeException { + if (!configToUpdate.has(FROM_METADATA_PROPERTY)) { + throw new TbNodeException("property to update: '" + FROM_METADATA_PROPERTY + "' doesn't exists in configuration!"); + } + var value = configToUpdate.get(FROM_METADATA_PROPERTY).asText(); + if ("true".equals(value)) { + configToUpdate.remove(FROM_METADATA_PROPERTY); + configToUpdate.put(newProperty, TbMsgSource.METADATA.name()); + return new TbPair<>(true, configToUpdate); + } + if ("false".equals(value)) { + configToUpdate.remove(FROM_METADATA_PROPERTY); + configToUpdate.put(newProperty, TbMsgSource.DATA.name()); + return new TbPair<>(true, configToUpdate); + } + throw new TbNodeException("property to update: '" + FROM_METADATA_PROPERTY + "' has unexpected value: " + + value + ". Allowed values: true or false!"); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 4d1a092097..9efc051e2b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -43,10 +43,14 @@ import java.util.NoSuchElementException; type = ComponentType.TRANSFORMATION, name = "change originator", configClazz = TbChangeOriginatorNodeConfiguration.class, - nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity/Alarm Originator", + nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity/Alarm Originator/Entity by name pattern", nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + - "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded.
" + - "Alarm Originator found only in case original Originator is Alarm entity.", + "If multiple related entities are found, only first entity is used as new originator, other entities are discarded.
" + + "Alarm Originator might be found only if the original Originator is Alarm entity.
" + + "Entity by name pattern lookup only if found only in case original Originator is Alarm entity.
" + + "Lookup of an entity by name pattern requires selection of entity type and name pattern to be specified in the configuration. " + + "Allowed entity types to select: 'DEVICE', 'ASSET', 'ENTITY_VIEW', 'EDGE' and 'USER'.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeChangeOriginatorConfig", icon = "find_replace" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java index 8d9a7102d0..93b5ecb5d7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNode.java @@ -21,87 +21,98 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; +import java.util.stream.Collectors; @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION, - name = "copy keys", + name = "copy key-values", + version = 1, configClazz = TbCopyKeysNodeConfiguration.class, - nodeDescription = "Copies the msg or metadata keys with specified key names selected in the list", - nodeDetails = "Will fetch fields values specified in list. If specified field is not part of msg or metadata fields it will be ignored." + - "Returns transformed messages via Success chain", + nodeDescription = "Copies key-values from message to message metadata or vice-versa.", + nodeDetails = "Fetches key-values from message or message metadata based on the keys list specified in the configuration " + + "and copies them into message metadata or message. Keys that are absent in the fetch source will be ignored. " + + "Use regular expression(s) as a key(s) to copy keys by pattern.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeCopyKeysConfig", icon = "content_copy" ) -public class TbCopyKeysNode implements TbNode { +public class TbCopyKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbCopyKeysNodeConfiguration config; private List patternKeys; - private boolean fromMetadata; + private TbMsgSource copyFrom; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbCopyKeysNodeConfiguration.class); - this.fromMetadata = config.isFromMetadata(); - this.patternKeys = new ArrayList<>(); - config.getKeys().forEach(key -> { - this.patternKeys.add(Pattern.compile(key)); - }); + this.copyFrom = config.getCopyFrom(); + if (copyFrom == null) { + throw new TbNodeException("CopyFrom can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); + } + this.patternKeys = config.getKeys().stream().map(Pattern::compile).collect(Collectors.toList()); } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - TbMsgMetaData metaData = msg.getMetaData(); + var metaDataCopy = msg.getMetaData().copy(); String msgData = msg.getData(); boolean msgChanged = false; JsonNode dataNode = JacksonUtil.toJsonNode(msgData); if (dataNode.isObject()) { - if (fromMetadata) { - ObjectNode msgDataNode = (ObjectNode) dataNode; - Map metaDataMap = metaData.getData(); - for (Map.Entry entry : metaDataMap.entrySet()) { - String keyData = entry.getKey(); - if (checkKey(keyData)) { - msgChanged = true; - msgDataNode.put(keyData, entry.getValue()); + switch (copyFrom) { + case METADATA: + ObjectNode msgDataNode = (ObjectNode) dataNode; + Map metaDataMap = metaDataCopy.getData(); + for (Map.Entry entry : metaDataMap.entrySet()) { + String keyData = entry.getKey(); + if (checkKey(keyData)) { + msgChanged = true; + msgDataNode.put(keyData, entry.getValue()); + } } - } - msgData = JacksonUtil.toString(msgDataNode); - } else { - Iterator> iteratorNode = dataNode.fields(); - while (iteratorNode.hasNext()) { - Map.Entry entry = iteratorNode.next(); - String keyData = entry.getKey(); - if (checkKey(keyData)) { - msgChanged = true; - metaData.putValue(keyData, JacksonUtil.toString(entry.getValue())); + msgData = JacksonUtil.toString(msgDataNode); + break; + case DATA: + Iterator> iteratorNode = dataNode.fields(); + while (iteratorNode.hasNext()) { + Map.Entry entry = iteratorNode.next(); + String keyData = entry.getKey(); + if (checkKey(keyData)) { + msgChanged = true; + metaDataCopy.putValue(keyData, JacksonUtil.toString(entry.getValue())); + } } - } + break; + default: + log.debug("Unexpected CopyFrom value: {}. Allowed values: {}", copyFrom, TbMsgSource.values()); + break; } } - if (msgChanged) { - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, msgData)); - } else { - ctx.tellSuccess(msg); - } + ctx.tellSuccess(msgChanged ? TbMsg.transformMsg(msg, metaDataCopy, msgData) : msg); + } + + @Override + protected String getKeyToUpgradeFromVersionZero() { + return "copyFrom"; } boolean checkKey(String key) { return patternKeys.stream().anyMatch(pattern -> pattern.matcher(key).matches()); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java index 6bf9f1e343..5c8d7edf4b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; import java.util.Set; @@ -24,14 +25,14 @@ import java.util.Set; @Data public class TbCopyKeysNodeConfiguration implements NodeConfiguration { - private boolean fromMetadata; + private TbMsgSource copyFrom; private Set keys; @Override public TbCopyKeysNodeConfiguration defaultConfiguration() { TbCopyKeysNodeConfiguration configuration = new TbCopyKeysNodeConfiguration(); configuration.setKeys(Collections.emptySet()); - configuration.setFromMetadata(false); + configuration.setCopyFrom(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java index 9ea31bc56d..4666016f78 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNode.java @@ -21,84 +21,96 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; +import java.util.stream.Collectors; @Slf4j @RuleNode( type = ComponentType.TRANSFORMATION, - name = "delete keys", + name = "delete key-values", + version = 1, configClazz = TbDeleteKeysNodeConfiguration.class, - nodeDescription = "Removes keys from the msg data or metadata with the specified key names selected in the list", - nodeDetails = "Will fetch fields (regex) values specified in list. If specified field (regex) is not part of msg " + - "or metadata fields it will be ignored. Returns transformed messages via Success chain", + nodeDescription = "Removes key-values from message or metadata.", + nodeDetails = "Removes key-values from message or message metadata based on the keys list specified in the configuration. " + + "Use regular expression(s) as a key(s) to remove keys by pattern.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeDeleteKeysConfig", icon = "remove_circle" ) -public class TbDeleteKeysNode implements TbNode { +public class TbDeleteKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbDeleteKeysNodeConfiguration config; private List patternKeys; - private boolean fromMetadata; + private TbMsgSource deleteFrom; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbDeleteKeysNodeConfiguration.class); - this.fromMetadata = config.isFromMetadata(); - this.patternKeys = new ArrayList<>(); - config.getKeys().forEach(key -> { - this.patternKeys.add(Pattern.compile(key)); - }); + this.deleteFrom = config.getDeleteFrom(); + if (deleteFrom == null) { + throw new TbNodeException("DeleteFrom can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); + } + this.patternKeys = config.getKeys().stream().map(Pattern::compile).collect(Collectors.toList()); } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - TbMsgMetaData metaData = msg.getMetaData(); + TbMsgMetaData metaDataCopy = msg.getMetaData().copy(); String msgData = msg.getData(); List keysToDelete = new ArrayList<>(); - if (fromMetadata) { - Map metaDataMap = metaData.getData(); - metaDataMap.forEach((keyMetaData, valueMetaData) -> { - if (checkKey(keyMetaData)) { - keysToDelete.add(keyMetaData); - } - }); - keysToDelete.forEach(metaDataMap::remove); - metaData = new TbMsgMetaData(metaDataMap); - } else { - JsonNode dataNode = JacksonUtil.toJsonNode(msgData); - if (dataNode.isObject()) { - ObjectNode msgDataObject = (ObjectNode) dataNode; - dataNode.fields().forEachRemaining(entry -> { - String keyData = entry.getKey(); - if (checkKey(keyData)) { - keysToDelete.add(keyData); + switch (deleteFrom) { + case METADATA: + Map metaDataMap = metaDataCopy.getData(); + metaDataMap.forEach((keyMetaData, valueMetaData) -> { + if (checkKey(keyMetaData)) { + keysToDelete.add(keyMetaData); } }); - msgDataObject.remove(keysToDelete); - msgData = JacksonUtil.toString(msgDataObject); - } - } - if (keysToDelete.isEmpty()) { - ctx.tellSuccess(msg); - } else { - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, msgData)); + keysToDelete.forEach(metaDataMap::remove); + metaDataCopy = new TbMsgMetaData(metaDataMap); + break; + case DATA: + JsonNode dataNode = JacksonUtil.toJsonNode(msgData); + if (dataNode.isObject()) { + ObjectNode msgDataObject = (ObjectNode) dataNode; + dataNode.fields().forEachRemaining(entry -> { + String keyData = entry.getKey(); + if (checkKey(keyData)) { + keysToDelete.add(keyData); + } + }); + msgDataObject.remove(keysToDelete); + msgData = JacksonUtil.toString(msgDataObject); + } + break; + default: + log.debug("Unexpected DeleteFrom value: {}. Allowed values: {}", deleteFrom, TbMsgSource.values()); + break; } + ctx.tellSuccess(keysToDelete.isEmpty() ? msg : TbMsg.transformMsg(msg, metaDataCopy, msgData)); + } + + @Override + protected String getKeyToUpgradeFromVersionZero() { + return "deleteFrom"; } boolean checkKey(String key) { return patternKeys.stream().anyMatch(pattern -> pattern.matcher(key).matches()); } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java index d0b22df409..d68b391df6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeConfiguration.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Collections; import java.util.Set; @@ -24,14 +25,14 @@ import java.util.Set; @Data public class TbDeleteKeysNodeConfiguration implements NodeConfiguration { - private boolean fromMetadata; + private TbMsgSource deleteFrom; private Set keys; @Override public TbDeleteKeysNodeConfiguration defaultConfiguration() { TbDeleteKeysNodeConfiguration configuration = new TbDeleteKeysNodeConfiguration(); configuration.setKeys(Collections.emptySet()); - configuration.setFromMetadata(false); + configuration.setDeleteFrom(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index 0e85a50c34..9454bd51c4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -38,10 +38,10 @@ import java.util.concurrent.ExecutionException; name = "json path", configClazz = TbJsonPathNodeConfiguration.class, nodeDescription = "Transforms incoming message body using JSONPath expression.", - nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure.
" - + "'$' represents the root object or array.
" - + "If JSONPath expression evaluation failed, incoming message routes via Failure chain, " - + "otherwise Success chain is used.", + nodeDetails = "JSONPath expression specifies a path to an element or a set of elements in a JSON structure.
" + + "'$' represents the root object or array.
" + + "If JSONPath expression evaluation failed, incoming message routes via Failure chain.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "functions", configDirective = "tbTransformationNodeJsonPathConfig" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java index 88e6dc1a8b..d6e4e0dc34 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNode.java @@ -21,14 +21,15 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -36,62 +37,78 @@ import java.util.concurrent.ExecutionException; @RuleNode( type = ComponentType.TRANSFORMATION, name = "rename keys", + version = 1, configClazz = TbRenameKeysNodeConfiguration.class, - nodeDescription = "Renames msg data or metadata keys to the new key names selected in the key mapping.", - nodeDetails = "If the key that is selected in the key mapping is missed in the selected msg source(data or metadata), it will be ignored." + - " Returns transformed messages via Success chain", + nodeDescription = "Renames message or message metadata key names to the new key names selected in the key mapping.", + nodeDetails = "If the key name that is selected in the key mapping is missed in the " + + "selected source(message or message metadata), it will be ignored.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeRenameKeysConfig", icon = "find_replace" ) -public class TbRenameKeysNode implements TbNode { +public class TbRenameKeysNode extends TbAbstractTransformNodeWithTbMsgSource { private TbRenameKeysNodeConfiguration config; private Map renameKeysMapping; - private boolean fromMetadata; + private TbMsgSource renameIn; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbRenameKeysNodeConfiguration.class); + this.renameIn = config.getRenameIn(); this.renameKeysMapping = config.getRenameKeysMapping(); - this.fromMetadata = config.isFromMetadata(); + if (renameIn == null) { + throw new TbNodeException("RenameIn can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); + } + if (renameKeysMapping == null || renameKeysMapping.isEmpty()) { + throw new TbNodeException("At least one mapping entry should be specified!"); + } } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - TbMsgMetaData metaData = msg.getMetaData(); + TbMsgMetaData metaDataCopy = msg.getMetaData().copy(); String data = msg.getData(); boolean msgChanged = false; - if (fromMetadata) { - Map metaDataMap = metaData.getData(); - for (Map.Entry entry : renameKeysMapping.entrySet()) { - String nameKey = entry.getKey(); - if (metaDataMap.containsKey(nameKey)) { - msgChanged = true; - metaDataMap.put(entry.getValue(), metaDataMap.get(nameKey)); - metaDataMap.remove(nameKey); - } - } - metaData = new TbMsgMetaData(metaDataMap); - } else { - JsonNode dataNode = JacksonUtil.toJsonNode(data); - if (dataNode.isObject()) { - ObjectNode msgData = (ObjectNode) dataNode; + switch (renameIn) { + case METADATA: + Map metaDataMap = metaDataCopy.getData(); for (Map.Entry entry : renameKeysMapping.entrySet()) { String nameKey = entry.getKey(); - if (msgData.has(nameKey)) { + if (metaDataMap.containsKey(nameKey)) { msgChanged = true; - msgData.set(entry.getValue(), msgData.get(nameKey)); - msgData.remove(nameKey); + metaDataMap.put(entry.getValue(), metaDataMap.get(nameKey)); + metaDataMap.remove(nameKey); } } - data = JacksonUtil.toString(msgData); - } - } - if (msgChanged) { - ctx.tellSuccess(TbMsg.transformMsg(msg, metaData, data)); - } else { - ctx.tellSuccess(msg); + metaDataCopy = new TbMsgMetaData(metaDataMap); + break; + case DATA: + JsonNode dataNode = JacksonUtil.toJsonNode(data); + if (dataNode.isObject()) { + ObjectNode msgData = (ObjectNode) dataNode; + for (Map.Entry entry : renameKeysMapping.entrySet()) { + String nameKey = entry.getKey(); + if (msgData.has(nameKey)) { + msgChanged = true; + msgData.set(entry.getValue(), msgData.get(nameKey)); + msgData.remove(nameKey); + } + } + data = JacksonUtil.toString(msgData); + } + break; + default: + log.debug("Unexpected RenameIn value: {}. Allowed values: {}", renameIn, TbMsgSource.values()); + break; } + ctx.tellSuccess(msgChanged ? TbMsg.transformMsg(msg, metaDataCopy, data) : msg); } + + @Override + protected String getKeyToUpgradeFromVersionZero() { + return "renameIn"; + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java index 0475ba4ca4..1e6d5e1483 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeConfiguration.java @@ -17,20 +17,21 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.util.TbMsgSource; import java.util.Map; @Data public class TbRenameKeysNodeConfiguration implements NodeConfiguration { - private boolean fromMetadata; + private TbMsgSource renameIn; private Map renameKeysMapping; @Override public TbRenameKeysNodeConfiguration defaultConfiguration() { TbRenameKeysNodeConfiguration configuration = new TbRenameKeysNodeConfiguration(); - configuration.setRenameKeysMapping(Map.of("temp", "temperature")); - configuration.setFromMetadata(false); + configuration.setRenameKeysMapping(Map.of("temperatureCelsius", "temperature")); + configuration.setRenameIn(TbMsgSource.DATA); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java index 8959d12c9f..7a3e53c69c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbSplitArrayMsgNode.java @@ -25,8 +25,8 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; @@ -39,10 +39,11 @@ import java.util.concurrent.ExecutionException; type = ComponentType.TRANSFORMATION, name = "split array msg", configClazz = EmptyNodeConfiguration.class, - nodeDescription = "Split array message into several msgs", - nodeDetails = "Split the array fetched from the msg body. If the msg data is not a JSON array returns the " + nodeDescription = "Split array message into several messages", + nodeDetails = "Split the array fetched from the message. If the msg data is not a JSON array returns the " + "incoming message as outbound message with Failure chain, otherwise returns " - + "inner objects of the extracted array as separate messages via Success chain.", + + "inner objects of the extracted array as separate messages via Success chain.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, icon = "content_copy", configDirective = "tbNodeEmptyConfig" diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index ba78970c54..0cff412d39 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -39,7 +39,8 @@ import java.util.List; "msgType - is a Message type.
" + "Should return the following structure:
" + "{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }

" + - "All fields in resulting object are optional and will be taken from original message if not specified.", + "All fields in resulting object are optional and will be taken from original message if not specified.

" + + "Output connections: Success, Failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeScriptConfig" ) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 291614464a..d200ec70d8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -59,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -119,7 +120,7 @@ public class TbGetCustomerAttributeNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java index 51625eafe7..16db712ef9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNodeTest.java @@ -57,6 +57,7 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.user.UserService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; @@ -127,7 +128,7 @@ public class TbGetCustomerDetailsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java index d681deb9c0..ded7400ea5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsNodeTest.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.device.DeviceService; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.UUID; @@ -86,7 +87,7 @@ public class TbGetOriginatorFieldsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java index dcf5c22ea5..b236c3097c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNodeTest.java @@ -127,7 +127,7 @@ public class TbGetRelatedAttributeNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java index 87b34cffe0..f0c0e76419 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNodeTest.java @@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -99,7 +100,7 @@ public class TbGetTenantAttributeNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java index bfbaa9a92e..93da628de0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTenantDetailsNodeTest.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.tenant.TenantService; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -96,7 +97,7 @@ public class TbGetTenantDetailsNodeTest { var exception = assertThrows(TbNodeException.class, () -> node.init(ctxMock, nodeConfiguration)); // THEN - assertThat(exception.getMessage()).isEqualTo("FetchTo cannot be null!"); + assertThat(exception.getMessage()).isEqualTo("FetchTo option can't be null! Allowed values: " + Arrays.toString(TbMsgSource.values())); verify(ctxMock, never()).tellSuccess(any()); } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java index e0ab34e35b..c44c3b5a24 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbCopyKeysNodeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.transform; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -37,6 +40,8 @@ import java.util.Set; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -59,7 +64,7 @@ public class TbCopyKeysNodeTest { ctx = mock(TbContext.class); config = new TbCopyKeysNodeConfiguration().defaultConfiguration(); config.setKeys(Set.of("TestKey_1", "TestKey_2", "TestKey_3", "(\\w*)Data(\\w*)")); - config.setFromMetadata(true); + config.setCopyFrom(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node = spy(new TbCopyKeysNode()); node.init(ctx, nodeConfiguration); @@ -74,7 +79,7 @@ public class TbCopyKeysNodeTest { void givenDefaultConfig_whenVerify_thenOK() { TbCopyKeysNodeConfiguration defaultConfig = new TbCopyKeysNodeConfiguration().defaultConfiguration(); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptySet()); - assertThat(defaultConfig.isFromMetadata()).isEqualTo(false); + assertThat(defaultConfig.getCopyFrom()).isEqualTo(TbMsgSource.DATA); } @Test @@ -95,7 +100,7 @@ public class TbCopyKeysNodeTest { @Test void givenMsgFromMsg_whenOnMsg_thenVerifyOutput() throws Exception { - config.setFromMetadata(false); + config.setCopyFrom(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -149,6 +154,20 @@ public class TbCopyKeysNodeTest { assertThat(newMsg).isSameAs(msg); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbCopyKeysNodeConfiguration().defaultConfiguration(); + var oldConfigJson = (ObjectNode) JacksonUtil.valueToTree(config); + oldConfigJson.remove("copyFrom"); + oldConfigJson.put("fromMetadata", "false"); + // WHEN + TbPair upgrade = node.upgrade(0, oldConfigJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + private TbMsg getTbMsg(EntityId entityId, String data) { final Map mdMap = Map.of( "TestKey_1", "Test", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java index 6eee16e7d3..042babb1b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbDeleteKeysNodeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.transform; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -37,6 +40,8 @@ import java.util.Set; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -59,7 +64,7 @@ public class TbDeleteKeysNodeTest { ctx = mock(TbContext.class); config = new TbDeleteKeysNodeConfiguration().defaultConfiguration(); config.setKeys(Set.of("TestKey_1", "TestKey_2", "TestKey_3", "(\\w*)Data(\\w*)")); - config.setFromMetadata(true); + config.setDeleteFrom(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node = spy(new TbDeleteKeysNode()); node.init(ctx, nodeConfiguration); @@ -74,7 +79,7 @@ public class TbDeleteKeysNodeTest { void givenDefaultConfig_whenVerify_thenOK() { TbDeleteKeysNodeConfiguration defaultConfig = new TbDeleteKeysNodeConfiguration().defaultConfiguration(); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptySet()); - assertThat(defaultConfig.isFromMetadata()).isEqualTo(false); + assertThat(defaultConfig.getDeleteFrom()).isEqualTo(TbMsgSource.DATA); } @Test @@ -95,7 +100,7 @@ public class TbDeleteKeysNodeTest { @Test void givenMsgFromMsg_whenOnMsg_thenVerifyOutput() throws Exception { - config.setFromMetadata(false); + config.setDeleteFrom(TbMsgSource.DATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -133,6 +138,20 @@ public class TbDeleteKeysNodeTest { assertThat(newMsg.getData()).isEqualTo(data); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbDeleteKeysNodeConfiguration().defaultConfiguration(); + var oldConfigJson = (ObjectNode) JacksonUtil.valueToTree(config); + oldConfigJson.remove("deleteFrom"); + oldConfigJson.put("fromMetadata", "false"); + // WHEN + TbPair upgrade = node.upgrade(0, oldConfigJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + private TbMsg getTbMsg(EntityId entityId, String data) { final Map mdMap = Map.of( "TestKey_1", "Test", diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java index c602aab5df..ae911ea529 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbRenameKeysNodeTest.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.transform; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,9 +25,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.util.TbMsgSource; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.msg.TbMsgType; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbMsgCallback; @@ -35,6 +38,8 @@ import java.util.Map; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -70,8 +75,8 @@ public class TbRenameKeysNodeTest { @Test void givenDefaultConfig_whenVerify_thenOK() { TbRenameKeysNodeConfiguration defaultConfig = new TbRenameKeysNodeConfiguration().defaultConfiguration(); - assertThat(defaultConfig.getRenameKeysMapping()).isEqualTo(Map.of("temp", "temperature")); - assertThat(defaultConfig.isFromMetadata()).isEqualTo(false); + assertThat(defaultConfig.getRenameKeysMapping()).isEqualTo(Map.of("temperatureCelsius", "temperature")); + assertThat(defaultConfig.getRenameIn()).isEqualTo(TbMsgSource.DATA); } @Test @@ -95,7 +100,7 @@ public class TbRenameKeysNodeTest { void givenMetadata_whenOnMsg_thenVerifyOutput() throws Exception { config = new TbRenameKeysNodeConfiguration().defaultConfiguration(); config.setRenameKeysMapping(Map.of("TestKey_1", "Attribute_1", "TestKey_2", "Attribute_2")); - config.setFromMetadata(true); + config.setRenameIn(TbMsgSource.METADATA); nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); node.init(ctx, nodeConfiguration); @@ -148,6 +153,20 @@ public class TbRenameKeysNodeTest { assertThat(newMsg).isSameAs(msg); } + @Test + void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception { + // GIVEN + var config = new TbRenameKeysNodeConfiguration().defaultConfiguration(); + var oldConfigJson = (ObjectNode) JacksonUtil.valueToTree(config); + oldConfigJson.remove("renameIn"); + oldConfigJson.put("fromMetadata", "false"); + // WHEN + TbPair upgrade = node.upgrade(0, oldConfigJson); + // THEN + assertTrue(upgrade.getFirst()); + assertEquals(config, JacksonUtil.treeToValue(upgrade.getSecond(), config.getClass())); + } + private TbMsg getTbMsg(EntityId entityId, String data) { final Map mdMap = Map.of( "TestKey_1", "Test",