Commit 024dd99cb70d663c483460b11e818a6da64279aa

Authored by Igor Kulikov
1 parent dc41e630

Refactor JS Executor.

Showing 22 changed files with 207 additions and 165 deletions
@@ -321,7 +321,7 @@ public class ActorSystemContext { @@ -321,7 +321,7 @@ public class ActorSystemContext {
321 .put("msgId", tbMsg.getId().toString()) 321 .put("msgId", tbMsg.getId().toString())
322 .put("msgType", tbMsg.getType()) 322 .put("msgType", tbMsg.getType())
323 .put("dataType", tbMsg.getDataType().name()) 323 .put("dataType", tbMsg.getDataType().name())
324 - .put("data", convertToString(tbMsg.getDataType(), tbMsg.getData())) 324 + .put("data", tbMsg.getData())
325 .put("metadata", metadata); 325 .put("metadata", metadata);
326 326
327 if (error != null) { 327 if (error != null) {
@@ -335,21 +335,6 @@ public class ActorSystemContext { @@ -335,21 +335,6 @@ public class ActorSystemContext {
335 } 335 }
336 } 336 }
337 337
338 - private String convertToString(TbMsgDataType messageType, byte[] data) {  
339 - if (data == null) {  
340 - return null;  
341 - }  
342 - switch (messageType) {  
343 - case JSON:  
344 - case TEXT:  
345 - return new String(data, StandardCharsets.UTF_8);  
346 - case BINARY:  
347 - return Base64Utils.encodeToString(data);  
348 - default:  
349 - throw new RuntimeException("Message type: " + messageType + " is not supported!");  
350 - }  
351 - }  
352 -  
353 public static Exception toException(Throwable error) { 338 public static Exception toException(Throwable error) {
354 return Exception.class.isInstance(error) ? (Exception) error : new Exception(error); 339 return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
355 } 340 }
@@ -149,7 +149,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule @@ -149,7 +149,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
149 "CUSTOM", 149 "CUSTOM",
150 device.getId(), 150 device.getId(),
151 new TbMsgMetaData(), 151 new TbMsgMetaData(),
152 - new byte[]{}); 152 + "{}");
153 actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); 153 actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
154 154
155 Thread.sleep(3000); 155 Thread.sleep(3000);
@@ -135,7 +135,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac @@ -135,7 +135,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
135 "CUSTOM", 135 "CUSTOM",
136 device.getId(), 136 device.getId(),
137 new TbMsgMetaData(), 137 new TbMsgMetaData(),
138 - new byte[]{}); 138 + "{}");
139 actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); 139 actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
140 140
141 Thread.sleep(3000); 141 Thread.sleep(3000);
@@ -39,9 +39,9 @@ public final class TbMsg implements Serializable { @@ -39,9 +39,9 @@ public final class TbMsg implements Serializable {
39 private final EntityId originator; 39 private final EntityId originator;
40 private final TbMsgMetaData metaData; 40 private final TbMsgMetaData metaData;
41 private final TbMsgDataType dataType; 41 private final TbMsgDataType dataType;
42 - private final byte[] data; 42 + private final String data;
43 43
44 - public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, byte[] data) { 44 + public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data) {
45 this.id = id; 45 this.id = id;
46 this.type = type; 46 this.type = type;
47 this.originator = originator; 47 this.originator = originator;
@@ -64,7 +64,7 @@ public final class TbMsg implements Serializable { @@ -64,7 +64,7 @@ public final class TbMsg implements Serializable {
64 } 64 }
65 65
66 builder.setDataType(msg.getDataType().ordinal()); 66 builder.setDataType(msg.getDataType().ordinal());
67 - builder.setData(ByteString.copyFrom(msg.getData())); 67 + builder.setData(msg.getData());
68 byte[] bytes = builder.build().toByteArray(); 68 byte[] bytes = builder.build().toByteArray();
69 return ByteBuffer.wrap(bytes); 69 return ByteBuffer.wrap(bytes);
70 } 70 }
@@ -75,16 +75,13 @@ public final class TbMsg implements Serializable { @@ -75,16 +75,13 @@ public final class TbMsg implements Serializable {
75 TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap()); 75 TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap());
76 EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); 76 EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId());
77 TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; 77 TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
78 - return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData().toByteArray()); 78 + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData());
79 } catch (InvalidProtocolBufferException e) { 79 } catch (InvalidProtocolBufferException e) {
80 throw new IllegalStateException("Could not parse protobuf for TbMsg", e); 80 throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
81 } 81 }
82 } 82 }
83 83
84 public TbMsg copy() { 84 public TbMsg copy() {
85 - int dataSize = data.length;  
86 - byte[] dataCopy = new byte[dataSize];  
87 - System.arraycopy( data, 0, dataCopy, 0, data.length );  
88 - return new TbMsg(id, type, originator, metaData.copy(), dataType, dataCopy); 85 + return new TbMsg(id, type, originator, metaData.copy(), dataType, data);
89 } 86 }
90 } 87 }
@@ -33,7 +33,7 @@ public final class TbMsgMetaData implements Serializable { @@ -33,7 +33,7 @@ public final class TbMsgMetaData implements Serializable {
33 33
34 private Map<String, String> data = new ConcurrentHashMap<>(); 34 private Map<String, String> data = new ConcurrentHashMap<>();
35 35
36 - TbMsgMetaData(Map<String, String> data) { 36 + public TbMsgMetaData(Map<String, String> data) {
37 this.data = data; 37 this.data = data;
38 } 38 }
39 39
@@ -32,5 +32,5 @@ message TbMsgProto { @@ -32,5 +32,5 @@ message TbMsgProto {
32 TbMsgMetaDataProto metaData = 5; 32 TbMsgMetaDataProto metaData = 5;
33 33
34 int32 dataType = 6; 34 int32 dataType = 6;
35 - bytes data = 7; 35 + string data = 7;
36 } 36 }
@@ -126,7 +126,7 @@ public class QueueBenchmark implements CommandLineRunner { @@ -126,7 +126,7 @@ public class QueueBenchmark implements CommandLineRunner {
126 TbMsgMetaData metaData = new TbMsgMetaData(); 126 TbMsgMetaData metaData = new TbMsgMetaData();
127 metaData.putValue("key", "value"); 127 metaData.putValue("key", "value");
128 String dataStr = "someContent"; 128 String dataStr = "someContent";
129 - return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr.getBytes()); 129 + return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr);
130 } 130 }
131 131
132 @Bean 132 @Bean
@@ -45,7 +45,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @@ -45,7 +45,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
45 45
46 @Test 46 @Test
47 public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException { 47 public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException {
48 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, new byte[4]); 48 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000");
49 UUID nodeId = UUIDs.timeBased(); 49 UUID nodeId = UUIDs.timeBased();
50 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); 50 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L);
51 future.get(); 51 future.get();
@@ -55,7 +55,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @@ -55,7 +55,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
55 55
56 @Test 56 @Test
57 public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException { 57 public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException {
58 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, new byte[4]); 58 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000");
59 UUID nodeId = UUIDs.timeBased(); 59 UUID nodeId = UUIDs.timeBased();
60 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 2L, 2L, 2L); 60 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 2L, 2L, 2L);
61 future.get(); 61 future.get();
@@ -68,7 +68,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @@ -68,7 +68,7 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest {
68 TbMsgMetaData metaData = new TbMsgMetaData(); 68 TbMsgMetaData metaData = new TbMsgMetaData();
69 metaData.putValue("key", "value"); 69 metaData.putValue("key", "value");
70 String dataStr = "someContent"; 70 String dataStr = "someContent";
71 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr.getBytes()); 71 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr);
72 UUID nodeId = UUIDs.timeBased(); 72 UUID nodeId = UUIDs.timeBased();
73 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); 73 ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L);
74 future.get(); 74 future.get();
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.rule.engine.debug; 16 package org.thingsboard.rule.engine.debug;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
  19 +import com.google.common.util.concurrent.ListenableFuture;
