001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.script;
018
019import java.io.File;
020import java.io.Serializable;
021import java.nio.file.Path;
022import java.security.AccessController;
023import java.security.PrivilegedAction;
024import java.util.List;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027
028import javax.script.Bindings;
029import javax.script.Compilable;
030import javax.script.CompiledScript;
031import javax.script.ScriptEngine;
032import javax.script.ScriptEngineFactory;
033import javax.script.ScriptEngineManager;
034import javax.script.ScriptException;
035
036import org.apache.logging.log4j.Logger;
037import org.apache.logging.log4j.core.util.FileWatcher;
038import org.apache.logging.log4j.core.util.WatchManager;
039import org.apache.logging.log4j.status.StatusLogger;
040
041/**
042 * Manages the scripts use by the Configuration.
043 */
044public class ScriptManager implements FileWatcher, Serializable {
045    private static final long serialVersionUID = -2534169384971965196L;
046    private static final String KEY_THREADING = "THREADING";
047    private static final Logger logger = StatusLogger.getLogger();
048    
049    private final ScriptEngineManager manager = new ScriptEngineManager();
050    private final ConcurrentMap<String, ScriptRunner> scripts = new ConcurrentHashMap<>();
051    private final String languages;
052    private final WatchManager watchManager;
053    private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
054
055    public ScriptManager(final WatchManager watchManager) {
056        this.watchManager = watchManager;
057        final List<ScriptEngineFactory> factories = manager.getEngineFactories();
058        if (logger.isDebugEnabled()) {
059            final StringBuilder sb = new StringBuilder();
060            logger.debug("Installed script engines");
061            for (final ScriptEngineFactory factory : factories) {
062                String threading = (String) factory.getParameter(KEY_THREADING);
063                if (threading == null) {
064                    threading = "Not Thread Safe";
065                }
066                final StringBuilder names = new StringBuilder();
067                for (final String name : factory.getNames()) {
068                    if (names.length() > 0) {
069                        names.append(", ");
070                    }
071                    names.append(name);
072                }
073                if (sb.length() > 0) {
074                    sb.append(", ");
075                }
076                sb.append(names);
077                final boolean compiled = factory.getScriptEngine() instanceof Compilable;
078                logger.debug(factory.getEngineName() + " Version: " + factory.getEngineVersion() +
079                    ", Language: " + factory.getLanguageName() + ", Threading: " + threading +
080                    ", Compile: " + compiled + ", Names: {" + names.toString() + "}");
081            }
082            languages = sb.toString();
083        } else {
084            final StringBuilder names = new StringBuilder();
085            for (final ScriptEngineFactory factory : factories) {
086                for (final String name : factory.getNames()) {
087                    if (names.length() > 0) {
088                        names.append(", ");
089                    }
090                    names.append(name);
091                }
092            }
093            languages = names.toString();
094        }
095    }
096
097    public void addScript(final AbstractScript script) {
098        final ScriptEngine engine = manager.getEngineByName(script.getLanguage());
099        if (engine == null) {
100            logger.error("No ScriptEngine found for language " + script.getLanguage() + ". Available languages are: " +
101                    languages);
102            return;
103        }
104        if (engine.getFactory().getParameter(KEY_THREADING) == null) {
105            scripts.put(script.getName(), new ThreadLocalScriptRunner(script));
106        } else {
107            scripts.put(script.getName(), new MainScriptRunner(engine, script));
108        }
109
110        if (script instanceof ScriptFile) {
111            final ScriptFile scriptFile = (ScriptFile)script;
112            final Path path = scriptFile.getPath();
113            if (scriptFile.isWatched() && path != null) {
114                watchManager.watchFile(path.toFile(), this);
115            }
116        }
117    }
118
119    public AbstractScript getScript(final String name) {
120        final ScriptRunner runner = scripts.get(name);
121        return runner != null ? runner.getScript() : null;
122    }
123
124    @Override
125    public void fileModified(final File file) {
126        final ScriptRunner runner = scripts.get(file.toString());
127        if (runner == null) {
128            logger.info("{} is not a running script");
129            return;
130        }
131        final ScriptEngine engine = runner.getScriptEngine();
132        final AbstractScript script = runner.getScript();
133        if (engine.getFactory().getParameter(KEY_THREADING) == null) {
134            scripts.put(script.getName(), new ThreadLocalScriptRunner(script));
135        } else {
136            scripts.put(script.getName(), new MainScriptRunner(engine, script));
137        }
138
139    }
140
141    public Object execute(final String name, final Bindings bindings) {
142        final ScriptRunner scriptRunner = scripts.get(name);
143        if (scriptRunner == null) {
144            logger.warn("No script named {} could be found");
145            return null;
146        }
147        return AccessController.doPrivileged(new PrivilegedAction<Object>() {
148            @Override
149            public Object run() {
150                return scriptRunner.execute(bindings);
151            }
152        });
153    }
154
155    private interface ScriptRunner {
156
157        Object execute(Bindings bindings);
158
159        AbstractScript getScript();
160
161        ScriptEngine getScriptEngine();
162    }
163
164    private class MainScriptRunner implements ScriptRunner {
165        private final AbstractScript script;
166        private final CompiledScript compiledScript;
167        private final ScriptEngine scriptEngine;
168
169
170        public MainScriptRunner(final ScriptEngine scriptEngine, final AbstractScript script) {
171            this.script = script;
172            this.scriptEngine = scriptEngine;
173            CompiledScript compiled = null;
174            if (scriptEngine instanceof Compilable) {
175                logger.debug("Script {} is compilable", script.getName());
176                compiled = AccessController.doPrivileged(new PrivilegedAction<CompiledScript>() {
177                    @Override
178                    public CompiledScript run() {
179                        try {
180                            return ((Compilable) scriptEngine).compile(script.getScriptText());
181                        } catch (final Throwable ex) {
182                                /* ScriptException is what really should be caught here. However, beanshell's
183                                 * ScriptEngine implements Compilable but then throws Error when the compile method
184                                 * is called!
185                                 */
186                            logger.warn("Error compiling script", ex);
187                            return null;
188                        }
189                    }
190                });
191            }
192            compiledScript = compiled;
193        }
194
195        @Override
196        public ScriptEngine getScriptEngine() {
197            return this.scriptEngine;
198        }
199
200        @Override
201        public Object execute(final Bindings bindings) {
202            if (compiledScript != null) {
203                try {
204                    return compiledScript.eval(bindings);
205                } catch (final ScriptException ex) {
206                    logger.error("Error running script " + script.getName(), ex);
207                    return null;
208                }
209            }
210            try {
211                return scriptEngine.eval(script.getScriptText(), bindings);
212            }   catch (final ScriptException ex) {
213                logger.error("Error running script " + script.getName(), ex);
214                return null;
215            }
216        }
217
218        @Override
219        public AbstractScript getScript() {
220            return script;
221        }
222    }
223
224    private class ThreadLocalScriptRunner implements ScriptRunner {
225        private final AbstractScript script;
226
227        private final ThreadLocal<MainScriptRunner> runners = new ThreadLocal<MainScriptRunner>() {
228            @Override protected MainScriptRunner initialValue() {
229                final ScriptEngine engine = manager.getEngineByName(script.getLanguage());
230                return new MainScriptRunner(engine, script);
231            }
232        };
233
234        public ThreadLocalScriptRunner(final AbstractScript script) {
235            this.script = script;
236        }
237
238        @Override
239        public Object execute(final Bindings bindings) {
240            return runners.get().execute(bindings);
241        }
242
243        @Override
244        public AbstractScript getScript() {
245            return script;
246        }
247
248        @Override
249        public ScriptEngine getScriptEngine() {
250            return runners.get().getScriptEngine();
251        }
252    }
253}