diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
index 0c9eca5f0a..1a2984ef4f 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
@@ -194,7 +194,7 @@ public class TbHttpClient {
config.isIgnoreRequestBody()) {
entity = new HttpEntity<>(headers);
} else {
- entity = new HttpEntity<>(getData(msg), headers);
+ entity = new HttpEntity<>(getData(msg, config.isIgnoreRequestBody(), config.isParseToPlainText()), headers);
}
URI uri = buildEncodedUri(endpointUrl);
@@ -242,12 +242,19 @@ public class TbHttpClient {
return uri;
}
- private String getData(TbMsg msg) {
- String data = msg.getData();
+ private String getData(TbMsg tbMsg, boolean ignoreBody, boolean parseToPlainText) {
+ if (!ignoreBody && parseToPlainText) {
+ return parseJsonStringToPlainText(tbMsg.getData());
+ }
+ return tbMsg.getData();
+ }
- if (config.isTrimDoubleQuotes()) {
+ protected String parseJsonStringToPlainText(String data) {
+ if (data.startsWith("\"") && data.endsWith("\"") && data.length() >= 2) {
final String dataBefore = data;
- data = data.replaceAll("^\"|\"$", "");
+ try {
+ data = JacksonUtil.fromString(data, String.class);
+ } catch (Exception ignored) {}
log.trace("Trimming double quotes. Before trim: [{}], after trim: [{}]", dataBefore, data);
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
index 281f65e1e4..a37310331e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.rule.engine.rest;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
@@ -23,6 +25,7 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.external.TbAbstractExternalNode;
import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg;
@Slf4j
@@ -30,6 +33,7 @@ import org.thingsboard.server.common.msg.TbMsg;
type = ComponentType.EXTERNAL,
name = "rest api call",
configClazz = TbRestApiCallNodeConfiguration.class,
+ version = 1,
nodeDescription = "Invoke REST API calls to external REST server",
nodeDetails = "Will invoke REST API call GET | POST | PUT | DELETE to external REST server. " +
"Message payload added into Request body. Configured attributes can be added into Headers from Message Metadata." +
@@ -45,6 +49,8 @@ import org.thingsboard.server.common.msg.TbMsg;
)
public class TbRestApiCallNode extends TbAbstractExternalNode {
+ static final String PARSE_TO_PLAIN_TEXT = "parseToPlainText";
+ static final String TRIM_DOUBLE_QUOTES = "trimDoubleQuotes";
protected TbHttpClient httpClient;
@Override
@@ -72,4 +78,21 @@ public class TbRestApiCallNode extends TbAbstractExternalNode {
}
}
+ @Override
+ public TbPair upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
+ boolean hasChanges = false;
+ switch (fromVersion) {
+ case 0:
+ if (!oldConfiguration.has(PARSE_TO_PLAIN_TEXT) && oldConfiguration.has(TRIM_DOUBLE_QUOTES)) {
+ hasChanges = true;
+ ((ObjectNode) oldConfiguration).put(PARSE_TO_PLAIN_TEXT, oldConfiguration.get(TRIM_DOUBLE_QUOTES).booleanValue());
+ ((ObjectNode) oldConfiguration).remove(TRIM_DOUBLE_QUOTES);
+ }
+ break;
+ default:
+ break;
+ }
+ return new TbPair<>(hasChanges, oldConfiguration);
+ }
+
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
index 3fb28d474b..1ab4c8d0f8 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
@@ -37,7 +37,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration data = metaData.getData();
- Assertions.assertThat(data).hasSize(2);
- Assertions.assertThat(data.get("Content-Type")).isEqualTo("binary");
- Assertions.assertThat(data.get("Set-Cookie")).isEqualTo("[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]");
+ Assertions.assertEquals(2, data.size());
+ Assertions.assertEquals(data.get("Content-Type"), "binary");
+ Assertions.assertEquals(data.get("Set-Cookie"), "[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]");
}
-}
\ No newline at end of file
+ @ParameterizedTest
+ @ValueSource(strings = { "false", "\"", "\"\"", "\"This is a string with double quotes\"", "Path: /home/developer/test.txt",
+ "First line\nSecond line\n\nFourth line", "Before\rAfter", "Tab\tSeparated\tValues", "Test\bbackspace", "[]",
+ "[1, 2, 3]", "{\"key\": \"value\"}", "{\n\"temperature\": 25.5,\n\"humidity\": 50.2\n\"}", "Expression: (a + b) * c",
+ "世界", "Україна", "\u1F1FA\u1F1E6", "🇺🇦"})
+ public void testParseJsonStringToPlainText(String original) {
+ Mockito.when(client.parseJsonStringToPlainText(anyString())).thenCallRealMethod();
+
+ String serialized = JacksonUtil.toString(original);
+ Assertions.assertNotNull(serialized);
+ Assertions.assertEquals(original, client.parseJsonStringToPlainText(serialized));
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java
index 705f298513..0a71a799fc 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java
@@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.rest;
import com.datastax.oss.driver.api.core.uuid.Uuids;
+import com.fasterxml.jackson.databind.JsonNode;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -26,6 +27,7 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.junit.After;
import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -39,6 +41,7 @@ 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.msg.TbMsgType;
+import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -91,7 +94,9 @@ public class TbRestApiCallNodeTest {
@After
public void teardown() {
- server.stop();
+ if (server != null) {
+ server.stop();
+ }
}
@Test
@@ -211,4 +216,20 @@ public class TbRestApiCallNodeTest {
assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue());
}
+ @Test
+ public void givenOldConfig_whenUpgrade_thenShouldReturnTrueResultWithNewConfig() throws Exception {
+ var defaultConfig = new TbRestApiCallNodeConfiguration().defaultConfiguration();
+ var node = new TbRestApiCallNode();
+ String oldConfig = "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\":\"POST\"," +
+ "\"useSimpleClientHttpFactory\":false,\"ignoreRequestBody\":false,\"enableProxy\":false," +
+ "\"useSystemProxyProperties\":false,\"proxyScheme\":null,\"proxyHost\":null,\"proxyPort\":0," +
+ "\"proxyUser\":null,\"proxyPassword\":null,\"readTimeoutMs\":0,\"maxParallelRequestsCount\":0," +
+ "\"headers\":{\"Content-Type\":\"application/json\"},\"useRedisQueueForMsgPersistence\":false," +
+ "\"trimQueue\":null,\"maxQueueSize\":null,\"credentials\":{\"type\":\"anonymous\"},\"trimDoubleQuotes\":true}";
+ JsonNode configJson = JacksonUtil.toJsonNode(oldConfig);
+ TbPair upgrade = node.upgrade(0, configJson);
+ Assertions.assertTrue(upgrade.getFirst());
+ Assertions.assertTrue(JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass()).isParseToPlainText());
+ }
+
}