| /* |
| * 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 java.io.File; |
| import java.io.Serializable; |
| import java.nio.file.Path; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| 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 javax.script.SimpleBindings; |
| |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.core.config.Configuration; |
| import org.apache.logging.log4j.core.util.FileWatcher; |
| import org.apache.logging.log4j.core.util.WatchManager; |
| import org.apache.logging.log4j.status.StatusLogger; |
| import org.apache.logging.log4j.util.Strings; |
| |
| /** |
| * Manages the scripts use by the Configuration. |
| */ |
| public class ScriptManager implements FileWatcher, Serializable { |
| |
| private abstract class AbstractScriptRunner implements ScriptRunner { |
| |
| private static final String KEY_STATUS_LOGGER = "statusLogger"; |
| private static final String KEY_CONFIGURATION = "configuration"; |
| |
| @Override |
| public Bindings createBindings() { |
| final SimpleBindings bindings = new SimpleBindings(); |
| bindings.put(KEY_CONFIGURATION, configuration); |
| bindings.put(KEY_STATUS_LOGGER, logger); |
| return bindings; |
| } |
| |
| } |
| |
| private static final long serialVersionUID = -2534169384971965196L; |
| private static final String KEY_THREADING = "THREADING"; |
| private static final Logger logger = StatusLogger.getLogger(); |
| |
| private final Configuration configuration; |
| private final ScriptEngineManager manager = new ScriptEngineManager(); |
| private final ConcurrentMap<String, ScriptRunner> scriptRunners = new ConcurrentHashMap<>(); |
| private final String languages; |
| private final WatchManager watchManager; |
| |
| public ScriptManager(final Configuration configuration, final WatchManager watchManager) { |
| this.configuration = configuration; |
| this.watchManager = watchManager; |
| final List<ScriptEngineFactory> factories = manager.getEngineFactories(); |
| if (logger.isDebugEnabled()) { |
| final StringBuilder sb = new StringBuilder(); |
| final int factorySize = factories.size(); |
| logger.debug("Installed {} script engine{}", factorySize, factorySize != 1 ? "s" : Strings.EMPTY); |
| for (final ScriptEngineFactory factory : factories) { |
| String threading = Objects.toString(factory.getParameter(KEY_THREADING), null); |
| if (threading == null) { |
| threading = "Not Thread Safe"; |
| } |
| final StringBuilder names = new StringBuilder(); |
| final List<String> languageNames = factory.getNames(); |
| for (final String name : languageNames) { |
| if (names.length() > 0) { |
| names.append(", "); |
| } |
| names.append(name); |
| } |
| if (sb.length() > 0) { |
| sb.append(", "); |
| } |
| sb.append(names); |
| final boolean compiled = factory.getScriptEngine() instanceof Compilable; |
| logger.debug("{} version: {}, language: {}, threading: {}, compile: {}, names: {}, factory class: {}", |
| factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(), threading, |
| compiled, languageNames, factory.getClass().getName()); |
| } |
| languages = sb.toString(); |
| } else { |
| final StringBuilder names = new StringBuilder(); |
| for (final ScriptEngineFactory factory : factories) { |
| for (final String name : factory.getNames()) { |
| if (names.length() > 0) { |
| names.append(", "); |
| } |
| names.append(name); |
| } |
| } |
| languages = names.toString(); |
| } |
| } |
| |
| public void addScript(final AbstractScript script) { |
| final ScriptEngine engine = manager.getEngineByName(script.getLanguage()); |
| if (engine == null) { |
| logger.error("No ScriptEngine found for language " + script.getLanguage() + ". Available languages are: " |
| + languages); |
| return; |
| } |
| if (engine.getFactory().getParameter(KEY_THREADING) == null) { |
| scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script)); |
| } else { |
| scriptRunners.put(script.getName(), new MainScriptRunner(engine, script)); |
| } |
| |
| if (script instanceof ScriptFile) { |
| final ScriptFile scriptFile = (ScriptFile) script; |
| final Path path = scriptFile.getPath(); |
| if (scriptFile.isWatched() && path != null) { |
| watchManager.watchFile(path.toFile(), this); |
| } |
| } |
| } |
| |
| public Bindings createBindings(final AbstractScript script) { |
| return getScriptRunner(script).createBindings(); |
| } |
| |
| public AbstractScript getScript(final String name) { |
| final ScriptRunner runner = scriptRunners.get(name); |
| return runner != null ? runner.getScript() : null; |
| } |
| |
| @Override |
| public void fileModified(final File file) { |
| final ScriptRunner runner = scriptRunners.get(file.toString()); |
| if (runner == null) { |
| logger.info("{} is not a running script"); |
| return; |
| } |
| final ScriptEngine engine = runner.getScriptEngine(); |
| final AbstractScript script = runner.getScript(); |
| if (engine.getFactory().getParameter(KEY_THREADING) == null) { |
| scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script)); |
| } else { |
| scriptRunners.put(script.getName(), new MainScriptRunner(engine, script)); |
| } |
| |
| } |
| |
| public Object execute(final String name, final Bindings bindings) { |
| final ScriptRunner scriptRunner = scriptRunners.get(name); |
| if (scriptRunner == null) { |
| logger.warn("No script named {} could be found"); |
| return null; |
| } |
| return AccessController.doPrivileged((PrivilegedAction<Object>) () -> scriptRunner.execute(bindings)); |
| } |
| |
| private interface ScriptRunner { |
| |
| Bindings createBindings(); |
| |
| Object execute(Bindings bindings); |
| |
| AbstractScript getScript(); |
| |
| ScriptEngine getScriptEngine(); |
| } |
| |
| private class MainScriptRunner extends AbstractScriptRunner { |
| private final AbstractScript script; |
| private final CompiledScript compiledScript; |
| private final ScriptEngine scriptEngine; |
| |
| public MainScriptRunner(final ScriptEngine scriptEngine, final AbstractScript script) { |
| this.script = script; |
| this.scriptEngine = scriptEngine; |
| CompiledScript compiled = null; |
| if (scriptEngine instanceof Compilable) { |
| logger.debug("Script {} is compilable", script.getName()); |
| compiled = AccessController.doPrivileged((PrivilegedAction<CompiledScript>) () -> { |
| try { |
| return ((Compilable) scriptEngine).compile(script.getScriptText()); |
| } catch (final 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); |
| return null; |
| } |
| }); |
| } |
| compiledScript = compiled; |
| } |
| |
| @Override |
| public ScriptEngine getScriptEngine() { |
| return this.scriptEngine; |
| } |
| |
| @Override |
| public Object execute(final Bindings bindings) { |
| if (compiledScript != null) { |
| try { |
| return compiledScript.eval(bindings); |
| } catch (final ScriptException ex) { |
| logger.error("Error running script " + script.getName(), ex); |
| return null; |
| } |
| } |
| try { |
| return scriptEngine.eval(script.getScriptText(), bindings); |
| } catch (final ScriptException ex) { |
| logger.error("Error running script " + script.getName(), ex); |
| return null; |
| } |
| } |
| |
| @Override |
| public AbstractScript getScript() { |
| return script; |
| } |
| } |
| |
| private class ThreadLocalScriptRunner extends AbstractScriptRunner { |
| private final AbstractScript script; |
| |
| private final ThreadLocal<MainScriptRunner> runners = new ThreadLocal<MainScriptRunner>() { |
| @Override |
| protected MainScriptRunner initialValue() { |
| final ScriptEngine engine = manager.getEngineByName(script.getLanguage()); |
| return new MainScriptRunner(engine, script); |
| } |
| }; |
| |
| public ThreadLocalScriptRunner(final AbstractScript script) { |
| this.script = script; |
| } |
| |
| @Override |
| public Object execute(final Bindings bindings) { |
| return runners.get().execute(bindings); |
| } |
| |
| @Override |
| public AbstractScript getScript() { |
| return script; |
| } |
| |
| @Override |
| public ScriptEngine getScriptEngine() { |
| return runners.get().getScriptEngine(); |
| } |
| } |
| |
| private ScriptRunner getScriptRunner(final AbstractScript script) { |
| return scriptRunners.get(script.getName()); |
| } |
| } |