19 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.util.StringUtils; 21 import org.springframework.util.StringUtils;
21 import org.thingsboard.rule.engine.TbNodeUtils; 22 import org.thingsboard.rule.engine.TbNodeUtils;
@@ -58,9 +59,11 @@ public class TbMsgGeneratorNode implements TbNode { @@ -58,9 +59,11 @@ public class TbMsgGeneratorNode implements TbNode {
58 public static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg"; 59 public static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
59 60
60 private TbMsgGeneratorNodeConfiguration config; 61 private TbMsgGeneratorNodeConfiguration config;
  62 + private NashornJsEngine jsEngine;
61 private long delay; 63 private long delay;
62 private EntityId originatorId; 64 private EntityId originatorId;
63 private UUID nextTickId; 65 private UUID nextTickId;
  66 + private TbMsg prevMsg;
64 67
65 @Override 68 @Override
66 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { 69 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
@@ -71,29 +74,41 @@ public class TbMsgGeneratorNode implements TbNode { @@ -71,29 +74,41 @@ public class TbMsgGeneratorNode implements TbNode {
71 } else { 74 } else {
72 originatorId = ctx.getSelfId(); 75 originatorId = ctx.getSelfId();
73 } 76 }
  77 + this.jsEngine = new NashornJsEngine(config.getJsScript(), "Generate", "prevMsg", "prevMetadata", "prevMsgType");
74 sentTickMsg(ctx); 78 sentTickMsg(ctx);
75 } 79 }
76 80
77 @Override 81 @Override
78 public void onMsg(TbContext ctx, TbMsg msg) { 82 public void onMsg(TbContext ctx, TbMsg msg) {
79 if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { 83 if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) {
80 - TbMsgMetaData metaData = new TbMsgMetaData();  
81 - if (config.getMsgMetaData() != null) {  
82 - config.getMsgMetaData().forEach(metaData::putValue);  
83 - }  
84 - ctx.tellNext(new TbMsg(UUIDs.timeBased(), config.getMsgType(), originatorId, metaData, config.getMsgBody().getBytes(StandardCharsets.UTF_8)));  
85 - sentTickMsg(ctx); 84 + withCallback(generate(ctx),
  85 + m -> {ctx.tellNext(m); sentTickMsg(ctx);},
  86 + t -> {ctx.tellError(msg, t); sentTickMsg(ctx);});
86 } 87 }
87 } 88 }
88 89
89 private void sentTickMsg(TbContext ctx) { 90 private void sentTickMsg(TbContext ctx) {
90 - TbMsg tickMsg = new TbMsg(UUIDs.timeBased(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), new byte[]{}); 91 + TbMsg tickMsg = new TbMsg(UUIDs.timeBased(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
91 nextTickId = tickMsg.getId(); 92 nextTickId = tickMsg.getId();
92 ctx.tellSelf(tickMsg, delay); 93 ctx.tellSelf(tickMsg, delay);
93 } 94 }
94 95
  96 + protected ListenableFuture<TbMsg> generate(TbContext ctx) {
  97 + return ctx.getJsExecutor().executeAsync(() -> {
  98 + if (prevMsg == null) {
  99 + prevMsg = new TbMsg(UUIDs.timeBased(), "", originatorId, new TbMsgMetaData(), "{}");
  100 + }
  101 + TbMsg generated = jsEngine.executeGenerate(prevMsg);
  102 + prevMsg = new TbMsg(UUIDs.timeBased(), generated.getType(), originatorId, generated.getMetaData(), generated.getData());
  103 + return prevMsg;
  104 + });
  105 + }
95 106
96 @Override 107 @Override
97 public void destroy() { 108 public void destroy() {
  109 + prevMsg = null;
  110 + if (jsEngine != null) {
  111 + jsEngine.destroy();
  112 + }
98 } 113 }
99 } 114 }
@@ -28,17 +28,17 @@ public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgG @@ -28,17 +28,17 @@ public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgG
28 private int periodInSeconds; 28 private int periodInSeconds;
29 private String originatorId; 29 private String originatorId;
30 private EntityType originatorType; 30 private EntityType originatorType;
31 - private String msgType;  
32 - private String msgBody;  
33 - private Map<String, String> msgMetaData; 31 + private String jsScript;
34 32
35 @Override 33 @Override
36 public TbMsgGeneratorNodeConfiguration defaultConfiguration() { 34 public TbMsgGeneratorNodeConfiguration defaultConfiguration() {
37 TbMsgGeneratorNodeConfiguration configuration = new TbMsgGeneratorNodeConfiguration(); 35 TbMsgGeneratorNodeConfiguration configuration = new TbMsgGeneratorNodeConfiguration();
38 configuration.setMsgCount(0); 36 configuration.setMsgCount(0);
39 configuration.setPeriodInSeconds(1); 37 configuration.setPeriodInSeconds(1);
40 - configuration.setMsgType("DebugMsg");  
41 - configuration.setMsgBody("{}"); 38 + configuration.setJsScript("var msg = { temp: 42, humidity: 77 };\n" +
  39 + "var metadata = { data: 40 };\n" +
  40 + "var msgType = \"DebugMsg\";\n\n" +
  41 + "return { msg: msg, metadata: metadata, msgType: msgType };");
42 return configuration; 42 return configuration;
43 } 43 }
44 } 44 }
@@ -35,7 +35,8 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -35,7 +35,8 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
35 nodeDetails = "Evaluate incoming Message with configured JS condition. " + 35 nodeDetails = "Evaluate incoming Message with configured JS condition. " +
36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." + 36 "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." +
37 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" + 37 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" +
38 - "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>", 38 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>" +
  39 + "Message type can be accessed via <code>msgType</code> property.",
