/* 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.flowable.common.engine.impl.scripting;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.variable.api.delegate.VariableScope;

/**
 * @author Tom Baeyens
 * @author Joram Barrez
 * @author Frederik Heremans
 */
public class ScriptingEngines {

    public static final String DEFAULT_SCRIPTING_LANGUAGE = "juel";
    public static final String GROOVY_SCRIPTING_LANGUAGE = "groovy";

    private final ScriptEngineManager scriptEngineManager;
    protected ScriptBindingsFactory scriptBindingsFactory;

    protected boolean cacheScriptingEngines = true;
    protected Map<String, ScriptEngine> cachedEngines;

    public ScriptingEngines(ScriptBindingsFactory scriptBindingsFactory) {
        this(new ScriptEngineManager());
        this.scriptBindingsFactory = scriptBindingsFactory;
    }

    public ScriptingEngines(ScriptEngineManager scriptEngineManager) {
        this.scriptEngineManager = scriptEngineManager;
        cachedEngines = new HashMap<>();
    }

    public ScriptingEngines addScriptEngineFactory(ScriptEngineFactory scriptEngineFactory) {
        scriptEngineManager.registerEngineName(scriptEngineFactory.getEngineName(), scriptEngineFactory);
        return this;
    }

    public void setScriptEngineFactories(List<ScriptEngineFactory> scriptEngineFactories) {
        if (scriptEngineFactories != null) {
            for (ScriptEngineFactory scriptEngineFactory : scriptEngineFactories) {
                scriptEngineManager.registerEngineName(scriptEngineFactory.getEngineName(), scriptEngineFactory);
            }
        }
    }

    public Object evaluate(String script, String language, VariableScope variableScope) {
        return evaluate(script, language, createBindings(variableScope));
    }

    public Object evaluate(String script, String language, VariableScope variableScope, boolean storeScriptVariables) {
        return evaluate(script, language, createBindings(variableScope, storeScriptVariables));
    }

    public void setCacheScriptingEngines(boolean cacheScriptingEngines) {
        this.cacheScriptingEngines = cacheScriptingEngines;
    }

    public boolean isCacheScriptingEngines() {
        return cacheScriptingEngines;
    }

    protected Object evaluate(String script, String language, Bindings bindings) {
        ScriptEngine scriptEngine = getEngineByName(language);
        return evaluate(scriptEngine, script, bindings);
    }

    protected Object evaluate(ScriptEngine scriptEngine, String script, Bindings bindings) {
        try {
            return scriptEngine.eval(script, bindings);
        } catch (ScriptException e) {
            Throwable rootCause = ExceptionUtils.getRootCause(e);
            if (rootCause instanceof FlowableException) {
                throw (FlowableException) rootCause;
            }
            throw new FlowableException("problem evaluating script: " + e.getMessage(), e);
        }
    }

    protected ScriptEngine getEngineByName(String language) {
        ScriptEngine scriptEngine = null;

        if (cacheScriptingEngines) {
            scriptEngine = cachedEngines.get(language);
            if (scriptEngine == null) {
                scriptEngine = scriptEngineManager.getEngineByName(language);

                if (scriptEngine != null) {
                    // ACT-1858: Special handling for groovy engine regarding GC
                    if (GROOVY_SCRIPTING_LANGUAGE.equals(language)) {
                        try {
                            scriptEngine.getContext().setAttribute("#jsr223.groovy.engine.keep.globals", "weak", ScriptContext.ENGINE_SCOPE);
                        } catch (Exception ignore) {
                            // ignore this, in case engine doesn't support the
                            // passed attribute
                        }
                    }

                    // Check if script-engine allows caching, using "THREADING"
                    // parameter as defined in spec
                    Object threadingParameter = scriptEngine.getFactory().getParameter("THREADING");
                    if (threadingParameter != null) {
                        // Add engine to cache as any non-null result from the
                        // threading-parameter indicates at least MT-access
                        cachedEngines.put(language, scriptEngine);
                    }
                }
            }
        } else {
            scriptEngine = scriptEngineManager.getEngineByName(language);
        }

        if (scriptEngine == null) {
            throw new FlowableException("Can't find scripting engine for '" + language + "'");
        }
        return scriptEngine;
    }

    /** override to build a spring aware ScriptingEngines */
    protected Bindings createBindings(VariableScope variableScope) {
        return scriptBindingsFactory.createBindings(variableScope);
    }

    /** override to build a spring aware ScriptingEngines */
    protected Bindings createBindings(VariableScope variableScope, boolean storeScriptVariables) {
        return scriptBindingsFactory.createBindings(variableScope, storeScriptVariables);
    }

    public ScriptBindingsFactory getScriptBindingsFactory() {
        return scriptBindingsFactory;
    }

    public void setScriptBindingsFactory(ScriptBindingsFactory scriptBindingsFactory) {
        this.scriptBindingsFactory = scriptBindingsFactory;
    }
}

@@ -4073,32 +4073,367 @@
 gine == null) %7B%0A
+                synchronized (scriptEngineManager) %7B%0A                    // Get the cached engine again in case a different thread already created it%0A                    scriptEngine = cachedEngines.get(language);%0A%0A                    if (scriptEngine != null) %7B%0A                        return scriptEngine;%0A                    %7D%0A%0A    
                 
@@ -4503,32 +4503,36 @@
                 
+ 
+   
 if (scriptEngine
@@ -4535,32 +4535,36 @@
 gine != null) %7B%0A
+    
                 
@@ -4644,24 +4644,28 @@
             
+    
 if (GROOVY_S
@@ -4718,38 +4718,46 @@
                 
+  
+  
 try %7B%0A
+    
                 
@@ -4898,32 +4898,36 @@
                 
+    
 %7D catch (Excepti
@@ -4935,24 +4935,28 @@
 n ignore) %7B%0A
+    
             
@@ -5046,24 +5046,28 @@
             
+    
 // passed at
@@ -5074,16 +5074,20 @@
 tribute%0A
+    
         
@@ -5116,32 +5116,36 @@
                 
+    
 %7D%0A%0A             
@@ -5151,16 +5151,20 @@
         
+ 
+   
 // Check
@@ -5215,16 +5215,20 @@
 EADING%22%0A
+    
         
@@ -5291,16 +5291,20 @@
         
+    
 Object t
@@ -5392,24 +5392,28 @@
             
+   
+ 
 if (threadin
@@ -5426,32 +5426,36 @@
 eter != null) %7B%0A
+    
                 
@@ -5541,16 +5541,20 @@
         
+    
 // threa
@@ -5597,16 +5597,20 @@
 -access%0A
+    
         
@@ -5664,24 +5664,50 @@
 iptEngine);%0A
+                        %7D%0A
             

Make sure that only one script engine is created in a multi threaded environment