thingsboard-aplcache

Details

diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
index 7f274ec..724f652 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
@@ -22,6 +22,7 @@ import delight.nashornsandbox.NashornSandbox;
 import delight.nashornsandbox.NashornSandboxes;
 import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.tuple.Pair;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
@@ -42,9 +43,10 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
     private ScriptEngine engine;
     private ExecutorService monitorExecutorService;
 
-    private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
-
-    private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
+    private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
+    private final Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
+    private final Map<String, Pair<UUID, AtomicInteger>> scriptToId = new ConcurrentHashMap<>();
+    private final Map<UUID, AtomicInteger> scriptIdToCount = new ConcurrentHashMap<>();
 
     @PostConstruct
     public void init() {
@@ -78,19 +80,27 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
 
     @Override
     public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
-        UUID scriptId = UUID.randomUUID();
-        String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
-        String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
-        try {
-            if (useJsSandbox()) {
-                sandbox.eval(jsScript);
-            } else {
-                engine.eval(jsScript);
+        Pair<UUID, AtomicInteger> deduplicated = deduplicate(scriptType, scriptBody);
+        UUID scriptId = deduplicated.getLeft();
+        AtomicInteger duplicateCount = deduplicated.getRight();
+
+        if(duplicateCount.compareAndSet(0, 1)) {
+            String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
+            String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
+            try {
+                if (useJsSandbox()) {
+                    sandbox.eval(jsScript);
+                } else {
+                    engine.eval(jsScript);
+                }
+                functionsMap.put(scriptId, functionName);
+            } catch (Exception e) {
+                duplicateCount.decrementAndGet();
+                log.warn("Failed to compile JS script: {}", e.getMessage(), e);
+                return Futures.immediateFailedFuture(e);
             }
-            functionsMap.put(scriptId, functionName);
-        } catch (Exception e) {
-            log.warn("Failed to compile JS script: {}", e.getMessage(), e);
-            return Futures.immediateFailedFuture(e);
+        } else {
+            duplicateCount.incrementAndGet();
         }
         return Futures.immediateFuture(scriptId);
     }
@@ -122,6 +132,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
 
     @Override
     public ListenableFuture<Void> release(UUID scriptId) {
+        AtomicInteger count = scriptIdToCount.get(scriptId);
+        if(count != null) {
+            if(count.decrementAndGet() > 0) {
+                return Futures.immediateFuture(null);
+            }
+        }
+
         String functionName = functionsMap.get(scriptId);
         if (functionName != null) {
             try {
@@ -156,4 +173,16 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
                 throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
         }
     }
+
+    private Pair<UUID, AtomicInteger> deduplicate(JsScriptType scriptType, String scriptBody) {
+        Pair<UUID, AtomicInteger> precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger());
+
+        Pair<UUID, AtomicInteger> pair = scriptToId.computeIfAbsent(deduplicateKey(scriptType, scriptBody), i -> precomputed);
+        AtomicInteger duplicateCount = scriptIdToCount.computeIfAbsent(pair.getLeft(), i -> pair.getRight());
+        return Pair.of(pair.getLeft(), duplicateCount);
+    }
+
+    private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
+        return scriptType + "_" + scriptBody;
+    }
 }
diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
index 98b0e77..2ba87ec 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
@@ -45,7 +45,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
         try {
             this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
         } catch (Exception e) {
-            throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
+            throw new IllegalArgumentException("Can't compile script: " + e.getMessage(), e);
         }
     }