39 uiResources = {"static/rulenode/rulenode-core-config.js"}, 40 uiResources = {"static/rulenode/rulenode-core-config.js"},
40 configDirective = "tbFilterNodeScriptConfig") 41 configDirective = "tbFilterNodeScriptConfig")
41 42
@@ -53,15 +54,11 @@ public class TbJsFilterNode implements TbNode { @@ -53,15 +54,11 @@ public class TbJsFilterNode implements TbNode {
53 @Override 54 @Override
54 public void onMsg(TbContext ctx, TbMsg msg) { 55 public void onMsg(TbContext ctx, TbMsg msg) {
55 ListeningExecutor jsExecutor = ctx.getJsExecutor(); 56 ListeningExecutor jsExecutor = ctx.getJsExecutor();
56 - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(toBindings(msg))), 57 + withCallback(jsExecutor.executeAsync(() -> jsEngine.executeFilter(msg)),
57 filterResult -> ctx.tellNext(msg, Boolean.toString(filterResult)), 58 filterResult -> ctx.tellNext(msg, Boolean.toString(filterResult)),
58 t -> ctx.tellError(msg, t)); 59 t -> ctx.tellError(msg, t));
59 } 60 }
60 61
61 - private Bindings toBindings(TbMsg msg) {  
62 - return NashornJsEngine.bindMsg(msg);  
63 - }  
64 -  
65 @Override 62 @Override
66 public void destroy() { 63 public void destroy() {
67 if (jsEngine != null) { 64 if (jsEngine != null) {
@@ -36,7 +36,8 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -36,7 +36,8 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
36 nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + 36 nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " +
37 "If Array is empty - message not routed to next Node. " + 37 "If Array is empty - message not routed to next Node. " +
38 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code> " + 38 "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code> " +
39 - "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>", 39 + "Message metadata can be accessed via <code>metadata</code> property. For example <code>metadata.customerName === 'John';</code>" +
  40 + "Message type can be accessed via <code>msgType</code> property.",
40 uiResources = {"static/rulenode/rulenode-core-config.js"}, 41 uiResources = {"static/rulenode/rulenode-core-config.js"},
41 configDirective = "tbFilterNodeSwitchConfig") 42 configDirective = "tbFilterNodeSwitchConfig")
42 public class TbJsSwitchNode implements TbNode { 43 public class TbJsSwitchNode implements TbNode {
@@ -53,7 +54,7 @@ public class TbJsSwitchNode implements TbNode { @@ -53,7 +54,7 @@ public class TbJsSwitchNode implements TbNode {
53 @Override 54 @Override
54 public void onMsg(TbContext ctx, TbMsg msg) { 55 public void onMsg(TbContext ctx, TbMsg msg) {
55 ListeningExecutor jsExecutor = ctx.getJsExecutor(); 56 ListeningExecutor jsExecutor = ctx.getJsExecutor();
56 - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(toBindings(msg))), 57 + withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(msg)),
57 result -> processSwitch(ctx, msg, result), 58 result -> processSwitch(ctx, msg, result),
58 t -> ctx.tellError(msg, t)); 59 t -> ctx.tellError(msg, t));
59 } 60 }
@@ -62,11 +63,7 @@ public class TbJsSwitchNode implements TbNode { @@ -62,11 +63,7 @@ public class TbJsSwitchNode implements TbNode {
62 ctx.tellNext(msg, nextRelations); 63 ctx.tellNext(msg, nextRelations);
63 } 64 }
64 65
65 - private Bindings toBindings(TbMsg msg) {  
66 - return NashornJsEngine.bindMsg(msg);  
67 - }  
68 -  
69 - @Override 66 + @Override
70 public void destroy() { 67 public void destroy() {
71 if (jsEngine != null) { 68 if (jsEngine != null) {
72 jsEngine.destroy(); 69 jsEngine.destroy();
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 package org.thingsboard.rule.engine.js; 16 package org.thingsboard.rule.engine.js;
17 17
18 import com.fasterxml.jackson.core.JsonProcessingException; 18 import com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.core.type.TypeReference;
19 import com.fasterxml.jackson.databind.JsonNode; 20 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.ObjectMapper; 21 import com.fasterxml.jackson.databind.ObjectMapper;
21 import com.google.common.collect.Sets; 22 import com.google.common.collect.Sets;
@@ -23,9 +24,13 @@ import jdk.nashorn.api.scripting.NashornScriptEngineFactory; @@ -23,9 +24,13 @@ import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
23 import jdk.nashorn.api.scripting.ScriptObjectMirror; 24 import jdk.nashorn.api.scripting.ScriptObjectMirror;
24 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
25 import org.apache.commons.lang3.ArrayUtils; 26 import org.apache.commons.lang3.ArrayUtils;
  27 +import org.apache.commons.lang3.StringUtils;
26 import org.thingsboard.server.common.msg.TbMsg; 28 import org.thingsboard.server.common.msg.TbMsg;
  29 +import org.thingsboard.server.common.msg.TbMsgMetaData;
27 30
28 import javax.script.*; 31 import javax.script.*;
  32 +import java.io.IOException;
  33 +import java.nio.charset.StandardCharsets;
29 import java.util.Collections; 34 import java.util.Collections;
30 import java.util.Map; 35 import java.util.Map;
31 import java.util.Set; 36 import java.util.Set;
@@ -34,112 +39,158 @@ import java.util.Set; @@ -34,112 +39,158 @@ import java.util.Set;
34 @Slf4j 39 @Slf4j
35 public class NashornJsEngine { 40 public class NashornJsEngine {
36 41
  42 +
  43 + public static final String MSG = "msg";
37 public static final String METADATA = "metadata"; 44 public static final String METADATA = "metadata";
38 - public static final String DATA = "msg"; 45 + public static final String MSG_TYPE = "msgType";
39 46
40 - private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msg, metadata) { ";  
41 - private static final String JS_WRAPPER_SUFFIX_TEMPLATE = "}\n %s(msg, metadata);"; 47 + private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msgStr, metadataStr, msgType) { " +
  48 + " var msg = JSON.parse(msgStr); " +
  49 + " var metadata = JSON.parse(metadataStr); " +
  50 + " return JSON.stringify(%s(msg, metadata, msgType));" +
  51 + " function %s(%s, %s, %s) {";
  52 + private static final String JS_WRAPPER_SUFFIX = "}" +
  53 + "\n}";
42 54
  55 + private static final ObjectMapper mapper = new ObjectMapper();
43 private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); 56 private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
44 -  
45 - private CompiledScript engine;  
46 -  
47 - public NashornJsEngine(String script, String functionName) {  
48 - String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, functionName);  
49 - String jsWrapperSuffix = String.format(JS_WRAPPER_SUFFIX_TEMPLATE, functionName);  
50 - engine = compileScript(jsWrapperPrefix + script + jsWrapperSuffix); 57 + private static ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});
  58 +
  59 + private final String invokeFunctionName;
  60 +
  61 + public NashornJsEngine(String script, String functionName, String... argNames) {
  62 + this.invokeFunctionName = "invokeInternal" + this.hashCode();
  63 + String msgArg;
  64 + String metadataArg;
  65 + String msgTypeArg;
  66 + if (argNames != null && argNames.length == 3) {
  67 + msgArg = argNames[0];
  68 + metadataArg = argNames[1];
  69 + msgTypeArg = argNames[2];
  70 + } else {
  71 + msgArg = MSG;
  72 + metadataArg = METADATA;
  73 + msgTypeArg = MSG_TYPE;
  74 + }
  75 + String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, this.invokeFunctionName,
  76 + functionName, functionName, msgArg, metadataArg, msgTypeArg);
  77 + compileScript(jsWrapperPrefix + script + JS_WRAPPER_SUFFIX);
51 } 78 }
52 79
53 - private static CompiledScript compileScript(String script) {  
54 - ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});  
55 - Compilable compEngine = (Compilable) engine; 80 + private static void compileScript(String script) {
56 try { 81 try {
57 - return compEngine.compile(script); 82 + engine.eval(script);
58 } catch (ScriptException e) { 83 } catch (ScriptException e) {
59 log.warn("Failed to compile JS script: {}", e.getMessage(), e); 84 log.warn("Failed to compile JS script: {}", e.getMessage(), e);
60 throw new IllegalArgumentException("Can't compile script: " + e.getMessage()); 85 throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
61 } 86 }
62 } 87 }
63 88
64 - public static Bindings bindMsg(TbMsg msg) { 89 + private static String[] prepareArgs(TbMsg msg) {
65 try { 90 try {
66 - Bindings bindings = new SimpleBindings();  
67 - if (ArrayUtils.isNotEmpty(msg.getData())) {  
68 - ObjectMapper mapper = new ObjectMapper();  
69 - JsonNode jsonNode = mapper.readTree(msg.getData());  
70 - Map map = mapper.treeToValue(jsonNode, Map.class);  
71 - bindings.put(DATA, map); 91 + String[] args = new String[3];
  92 + if (msg.getData() != null) {
  93 + args[0] = msg.getData();
72 } else { 94 } else {
73 - bindings.put(DATA, Collections.emptyMap()); 95 + args[0] = "";
74 } 96 }
75 - bindings.put(METADATA, msg.getMetaData().getData());  
76 - return bindings; 97 + args[1] = mapper.writeValueAsString(msg.getMetaData().getData());
  98 + args[2] = msg.getType();
  99 + return args;
77 } catch (Throwable th) { 100 } catch (Throwable th) {
78 throw new IllegalArgumentException("Cannot bind js args", th); 101 throw new IllegalArgumentException("Cannot bind js args", th);
79 } 102 }
80 } 103 }
81 104
82 - private static TbMsg unbindMsg(Bindings bindings, TbMsg msg) throws JsonProcessingException {  
83 - for (Map.Entry<String, String> entry : msg.getMetaData().getData().entrySet()) {  
84 - Object obj = entry.getValue();  
85 - entry.setValue(obj.toString()); 105 + private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) {
  106 + try {
  107 + String data = null;
  108 + Map<String, String> metadata = null;
  109 + String messageType = null;
  110 + if (msgData.has(MSG)) {
  111 + JsonNode msgPayload = msgData.get(MSG);
  112 + data = mapper.writeValueAsString(msgPayload);
  113 + }
  114 + if (msgData.has(METADATA)) {
  115 + JsonNode msgMetadata = msgData.get(METADATA);
  116 + metadata = mapper.convertValue(msgMetadata, new TypeReference<Map<String, String>>() {
  117 + });
  118 + }
  119 + if (msgData.has(MSG_TYPE)) {
  120 + messageType = msgData.get(MSG_TYPE).asText();
  121 + }
  122 + String newData = data != null ? data : msg.getData();
  123 + TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData();
  124 + String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
  125 + return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData);
  126 + } catch (Throwable th) {
  127 + th.printStackTrace();
  128 + throw new RuntimeException("Failed to unbind message data from javascript result", th);
86 } 129 }
  130 + }
