Browse Source

Merge pull request #9534 from AndriiLandiak/fix/rest-api-node-trim-quotes

Fix trim double quotes for RestApiCallNode
pull/9583/head
Andrew Shvayka 3 years ago
committed by GitHub
parent
commit
d2d32c5add
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  2. 23
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
  3. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
  4. 45
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java
  5. 23
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java

17
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -194,7 +194,7 @@ public class TbHttpClient {
config.isIgnoreRequestBody()) { config.isIgnoreRequestBody()) {
entity = new HttpEntity<>(headers); entity = new HttpEntity<>(headers);
} else { } else {
entity = new HttpEntity<>(getData(msg), headers); entity = new HttpEntity<>(getData(msg, config.isIgnoreRequestBody(), config.isParseToPlainText()), headers);
} }
URI uri = buildEncodedUri(endpointUrl); URI uri = buildEncodedUri(endpointUrl);
@ -242,12 +242,19 @@ public class TbHttpClient {
return uri; return uri;
} }
private String getData(TbMsg msg) { private String getData(TbMsg tbMsg, boolean ignoreBody, boolean parseToPlainText) {
String data = msg.getData(); 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; 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); log.trace("Trimming double quotes. Before trim: [{}], after trim: [{}]", dataBefore, data);
} }

23
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; 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 lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; 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.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.external.TbAbstractExternalNode; import org.thingsboard.rule.engine.external.TbAbstractExternalNode;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@Slf4j @Slf4j
@ -30,6 +33,7 @@ import org.thingsboard.server.common.msg.TbMsg;
type = ComponentType.EXTERNAL, type = ComponentType.EXTERNAL,
name = "rest api call", name = "rest api call",
configClazz = TbRestApiCallNodeConfiguration.class, configClazz = TbRestApiCallNodeConfiguration.class,
version = 1,
nodeDescription = "Invoke REST API calls to external REST server", nodeDescription = "Invoke REST API calls to external REST server",
nodeDetails = "Will invoke REST API call <code>GET | POST | PUT | DELETE</code> to external REST server. " + nodeDetails = "Will invoke REST API call <code>GET | POST | PUT | DELETE</code> to external REST server. " +
"Message payload added into Request body. Configured attributes can be added into Headers from Message Metadata." + "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 { public class TbRestApiCallNode extends TbAbstractExternalNode {
static final String PARSE_TO_PLAIN_TEXT = "parseToPlainText";
static final String TRIM_DOUBLE_QUOTES = "trimDoubleQuotes";
protected TbHttpClient httpClient; protected TbHttpClient httpClient;
@Override @Override
@ -72,4 +78,21 @@ public class TbRestApiCallNode extends TbAbstractExternalNode {
} }
} }
@Override
public TbPair<Boolean, JsonNode> 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);
}
} }

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java

@ -37,7 +37,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
private int readTimeoutMs; private int readTimeoutMs;
private int maxParallelRequestsCount; private int maxParallelRequestsCount;
private boolean useRedisQueueForMsgPersistence; private boolean useRedisQueueForMsgPersistence;
private boolean trimDoubleQuotes; private boolean parseToPlainText;
private boolean enableProxy; private boolean enableProxy;
private boolean useSystemProxyProperties; private boolean useSystemProxyProperties;
private String proxyHost; private String proxyHost;
@ -58,7 +58,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
configuration.setReadTimeoutMs(0); configuration.setReadTimeoutMs(0);
configuration.setMaxParallelRequestsCount(0); configuration.setMaxParallelRequestsCount(0);
configuration.setUseRedisQueueForMsgPersistence(false); configuration.setUseRedisQueueForMsgPersistence(false);
configuration.setTrimDoubleQuotes(false); configuration.setParseToPlainText(false);
configuration.setEnableProxy(false); configuration.setEnableProxy(false);
configuration.setCredentials(new AnonymousCredentials()); configuration.setCredentials(new AnonymousCredentials());
configuration.setIgnoreRequestBody(false); configuration.setIgnoreRequestBody(false);

45
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java

