diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 147c214a2f..b2496c0875 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -29,9 +29,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.util.Base64Utils; import org.thingsboard.rule.engine.api.ListeningExecutor; -import org.thingsboard.rule.engine.js.JsExecutorService; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; @@ -39,7 +37,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; @@ -63,15 +60,10 @@ import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.nio.charset.StandardCharsets; import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; @Slf4j @Component @@ -170,6 +162,10 @@ public class ActorSystemContext { @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; + @Autowired + @Getter + private ListeningExecutor jsExecutor; + @Value("${actors.session.sync.timeout}") @Getter private long syncSessionTimeout; @@ -339,9 +335,4 @@ public class ActorSystemContext { return Exception.class.isInstance(error) ? (Exception) error : new Exception(error); } - public ListeningExecutor getJsExecutor() { - //TODO: take thread count from yml. - return new JsExecutorService(1); - } - } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 03dc376f10..d3fdc94c8e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -20,6 +20,7 @@ import akka.actor.Cancellable; import com.google.common.base.Function; import org.thingsboard.rule.engine.api.ListeningExecutor; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; +import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -36,6 +37,7 @@ import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.service.script.NashornJsEngine; import scala.concurrent.duration.Duration; import java.util.List; @@ -127,6 +129,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getJsExecutor(); } + @Override + public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) { + return new NashornJsEngine(script, functionName, argNames); + } + @Override public AttributesService getAttributesService() { return mainCtx.getAttributesService(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java similarity index 69% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java rename to application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java index 19c2bb9405..9d110d13f7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/JsExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java @@ -13,23 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.js; +package org.thingsboard.server.service.script; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.ListeningExecutor; +import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.Callable; import java.util.concurrent.Executors; +@Component public class JsExecutorService implements ListeningExecutor{ - private final ListeningExecutorService service; + @Value("${actors.rule.js_thread_pool_size}") + private int jsExecutorThreadPoolSize; - public JsExecutorService(int threadCount) { - this.service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threadCount)); + private ListeningExecutorService service; + + @PostConstruct + public void init() { + this.service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(jsExecutorThreadPoolSize)); + } + + @PreDestroy + public void destroy() { + if (this.service != null) { + this.service.shutdown(); + } } @Override @@ -37,9 +52,4 @@ public class JsExecutorService implements ListeningExecutor{ return service.submit(task); } - @PreDestroy - @Override - public void onDestroy() { - service.shutdown(); - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java similarity index 95% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java rename to application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java index e3cc225ae7..38add44569 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/js/NashornJsEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java @@ -13,32 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.js; +package org.thingsboard.server.service.script; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; -import jdk.nashorn.api.scripting.ScriptObjectMirror; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import javax.script.*; -import java.io.IOException; -import java.nio.charset.StandardCharsets; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptException; import java.util.Collections; import java.util.Map; import java.util.Set; @Slf4j -public class NashornJsEngine { - +public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEngine { public static final String MSG = "msg"; public static final String METADATA = "metadata"; @@ -129,6 +125,7 @@ public class NashornJsEngine { } } + @Override public TbMsg executeUpdate(TbMsg msg) throws ScriptException { JsonNode result = executeScript(msg); if (!result.isObject()) { @@ -138,6 +135,7 @@ public class NashornJsEngine { return unbindMsg(result, msg); } + @Override public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException { JsonNode result = executeScript(prevMsg); if (!result.isObject()) { @@ -147,6 +145,7 @@ public class NashornJsEngine { return unbindMsg(result, prevMsg); } + @Override public boolean executeFilter(TbMsg msg) throws ScriptException { JsonNode result = executeScript(msg); if (!result.isBoolean()) { @@ -156,7 +155,8 @@ public class NashornJsEngine { return result.asBoolean(); } - public Set executeSwitch(TbMsg msg) throws ScriptException, NoSuchMethodException { + @Override + public Set executeSwitch(TbMsg msg) throws ScriptException { JsonNode result = executeScript(msg); if (result.isTextual()) { return Collections.singleton(result.asText()); @@ -191,6 +191,6 @@ public class NashornJsEngine { } public void destroy() { - //engine = null; + engine = null; } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index fc04a193e1..4ffa99811d 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -219,6 +219,8 @@ actors: termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}" # Errors for particular actor are persisted once per specified amount of milliseconds error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}" + # Specify thread pool size for javascript executor service + js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:10}" chain: # Errors for particular actor are persisted once per specified amount of milliseconds error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}" diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java index fecf019425..5420f7ff6f 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ListeningExecutor.java @@ -23,5 +23,4 @@ public interface ListeningExecutor { ListenableFuture executeAsync(Callable task); - void onDestroy(); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java new file mode 100644 index 0000000000..c2c8210f80 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api; + +import org.thingsboard.server.common.msg.TbMsg; + +import javax.script.ScriptException; +import java.util.Set; + +public interface ScriptEngine { + + TbMsg executeUpdate(TbMsg msg) throws ScriptException; + + TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException; + + boolean executeFilter(TbMsg msg) throws ScriptException; + + Set executeSwitch(TbMsg msg) throws ScriptException; + + void destroy(); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 9b63041193..b8c2b3888c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -83,4 +83,6 @@ public interface TbContext { ListeningExecutor getJsExecutor(); + ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames); + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 857baf6785..65b13710e3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -20,23 +20,13 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.TbNodeUtils; -import org.thingsboard.rule.engine.api.ListeningExecutor; -import org.thingsboard.rule.engine.api.RuleNode; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.filter.TbJsFilterNodeConfiguration; -import org.thingsboard.rule.engine.js.NashornJsEngine; +import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import javax.script.Bindings; - -import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -59,7 +49,7 @@ public class TbMsgGeneratorNode implements TbNode { public static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg"; private TbMsgGeneratorNodeConfiguration config; - private NashornJsEngine jsEngine; + private ScriptEngine jsEngine; private long delay; private EntityId originatorId; private UUID nextTickId; @@ -74,7 +64,7 @@ public class TbMsgGeneratorNode implements TbNode { } else { originatorId = ctx.getSelfId(); } - this.jsEngine = new NashornJsEngine(config.getJsScript(), "Generate", "prevMsg", "prevMetadata", "prevMsgType"); + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Generate", "prevMsg", "prevMetadata", "prevMsgType"); sentTickMsg(ctx); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java index f08135bad7..8ad344f87d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java @@ -18,12 +18,9 @@ package org.thingsboard.rule.engine.filter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; -import org.thingsboard.rule.engine.js.NashornJsEngine; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import javax.script.Bindings; - import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j @@ -43,12 +40,12 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; - private NashornJsEngine jsEngine; + private ScriptEngine jsEngine; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class); - this.jsEngine = new NashornJsEngine(config.getJsScript(), "Filter"); + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Filter"); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 0a96fab31f..3c6704b013 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -18,11 +18,9 @@ package org.thingsboard.rule.engine.filter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; -import org.thingsboard.rule.engine.js.NashornJsEngine; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import javax.script.Bindings; import java.util.Set; import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @@ -43,12 +41,12 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; - private NashornJsEngine jsEngine; + private ScriptEngine jsEngine; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class); - this.jsEngine = new NashornJsEngine(config.getJsScript(), "Switch"); + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Switch"); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index bbb4b5f37a..bf0c9feada 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -18,12 +18,9 @@ package org.thingsboard.rule.engine.transform; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; -import org.thingsboard.rule.engine.js.NashornJsEngine; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import javax.script.Bindings; - @RuleNode( type = ComponentType.TRANSFORMATION, name = "script", @@ -41,12 +38,12 @@ import javax.script.Bindings; public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; - private NashornJsEngine jsEngine; + private ScriptEngine jsEngine; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class); - this.jsEngine = new NashornJsEngine(config.getJsScript(), "Transform"); + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Transform"); setConfig(config); } diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index c20fa44ba7..9bd7960d0c 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -353,7 +353,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge); vm.editingRuleNodeLink = angular.copy(edge); $mdUtil.nextTick(() => { - vm.ruleNodeLinkForm.$setPristine(); + if (vm.ruleNodeLinkForm) { + vm.ruleNodeLinkForm.$setPristine(); + } }); } }, @@ -366,7 +368,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node); vm.editingRuleNode = angular.copy(node); $mdUtil.nextTick(() => { - vm.ruleNodeForm.$setPristine(); + if (vm.ruleNodeForm) { + vm.ruleNodeForm.$setPristine(); + } }); } } @@ -737,7 +741,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time nodes.push(node); } } - var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}); + var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}, true); if (res && res.length) { var firstNodeEdge = res[0]; var firstNode = vm.modelservice.nodes.getNodeByConnectorId(firstNodeEdge.destination);