87 131
88 - Object payload = bindings.get(DATA);  
89 - if (payload != null) {  
90 - ObjectMapper mapper = new ObjectMapper();  
91 - byte[] bytes = mapper.writeValueAsBytes(payload);  
92 - return new TbMsg(msg.getId(), msg.getType(), msg.getOriginator(), msg.getMetaData(), bytes); 132 + public TbMsg executeUpdate(TbMsg msg) throws ScriptException {
  133 + JsonNode result = executeScript(msg);
  134 + if (!result.isObject()) {
  135 + log.warn("Wrong result type: {}", result.getNodeType());
  136 + throw new ScriptException("Wrong result type: " + result.getNodeType());
93 } 137 }
94 -  
95 - return msg; 138 + return unbindMsg(result, msg);
96 } 139 }
97 140
98 - public TbMsg executeUpdate(Bindings bindings, TbMsg msg) throws ScriptException {  
99 - try {  
100 - engine.eval(bindings);  
101 - return unbindMsg(bindings, msg);  
102 - } catch (Throwable th) {  
103 - th.printStackTrace();  
104 - throw new IllegalArgumentException("Cannot unbind js args", th); 141 + public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException {
  142 + JsonNode result = executeScript(prevMsg);
  143 + if (!result.isObject()) {
  144 + log.warn("Wrong result type: {}", result.getNodeType());
  145 + throw new ScriptException("Wrong result type: " + result.getNodeType());
105 } 146 }
  147 + return unbindMsg(result, prevMsg);
106 } 148 }
107 149
108 - public boolean executeFilter(Bindings bindings) throws ScriptException {  
109 - Object eval = engine.eval(bindings);  
110 - if (eval instanceof Boolean) {  
111 - return (boolean) eval;  
112 - } else {  
113 - log.warn("Wrong result type: {}", eval);  
114 - throw new ScriptException("Wrong result type: " + eval); 150 + public boolean executeFilter(TbMsg msg) throws ScriptException {
  151 + JsonNode result = executeScript(msg);
  152 + if (!result.isBoolean()) {
  153 + log.warn("Wrong result type: {}", result.getNodeType());
  154 + throw new ScriptException("Wrong result type: " + result.getNodeType());
115 } 155 }
  156 + return result.asBoolean();
116 } 157 }
117 158
118 - public Set<String> executeSwitch(Bindings bindings) throws ScriptException, NoSuchMethodException {  
119 - Object eval = this.engine.eval(bindings);  
120 - if (eval instanceof String) {  
121 - return Collections.singleton((String) eval);  
122 - } else if (eval instanceof ScriptObjectMirror) {  
123 - ScriptObjectMirror mir = (ScriptObjectMirror) eval;  
124 - if (mir.isArray()) {  
125 - Set<String> nextStates = Sets.newHashSet();  
126 - for (Map.Entry<String, Object> entry : mir.entrySet()) {  
127 - if (entry.getValue() instanceof String) {  
128 - nextStates.add((String) entry.getValue());  
129 - } else {  
130 - log.warn("Wrong result type: {}", eval);  
131 - throw new ScriptException("Wrong result type: " + eval);  
132 - } 159 + public Set<String> executeSwitch(TbMsg msg) throws ScriptException, NoSuchMethodException {
  160 + JsonNode result = executeScript(msg);
  161 + if (result.isTextual()) {
  162 + return Collections.singleton(result.asText());
  163 + } else if (result.isArray()) {
  164 + Set<String> nextStates = Sets.newHashSet();
  165 + for (JsonNode val : result) {
  166 + if (!val.isTextual()) {
  167 + log.warn("Wrong result type: {}", val.getNodeType());
  168 + throw new ScriptException("Wrong result type: " + val.getNodeType());
  169 + } else {
  170 + nextStates.add(val.asText());
133 } 171 }
134 - return nextStates;  
135 } 172 }
  173 + return nextStates;
  174 + } else {
  175 + log.warn("Wrong result type: {}", result.getNodeType());
  176 + throw new ScriptException("Wrong result type: " + result.getNodeType());
136 } 177 }
  178 + }
137 179
138 - log.warn("Wrong result type: {}", eval);  
139 - throw new ScriptException("Wrong result type: " + eval); 180 + private JsonNode executeScript(TbMsg msg) throws ScriptException {
  181 + try {
  182 + String[] inArgs = prepareArgs(msg);
  183 + String eval = ((Invocable)engine).invokeFunction(this.invokeFunctionName, inArgs[0], inArgs[1], inArgs[2]).toString();
  184 + return mapper.readTree(eval);
  185 + } catch (ScriptException | IllegalArgumentException th) {
  186 + throw th;
  187 + } catch (Throwable th) {
  188 + th.printStackTrace();
  189 + throw new RuntimeException("Failed to execute js script", th);
  190 + }
140 } 191 }
141 192
142 public void destroy() { 193 public void destroy() {
143 - engine = null; 194 + //engine = null;
144 } 195 }
145 } 196 }
@@ -64,7 +64,7 @@ public class TbMsgTelemetryNode implements TbNode { @@ -64,7 +64,7 @@ public class TbMsgTelemetryNode implements TbNode {
64 return; 64 return;
65 } 65 }
66 66
67 - String src = new String(msg.getData(), StandardCharsets.UTF_8); 67 + String src = msg.getData();
68 TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src)); 68 TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src));
69 Map<Long, List<KvEntry>> tsKvMap = telemetryUploadRequest.getData(); 69 Map<Long, List<KvEntry>> tsKvMap = telemetryUploadRequest.getData();
70 if (tsKvMap == null) { 70 if (tsKvMap == null) {
@@ -28,10 +28,14 @@ import javax.script.Bindings; @@ -28,10 +28,14 @@ import javax.script.Bindings;
28 type = ComponentType.TRANSFORMATION, 28 type = ComponentType.TRANSFORMATION,
29 name = "script", 29 name = "script",
30 configClazz = TbTransformMsgNodeConfiguration.class, 30 configClazz = TbTransformMsgNodeConfiguration.class,
31 - nodeDescription = "Change Message payload and Metadata using JavaScript",  
32 - nodeDetails = "JavaScript function recieve 2 input parameters that can be changed inside.<br/> " + 31 + nodeDescription = "Change Message payload, Metadata or Message type using JavaScript",
  32 + nodeDetails = "JavaScript function receive 3 input parameters.<br/> " +
33 "<code>metadata</code> - is a Message metadata.<br/>" + 33 "<code>metadata</code> - is a Message metadata.<br/>" +
34 - "<code>msg</code> - is a Message payload.<br/>Any properties can be changed/removed/added in those objects.", 34 + "<code>msg</code> - is a Message payload.<br/>" +
  35 + "<code>msgType</code> - is a Message type.<br/>" +
  36 + "Should return the following structure:<br/>" +
  37 + "<code>{ msg: <new payload>, metadata: <new metadata>, msgType: <new msgType> }</code>" +
  38 + "All fields in resulting object are optional and will be taken from original message if not specified.",
35 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, 39 uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
36 configDirective = "tbTransformationNodeScriptConfig") 40 configDirective = "tbTransformationNodeScriptConfig")
37 public class TbTransformMsgNode extends TbAbstractTransformNode { 41 public class TbTransformMsgNode extends TbAbstractTransformNode {
@@ -48,11 +52,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode { @@ -48,11 +52,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
48 52
49 @Override 53 @Override
50 protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) { 54 protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
51 - return ctx.getJsExecutor().executeAsync(() -> jsEngine.executeUpdate(toBindings(msg), msg));  
52 - }  
53 -  
54 - private Bindings toBindings(TbMsg msg) {  
55 - return NashornJsEngine.bindMsg(msg); 55 + return ctx.getJsExecutor().executeAsync(() -> jsEngine.executeUpdate(msg));
56 } 56 }
57 57
58 @Override 58 @Override
@@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio @@ -27,7 +27,7 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
27 public TbTransformMsgNodeConfiguration defaultConfiguration() { 27 public TbTransformMsgNodeConfiguration defaultConfiguration() {
28 TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); 28 TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
29 configuration.setStartNewChain(false); 29 configuration.setStartNewChain(false);
30 - configuration.setJsScript("return msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine' "); 30 + configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};");
31 return configuration; 31 return configuration;
32 } 32 }
33 } 33 }
1 -!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(34)},function(e,t){},1,1,function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div flex layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select flex the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <md-input-container class=md-block> <label translate>tb.rulenode.message-type</label> <input ng-required=true name=messageType ng-model=configuration.msgType> <div ng-messages=generatorConfigForm.messageType.$error> <div ng-message=required translate>tb.rulenode.message-type-required</div> </div> </md-input-container> <tb-json-content class=tb-message-body ng-model=configuration.msgBody label=\"{{ 'tb.rulenode.message-body' | translate }}\" content-type=types.contentType.JSON.value validate-content=false fill-height=true> </tb-json-content> <tb-json-object-edit class=tb-metadata-json ng-model=configuration.msgMetaData label=\"{{ 'tb.rulenode.message-metadata' | translate }}\" fill-height=true> </tb-json-object-edit> </section> "},function(e,t){e.exports=" <section ng-form name=telemetryConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=telemetryConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},6,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div flex layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> <md-checkbox aria-label="{{ \'tb.rulenode.clone-message\' | translate }}" ng-model=configuration.startNewChain>{{ \'tb.rulenode.clone-message\' | translate }} </md-checkbox> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata'] }}\" no-validate=true> </tb-js-func> <md-checkbox aria-label=\"{{ 'tb.rulenode.clone-message' | translate }}\" ng-model=configuration.startNewChain>{{ 'tb.rulenode.clone-message' | translate }} </md-checkbox> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.originator=null,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue,n.configuration.originatorId&&n.configuration.originatorType?n.originator={id:n.configuration.originatorId,entityType:n.configuration.originatorType}:n.originator=null,n.$watch("originator",function(e,t){angular.equals(e,t)||(n.originator?(i.$viewValue.originatorId=n.originator.id,i.$viewValue.originatorType=n.originator.entityType):(i.$viewValue.originatorId=null,i.$viewValue.originatorType=null))},!0)},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(4),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(19),i=a(r),o=n(17),l=a(o);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTelemetryConfig",i.default).directive("tbActionNodeGeneratorConfig",l.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(5),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(22),i=a(r),o=n(23),l=a(o),s=n(20),u=a(s),d=n(24),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(27),i=a(r),o=n(26),l=a(o),s=n(28),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(10),o=a(i)},[38,11],function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(31),i=a(r),o=n(33),l=a(o);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).name},[38,16],function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(37),i=a(r),o=n(25),l=a(o),s=n(21),u=a(s),d=n(32),c=a(d),m=n(18),g=a(m),f=n(30),p=a(f),b=n(29),y=a(b),v=n(36),h=a(v);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbRelationsQueryConfig",p.default).directive("tbKvMapConfig",y.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){(0,o.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(35),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name},function(e,t,n,a){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,r){var i=l.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var o=n(a),l=r(o)}])); 1 +!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(34)},function(e,t){},1,1,function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div flex layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select flex the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section ng-form name=telemetryConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=telemetryConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},6,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div flex layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> <md-checkbox aria-label="{{ \'tb.rulenode.clone-message\' | translate }}" ng-model=configuration.startNewChain>{{ \'tb.rulenode.clone-message\' | translate }} </md-checkbox> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <md-checkbox aria-label=\"{{ 'tb.rulenode.clone-message' | translate }}\" ng-model=configuration.startNewChain>{{ 'tb.rulenode.clone-message' | translate }} </md-checkbox> </section> "},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.originator=null,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue,n.configuration.originatorId&&n.configuration.originatorType?n.originator={id:n.configuration.originatorId,entityType:n.configuration.originatorType}:n.originator=null,n.$watch("originator",function(e,t){angular.equals(e,t)||(n.originator?(i.$viewValue.originatorId=n.originator.id,i.$viewValue.originatorType=n.originator.entityType):(i.$viewValue.originatorId=null,i.$viewValue.originatorType=null))},!0)},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(4),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(19),i=r(a),o=n(17),l=r(o);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTelemetryConfig",i.default).directive("tbActionNodeGeneratorConfig",l.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(22),i=r(a),o=n(23),l=r(o),s=n(20),u=r(s),d=n(24),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(27),i=r(a),o=n(26),l=r(o),s=n(28),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;a.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}r.transformMessageTypeChip=function(e){var n,r=t("filter")(c,{name:e},!0);return n=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},r.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,r=angular.element(n),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var i=e.messageTypes[a];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$filter","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(10),o=r(i)},[38,11],function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i);n(3)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(31),i=r(a),o=n(33),l=r(o);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).name},[38,16],function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(37),i=r(a),o=n(25),l=r(o),s=n(21),u=r(s),d=n(32),c=r(d),m=n(18),g=r(m),f=n(30),p=r(f),b=n(29),v=r(b),y=n(36),h=r(y);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbRelationsQueryConfig",p.default).directive("tbKvMapConfig",v.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(35),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name},function(e,t,n,r){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,r,a){var i=l.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var o=n(r),l=a(o)}]));
2 //# sourceMappingURL=rulenode-core-config.js.map 2 //# sourceMappingURL=rulenode-core-config.js.map
@@ -52,7 +52,7 @@ public class TbJsFilterNodeTest { @@ -52,7 +52,7 @@ public class TbJsFilterNodeTest {
52 @Test 52 @Test
53 public void falseEvaluationDoNotSendMsg() throws TbNodeException { 53 public void falseEvaluationDoNotSendMsg() throws TbNodeException {
54 initWithScript("return 10 > 15;"); 54 initWithScript("return 10 > 15;");
55 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}".getBytes()); 55 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}");
56 56
57 mockJsExecutor(); 57 mockJsExecutor();
58 58
@@ -65,7 +65,7 @@ public class TbJsFilterNodeTest { @@ -65,7 +65,7 @@ public class TbJsFilterNodeTest {
65 @Test 65 @Test
66 public void notValidMsgDataThrowsException() throws TbNodeException { 66 public void notValidMsgDataThrowsException() throws TbNodeException {
67 initWithScript("return 10 > 15;"); 67 initWithScript("return 10 > 15;");
68 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), new byte[4]); 68 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, null, "{}");
69 69
70 when(ctx.getJsExecutor()).thenReturn(executor); 70 when(ctx.getJsExecutor()).thenReturn(executor);
71 71
@@ -79,11 +79,11 @@ public class TbJsFilterNodeTest { @@ -79,11 +79,11 @@ public class TbJsFilterNodeTest {
79 public void exceptionInJsThrowsException() throws TbNodeException { 79 public void exceptionInJsThrowsException() throws TbNodeException {
80 initWithScript("return metadata.temp.curr < 15;"); 80 initWithScript("return metadata.temp.curr < 15;");
81 TbMsgMetaData metaData = new TbMsgMetaData(); 81 TbMsgMetaData metaData = new TbMsgMetaData();
82 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); 82 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}");
83 mockJsExecutor(); 83 mockJsExecutor();
84 84
85 node.onMsg(ctx, msg); 85 node.onMsg(ctx, msg);
86 - String expectedMessage = "TypeError: Cannot get property \"curr\" of null in <eval> at line number 1"; 86 + String expectedMessage = "TypeError: Cannot read property \"curr\" from undefined in <eval> at line number 1";
87 verifyError(msg, expectedMessage, ScriptException.class); 87 verifyError(msg, expectedMessage, ScriptException.class);
88 } 88 }
89 89
@@ -98,7 +98,7 @@ public class TbJsFilterNodeTest { @@ -98,7 +98,7 @@ public class TbJsFilterNodeTest {
98 TbMsgMetaData metaData = new TbMsgMetaData(); 98 TbMsgMetaData metaData = new TbMsgMetaData();
99 metaData.putValue("temp", "10"); 99 metaData.putValue("temp", "10");
100 metaData.putValue("humidity", "99"); 100 metaData.putValue("humidity", "99");
101 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); 101 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}");
102 mockJsExecutor(); 102 mockJsExecutor();
103 103
104 node.onMsg(ctx, msg); 104 node.onMsg(ctx, msg);
@@ -113,7 +113,7 @@ public class TbJsFilterNodeTest { @@ -113,7 +113,7 @@ public class TbJsFilterNodeTest {
113 TbMsgMetaData metaData = new TbMsgMetaData(); 113 TbMsgMetaData metaData = new TbMsgMetaData();
114 metaData.putValue("temp", "10"); 114 metaData.putValue("temp", "10");
115 metaData.putValue("humidity", "99"); 115 metaData.putValue("humidity", "99");
116 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}".getBytes()); 116 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}");
117 mockJsExecutor(); 117 mockJsExecutor();
118 118
119 node.onMsg(ctx, msg); 119 node.onMsg(ctx, msg);
@@ -129,7 +129,7 @@ public class TbJsFilterNodeTest { @@ -129,7 +129,7 @@ public class TbJsFilterNodeTest {
129 metaData.putValue("humidity", "99"); 129 metaData.putValue("humidity", "99");
130 String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; 130 String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
131 131
132 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); 132 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
133 mockJsExecutor(); 133 mockJsExecutor();
134 134
135 node.onMsg(ctx, msg); 135 node.onMsg(ctx, msg);
@@ -68,7 +68,7 @@ public class TbJsSwitchNodeTest { @@ -68,7 +68,7 @@ public class TbJsSwitchNodeTest {
68 metaData.putValue("humidity", "99"); 68 metaData.putValue("humidity", "99");
69 String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; 69 String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
70 70
71 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); 71 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
72 mockJsExecutor(); 72 mockJsExecutor();
73 73
74 node.onMsg(ctx, msg); 74 node.onMsg(ctx, msg);
@@ -92,7 +92,7 @@ public class TbJsSwitchNodeTest { @@ -92,7 +92,7 @@ public class TbJsSwitchNodeTest {
92 metaData.putValue("humidity", "99"); 92 metaData.putValue("humidity", "99");
93 String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; 93 String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
94 94
95 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); 95 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
96 mockJsExecutor(); 96 mockJsExecutor();
97 97
98 node.onMsg(ctx, msg); 98 node.onMsg(ctx, msg);
@@ -98,7 +98,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -98,7 +98,7 @@ public class TbGetCustomerAttributeNodeTest {
98 User user = new User(); 98 User user = new User();
99 user.setCustomerId(customerId); 99 user.setCustomerId(customerId);
100 100
101 - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); 101 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
102 102
103 when(ctx.getUserService()).thenReturn(userService); 103 when(ctx.getUserService()).thenReturn(userService);
104 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); 104 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
@@ -123,7 +123,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -123,7 +123,7 @@ public class TbGetCustomerAttributeNodeTest {
123 User user = new User(); 123 User user = new User();
124 user.setCustomerId(customerId); 124 user.setCustomerId(customerId);
125 125
126 - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); 126 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
127 127
128 when(ctx.getUserService()).thenReturn(userService); 128 when(ctx.getUserService()).thenReturn(userService);
129 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); 129 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
@@ -148,7 +148,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -148,7 +148,7 @@ public class TbGetCustomerAttributeNodeTest {
148 User user = new User(); 148 User user = new User();
149 user.setCustomerId(customerId); 149 user.setCustomerId(customerId);
150 150
151 - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); 151 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
152 152
153 when(ctx.getUserService()).thenReturn(userService); 153 when(ctx.getUserService()).thenReturn(userService);
154 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null)); 154 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null));
@@ -166,7 +166,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -166,7 +166,7 @@ public class TbGetCustomerAttributeNodeTest {
166 @Test 166 @Test
167 public void customerAttributeAddedInMetadata() { 167 public void customerAttributeAddedInMetadata() {
168 CustomerId customerId = new CustomerId(UUIDs.timeBased()); 168 CustomerId customerId = new CustomerId(UUIDs.timeBased());
169 - msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), new byte[4]); 169 + msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), "{}");
170 entityAttributeFetched(customerId); 170 entityAttributeFetched(customerId);
171 } 171 }
172 172
@@ -177,7 +177,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -177,7 +177,7 @@ public class TbGetCustomerAttributeNodeTest {
177 User user = new User(); 177 User user = new User();
178 user.setCustomerId(customerId); 178 user.setCustomerId(customerId);
179 179
180 - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); 180 + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}");
181 181
182 when(ctx.getUserService()).thenReturn(userService); 182 when(ctx.getUserService()).thenReturn(userService);
183 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); 183 when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user));
@@ -192,7 +192,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -192,7 +192,7 @@ public class TbGetCustomerAttributeNodeTest {
192 Asset asset = new Asset(); 192 Asset asset = new Asset();
193 asset.setCustomerId(customerId); 193 asset.setCustomerId(customerId);
194 194
195 - msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), new byte[4]); 195 + msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), "{}");
196 196
197 when(ctx.getAssetService()).thenReturn(assetService); 197 when(ctx.getAssetService()).thenReturn(assetService);
198 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); 198 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
@@ -207,7 +207,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -207,7 +207,7 @@ public class TbGetCustomerAttributeNodeTest {
207 Device device = new Device(); 207 Device device = new Device();
208 device.setCustomerId(customerId); 208 device.setCustomerId(customerId);
209 209
210 - msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]); 210 + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}");
211 211
212 when(ctx.getDeviceService()).thenReturn(deviceService); 212 when(ctx.getDeviceService()).thenReturn(deviceService);
213 when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); 213 when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device));
@@ -234,7 +234,7 @@ public class TbGetCustomerAttributeNodeTest { @@ -234,7 +234,7 @@ public class TbGetCustomerAttributeNodeTest {
234 Device device = new Device(); 234 Device device = new Device();
235 device.setCustomerId(customerId); 235 device.setCustomerId(customerId);
236 236
237 - msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]); 237 + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}");
238 238
239 when(ctx.getDeviceService()).thenReturn(deviceService); 239 when(ctx.getDeviceService()).thenReturn(deviceService);
240 when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); 240 when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device));
@@ -57,7 +57,7 @@ public class TbChangeOriginatorNodeTest { @@ -57,7 +57,7 @@ public class TbChangeOriginatorNodeTest {
57 Asset asset = new Asset(); 57 Asset asset = new Asset();
58 asset.setCustomerId(customerId); 58 asset.setCustomerId(customerId);
59 59
60 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]); 60 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}");
61 61
62 when(ctx.getAssetService()).thenReturn(assetService); 62 when(ctx.getAssetService()).thenReturn(assetService);
63 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); 63 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
@@ -78,7 +78,7 @@ public class TbChangeOriginatorNodeTest { @@ -78,7 +78,7 @@ public class TbChangeOriginatorNodeTest {
78 Asset asset = new Asset(); 78 Asset asset = new Asset();
79 asset.setCustomerId(customerId); 79 asset.setCustomerId(customerId);
80 80
81 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]); 81 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}");
82 82
83 when(ctx.getAssetService()).thenReturn(assetService); 83 when(ctx.getAssetService()).thenReturn(assetService);
84 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); 84 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset));
@@ -99,7 +99,7 @@ public class TbChangeOriginatorNodeTest { @@ -99,7 +99,7 @@ public class TbChangeOriginatorNodeTest {
99 Asset asset = new Asset(); 99 Asset asset = new Asset();
100 asset.setCustomerId(customerId); 100 asset.setCustomerId(customerId);
101 101
102 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), new byte[4]); 102 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}");
103 103
104 when(ctx.getAssetService()).thenReturn(assetService); 104 when(ctx.getAssetService()).thenReturn(assetService);
105 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong"))); 105 when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong")));
@@ -51,13 +51,13 @@ public class TbTransformMsgNodeTest { @@ -51,13 +51,13 @@ public class TbTransformMsgNodeTest {
51 51
52 @Test 52 @Test
53 public void metadataCanBeUpdated() throws TbNodeException { 53 public void metadataCanBeUpdated() throws TbNodeException {
54 - initWithScript("return metadata.temp = metadata.temp * 10;"); 54 + initWithScript("metadata.temp = metadata.temp * 10; return {metadata: metadata};");
55 TbMsgMetaData metaData = new TbMsgMetaData(); 55 TbMsgMetaData metaData = new TbMsgMetaData();
56 metaData.putValue("temp", "7"); 56 metaData.putValue("temp", "7");
57 metaData.putValue("humidity", "99"); 57 metaData.putValue("humidity", "99");
58 String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; 58 String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
59 59
60 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); 60 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
61 mockJsExecutor(); 61 mockJsExecutor();
62 62
63 node.onMsg(ctx, msg); 63 node.onMsg(ctx, msg);
@@ -65,18 +65,18 @@ public class TbTransformMsgNodeTest { @@ -65,18 +65,18 @@ public class TbTransformMsgNodeTest {
65 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class); 65 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
66 verify(ctx).tellNext(captor.capture()); 66 verify(ctx).tellNext(captor.capture());
67 TbMsg actualMsg = captor.getValue(); 67 TbMsg actualMsg = captor.getValue();
68 - assertEquals("70.0", actualMsg.getMetaData().getValue("temp")); 68 + assertEquals("70", actualMsg.getMetaData().getValue("temp"));
69 } 69 }
70 70
71 @Test 71 @Test
72 public void metadataCanBeAdded() throws TbNodeException { 72 public void metadataCanBeAdded() throws TbNodeException {
73 - initWithScript("return metadata.newAttr = metadata.humidity - msg.passed;"); 73 + initWithScript("metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};");
74 TbMsgMetaData metaData = new TbMsgMetaData(); 74 TbMsgMetaData metaData = new TbMsgMetaData();
75 metaData.putValue("temp", "7"); 75 metaData.putValue("temp", "7");
76 metaData.putValue("humidity", "99"); 76 metaData.putValue("humidity", "99");
77 String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; 77 String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}";
78 78
79 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); 79 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
80 mockJsExecutor(); 80 mockJsExecutor();
81 81
82 node.onMsg(ctx, msg); 82 node.onMsg(ctx, msg);
@@ -84,18 +84,18 @@ public class TbTransformMsgNodeTest { @@ -84,18 +84,18 @@ public class TbTransformMsgNodeTest {
84 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class); 84 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
85 verify(ctx).tellNext(captor.capture()); 85 verify(ctx).tellNext(captor.capture());
86 TbMsg actualMsg = captor.getValue(); 86 TbMsg actualMsg = captor.getValue();
87 - assertEquals("94.0", actualMsg.getMetaData().getValue("newAttr")); 87 + assertEquals("94", actualMsg.getMetaData().getValue("newAttr"));
88 } 88 }
89 89
90 @Test 90 @Test
91 public void payloadCanBeUpdated() throws TbNodeException { 91 public void payloadCanBeUpdated() throws TbNodeException {
92 - initWithScript("msg.passed = msg.passed * metadata.temp; return msg.bigObj.newProp = 'Ukraine' "); 92 + initWithScript("msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};");
93 TbMsgMetaData metaData = new TbMsgMetaData(); 93 TbMsgMetaData metaData = new TbMsgMetaData();
94 metaData.putValue("temp", "7"); 94 metaData.putValue("temp", "7");
95 metaData.putValue("humidity", "99"); 95 metaData.putValue("humidity", "99");
96 String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}"; 96 String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}";
97 97
98 - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson.getBytes()); 98 + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson);
99 mockJsExecutor(); 99 mockJsExecutor();
100 100
101 node.onMsg(ctx, msg); 101 node.onMsg(ctx, msg);
@@ -103,7 +103,7 @@ public class TbTransformMsgNodeTest { @@ -103,7 +103,7 @@ public class TbTransformMsgNodeTest {
103 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class); 103 ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
104 verify(ctx).tellNext(captor.capture()); 104 verify(ctx).tellNext(captor.capture());
105 TbMsg actualMsg = captor.getValue(); 105 TbMsg actualMsg = captor.getValue();
106 - String expectedJson = "{\"name\":\"Vit\",\"passed\":35.0,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}"; 106 + String expectedJson = "{\"name\":\"Vit\",\"passed\":35,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}";
107 assertEquals(expectedJson, new String(actualMsg.getData())); 107 assertEquals(expectedJson, new String(actualMsg.getData()));
108 } 108 }
109 109