@ -18,17 +18,19 @@ package org.thingsboard.rule.engine.rest;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.junit.After; import org.junit.jupiter.api.AfterEach;
import org.junit.Assert; import org.junit.jupiter.api.Assertions;
import org.junit.Before; import org.junit.jupiter.api.BeforeEach;
import org.junit.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockserver.integration.ClientAndServer; import org.mockserver.integration.ClientAndServer;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.AsyncRestTemplate;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -46,6 +48,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -61,13 +64,13 @@ public class TbHttpClientTest {
EventLoopGroup eventLoop; EventLoopGroup eventLoop;
TbHttpClient client; TbHttpClient client;
@Before @BeforeEach
public void setUp() throws Exception { public void setUp() throws Exception {
client = mock(TbHttpClient.class); client = mock(TbHttpClient.class);
willCallRealMethod().given(client).getSharedOrCreateEventLoopGroup(any()); when(client.getSharedOrCreateEventLoopGroup(any())).thenCallRealMethod();
} }
@After @AfterEach
public void tearDown() throws Exception { public void tearDown() throws Exception {
if (eventLoop != null) { if (eventLoop != null) {
eventLoop.shutdownGracefully(); eventLoop.shutdownGracefully();
@ -91,7 +94,7 @@ public class TbHttpClientTest {
Mockito.when(client.buildEncodedUri(any())).thenCallRealMethod(); Mockito.when(client.buildEncodedUri(any())).thenCallRealMethod();
String url = "http://localhost:8080/"; String url = "http://localhost:8080/";
URI uri = client.buildEncodedUri(url); URI uri = client.buildEncodedUri(url);
Assert.assertEquals(url, uri.toString()); Assertions.assertEquals(url, uri.toString());
} }
@Test @Test
@ -114,7 +117,7 @@ public class TbHttpClientTest {
String url = "http://192.168.1.1/data?d={\"a\": 12}"; String url = "http://192.168.1.1/data?d={\"a\": 12}";
String expected = "http://192.168.1.1/data?d=%7B%22a%22:%2012%7D"; String expected = "http://192.168.1.1/data?d=%7B%22a%22:%2012%7D";
URI uri = client.buildEncodedUri(url); URI uri = client.buildEncodedUri(url);
Assert.assertEquals(expected, uri.toString()); Assertions.assertEquals(expected, uri.toString());
} }
@Test @Test
@ -183,7 +186,7 @@ public class TbHttpClientTest {
verify(ctx, times(1)).tellSuccess(any()); verify(ctx, times(1)).tellSuccess(any());
verify(ctx, times(0)).tellFailure(any(), any()); verify(ctx, times(0)).tellFailure(any(), any());
Assert.assertEquals(successResponseBody, capturedData.getValue()); Assertions.assertEquals(successResponseBody, capturedData.getValue());
} }
private ClientAndServer setUpDummyServer(String host, String path, String paramKey, String paramVal, String successResponseBody) { private ClientAndServer setUpDummyServer(String host, String path, String paramKey, String paramVal, String successResponseBody) {
@ -219,9 +222,21 @@ public class TbHttpClientTest {
Map<String, String> data = metaData.getData(); Map<String, String> data = metaData.getData();
Assertions.assertThat(data).hasSize(2); Assertions.assertEquals(2, data.size());
Assertions.assertThat(data.get("Content-Type")).isEqualTo("binary"); Assertions.assertEquals(data.get("Content-Type"), "binary");
Assertions.assertThat(data.get("Set-Cookie")).isEqualTo("[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]"); Assertions.assertEquals(data.get("Set-Cookie"), "[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]");
} }
} @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));
}
}

23
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; package org.thingsboard.rule.engine.rest;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.http.HttpException; import org.apache.http.HttpException;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -26,6 +27,7 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler; import org.apache.http.protocol.HttpRequestHandler;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; 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.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.msg.TbMsgType; 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.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -91,7 +94,9 @@ public class TbRestApiCallNodeTest {
@After @After
public void teardown() { public void teardown() {
server.stop(); if (server != null) {
server.stop();
}
} }
@Test @Test
@ -211,4 +216,20 @@ public class TbRestApiCallNodeTest {
assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); 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<Boolean, JsonNode> upgrade = node.upgrade(0, configJson);
Assertions.assertTrue(upgrade.getFirst());
Assertions.assertTrue(JacksonUtil.treeToValue(upgrade.getSecond(), defaultConfig.getClass()).isParseToPlainText());
}
} }

Loading…
Cancel
Save