/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.logging.log4j.core.script;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.status.StatusLogger;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Manages the scripts use by the Configuration.
 */
public class ScriptManager {

    private static final Logger logger = StatusLogger.getLogger();
    private final ScriptEngineManager manager = new ScriptEngineManager();
    private final ConcurrentMap<String, ScriptRunner> scripts = new ConcurrentHashMap<>();

    public ScriptManager() {
        if (logger.isDebugEnabled()) {
            List<ScriptEngineFactory> factories = manager.getEngineFactories();
            logger.debug("Installed script engines");
            for (ScriptEngineFactory factory : factories) {
                String threading = (String) factory.getParameter("THREADING");
                if (threading == null) {
                    threading = "Not Thread Safe";
                }
                StringBuilder names = new StringBuilder();
                boolean first = true;
                for (String name : factory.getNames()) {
                    if (!first) {
                        names.append(", ");
                    }
                    names.append(name);
                    first = false;
                }
                boolean compiled = factory.getScriptEngine() instanceof Compilable;
                logger.debug(factory.getEngineName() + " Version: " + factory.getEngineVersion() +
                    ", Language: " + factory.getLanguageName() + ", Threading: " + threading +
                    ", Compile: " + compiled + ", Names: {" + names.toString() + "}");
            }
        }
    }

    public void addScript(Script script) {
        ScriptEngine engine = manager.getEngineByName(script.getLanguage());
        if (engine == null) {
            logger.error("No ScriptEngine found for language " + script.getLanguage());
        }
        if (engine.getFactory().getParameter("THREADING") == null) {
            scripts.put(script.getName(), new ThreadLocalScriptRunner(script));
        } else {
            scripts.put(script.getName(), new MainScriptRunner(engine, script));
        }
    }

    public Object execute(String name, Bindings bindings) {
        ScriptRunner scriptRunner = scripts.get(name);
        if (scriptRunner == null) {
            logger.warn("No script named {} could be found");
            return null;
        }
        return scriptRunner.execute(bindings);
    }

    private interface ScriptRunner {

        public Object execute(Bindings bindings);
    }

    private class MainScriptRunner implements ScriptRunner {
        private final Script script;
        private final CompiledScript compiledScript;
        private final ScriptEngine scriptEngine;


        public MainScriptRunner(ScriptEngine scriptEngine, Script script) {
            this.script = script;
            this.scriptEngine = scriptEngine;
            CompiledScript compiled = null;
            if (scriptEngine instanceof Compilable) {
                logger.debug("Script {} is compilable", script.getName());
                try {
                    compiled = ((Compilable) scriptEngine).compile(script.getScriptText());
                } catch (Throwable ex) {
                /* ScriptException is what really should be caught here. However, beanshell's ScriptEngine
                 * implements Compilable but then throws Error when the compile method is called!
                 */
                    logger.warn("Error compiling script", ex);
                }
            }
            compiledScript = compiled;
        }

        public Object execute(Bindings bindings) {
            if (compiledScript != null) {
                try {
                    return compiledScript.eval(bindings);
                } catch (ScriptException ex) {
                    logger.error("Error running script " + script.getName(), ex);
                    return null;
                }
            } else {
                try {
                    return scriptEngine.eval(script.getScriptText(), bindings);
                }   catch (ScriptException ex) {
                    logger.error("Error running script " + script.getName(), ex);
                    return null;
                }
            }
        }

    }

    private class ThreadLocalScriptRunner implements ScriptRunner {
        private final Script script;

        private ThreadLocal<MainScriptRunner> runners = new ThreadLocal<MainScriptRunner>() {
            @Override protected MainScriptRunner initialValue() {
                ScriptEngine engine = manager.getEngineByName(script.getLanguage());
                return new MainScriptRunner(engine, script);
            }
        };

        public ThreadLocalScriptRunner(Script script) {
            this.script = script;
        }

        public Object execute(Bindings bindings) {
            return runners.get().execute(bindings);
        }
    }

}

@@ -885,76 +885,8 @@
 er;%0A
-import org.apache.logging.log4j.core.config.ConfigurationException;%0A
 impo
@@ -1624,81 +1624,74 @@
 ();%0A
-%0A
     p
-ublic ScriptManager() %7B%0A        if (logger.isDebugEnabled()) %7B%0A    
+rivate final String languages;%0A%0A    public ScriptManager() %7B%0A
     
@@ -1758,24 +1758,115 @@
 actories();%0A
+        if (logger.isDebugEnabled()) %7B%0A            StringBuilder sb = new StringBuilder();%0A
             
@@ -2219,46 +2219,8 @@
 ();%0A
-                boolean first = true;%0A
     
@@ -2300,14 +2300,26 @@
 if (
-!first
+names.length() %3E 0
 ) %7B%0A
@@ -2444,44 +2444,137 @@
     
-    first = false;%0A                %7D
+%7D%0A                if (sb.length() %3E 0) %7B%0A                    sb.append(%22, %22);%0A                %7D%0A                sb.append(names);
 %0A   
@@ -2941,32 +2941,486 @@
 ;%0A            %7D%0A
+            languages = sb.toString();%0A        %7D else %7B%0A            StringBuilder names = new StringBuilder();%0A            for (ScriptEngineFactory factory : factories) %7B%0A                for (String name : factory.getNames()) %7B%0A                    if (names.length() %3E 0) %7B%0A                        names.append(%22, %22);%0A                    %7D%0A                    names.append(name);%0A                %7D%0A            %7D%0A            languages = names.toString();%0A
         %7D%0A    %7D%0A
@@ -3647,33 +3647,113 @@
 pt.getLanguage()
-)
+ + %22. Available languages are: %22 +%0A                languages);%0A            return
 ;%0A        %7D%0A    

Improve error message