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.Objects; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ConcurrentMap; 028 029import javax.script.Bindings; 030import javax.script.Compilable; 031import javax.script.CompiledScript; 032import javax.script.ScriptEngine; 033import javax.script.ScriptEngineFactory; 034import javax.script.ScriptEngineManager; 035import javax.script.ScriptException; 036import javax.script.SimpleBindings; 037 038import org.apache.logging.log4j.Logger; 039import org.apache.logging.log4j.core.config.Configuration; 040import org.apache.logging.log4j.core.util.FileWatcher; 041import org.apache.logging.log4j.core.util.WatchManager; 042import org.apache.logging.log4j.status.StatusLogger; 043import org.apache.logging.log4j.util.Strings; 044 045/** 046 * Manages the scripts use by the Configuration. 047 */ 048public class ScriptManager implements FileWatcher, Serializable { 049 050 private abstract class AbstractScriptRunner implements ScriptRunner { 051 052 private static final String KEY_STATUS_LOGGER = "statusLogger"; 053 private static final String KEY_CONFIGURATION = "configuration"; 054 055 @Override 056 public Bindings createBindings() { 057 final SimpleBindings bindings = new SimpleBindings(); 058 bindings.put(KEY_CONFIGURATION, configuration); 059 bindings.put(KEY_STATUS_LOGGER, logger); 060 return bindings; 061 } 062 063 } 064 065 private static final long serialVersionUID = -2534169384971965196L; 066 private static final String KEY_THREADING = "THREADING"; 067 private static final Logger logger = StatusLogger.getLogger(); 068 069 private final Configuration configuration; 070 private final ScriptEngineManager manager = new ScriptEngineManager(); 071 private final ConcurrentMap<String, ScriptRunner> scriptRunners = new ConcurrentHashMap<>(); 072 private final String languages; 073 private final WatchManager watchManager; 074 075 public ScriptManager(final Configuration configuration, final WatchManager watchManager) { 076 this.configuration = configuration; 077 this.watchManager = watchManager; 078 final List<ScriptEngineFactory> factories = manager.getEngineFactories(); 079 if (logger.isDebugEnabled()) { 080 final StringBuilder sb = new StringBuilder(); 081 final int factorySize = factories.size(); 082 logger.debug("Installed {} script engine{}", factorySize, factorySize != 1 ? "s" : Strings.EMPTY); 083 for (final ScriptEngineFactory factory : factories) { 084 String threading = Objects.toString(factory.getParameter(KEY_THREADING), null); 085 if (threading == null) { 086 threading = "Not Thread Safe"; 087 } 088 final StringBuilder names = new StringBuilder(); 089 final List<String> languageNames = factory.getNames(); 090 for (final String name : languageNames) { 091 if (names.length() > 0) { 092 names.append(", "); 093 } 094 names.append(name); 095 } 096 if (sb.length() > 0) { 097 sb.append(", "); 098 } 099 sb.append(names); 100 final boolean compiled = factory.getScriptEngine() instanceof Compilable; 101 logger.debug("{} version: {}, language: {}, threading: {}, compile: {}, names: {}, factory class: {}", 102 factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(), threading, 103 compiled, languageNames, factory.getClass().getName()); 104 } 105 languages = sb.toString(); 106 } else { 107 final StringBuilder names = new StringBuilder(); 108 for (final ScriptEngineFactory factory : factories) { 109 for (final String name : factory.getNames()) { 110 if (names.length() > 0) { 111 names.append(", "); 112 } 113 names.append(name); 114 } 115 } 116 languages = names.toString(); 117 } 118 } 119 120 public void addScript(final AbstractScript script) { 121 final ScriptEngine engine = manager.getEngineByName(script.getLanguage()); 122 if (engine == null) { 123 logger.error("No ScriptEngine found for language " + script.getLanguage() + ". Available languages are: " 124 + languages); 125 return; 126 } 127 if (engine.getFactory().getParameter(KEY_THREADING) == null) { 128 scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script)); 129 } else { 130 scriptRunners.put(script.getName(), new MainScriptRunner(engine, script)); 131 } 132 133 if (script instanceof ScriptFile) { 134 final ScriptFile scriptFile = (ScriptFile) script; 135 final Path path = scriptFile.getPath(); 136 if (scriptFile.isWatched() && path != null) { 137 watchManager.watchFile(path.toFile(), this); 138 } 139 } 140 } 141 142 public Bindings createBindings(final AbstractScript script) { 143 return getScriptRunner(script).createBindings(); 144 } 145 146 public AbstractScript getScript(final String name) { 147 final ScriptRunner runner = scriptRunners.get(name); 148 return runner != null ? runner.getScript() : null; 149 } 150 151 @Override 152 public void fileModified(final File file) { 153 final ScriptRunner runner = scriptRunners.get(file.toString()); 154 if (runner == null) { 155 logger.info("{} is not a running script"); 156 return; 157 } 158 final ScriptEngine engine = runner.getScriptEngine(); 159 final AbstractScript script = runner.getScript(); 160 if (engine.getFactory().getParameter(KEY_THREADING) == null) { 161 scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script)); 162 } else { 163 scriptRunners.put(script.getName(), new MainScriptRunner(engine, script)); 164 } 165 166 } 167 168 public Object execute(final String name, final Bindings bindings) { 169 final ScriptRunner scriptRunner = scriptRunners.get(name); 170 if (scriptRunner == null) { 171 logger.warn("No script named {} could be found"); 172 return null; 173 } 174 return AccessController.doPrivileged(new PrivilegedAction<Object>() { 175 @Override 176 public Object run() { 177 return scriptRunner.execute(bindings); 178 } 179 }); 180 } 181 182 private interface ScriptRunner { 183 184 Bindings createBindings(); 185 186 Object execute(Bindings bindings); 187 188 AbstractScript getScript(); 189 190 ScriptEngine getScriptEngine(); 191 } 192 193 private class MainScriptRunner extends AbstractScriptRunner { 194 private final AbstractScript script; 195 private final CompiledScript compiledScript; 196 private final ScriptEngine scriptEngine; 197 198 public MainScriptRunner(final ScriptEngine scriptEngine, final AbstractScript script) { 199 this.script = script; 200 this.scriptEngine = scriptEngine; 201 CompiledScript compiled = null; 202 if (scriptEngine instanceof Compilable) { 203 logger.debug("Script {} is compilable", script.getName()); 204 compiled = AccessController.doPrivileged(new PrivilegedAction<CompiledScript>() { 205 @Override 206 public CompiledScript run() { 207 try { 208 return ((Compilable) scriptEngine).compile(script.getScriptText()); 209 } catch (final Throwable ex) { 210 /* 211 * ScriptException is what really should be caught here. However, beanshell's ScriptEngine 212 * implements Compilable but then throws Error when the compile method is called! 213 */ 214 logger.warn("Error compiling script", ex); 215 return null; 216 } 217 } 218 }); 219 } 220 compiledScript = compiled; 221 } 222 223 @Override 224 public ScriptEngine getScriptEngine() { 225 return this.scriptEngine; 226 } 227 228 @Override 229 public Object execute(final Bindings bindings) { 230 if (compiledScript != null) { 231 try { 232 return compiledScript.eval(bindings); 233 } catch (final ScriptException ex) { 234 logger.error("Error running script " + script.getName(), ex); 235 return null; 236 } 237 } 238 try { 239 return scriptEngine.eval(script.getScriptText(), bindings); 240 } catch (final ScriptException ex) { 241 logger.error("Error running script " + script.getName(), ex); 242 return null; 243 } 244 } 245 246 @Override 247 public AbstractScript getScript() { 248 return script; 249 } 250 } 251 252 private class ThreadLocalScriptRunner extends AbstractScriptRunner { 253 private final AbstractScript script; 254 255 private final ThreadLocal<MainScriptRunner> runners = new ThreadLocal<MainScriptRunner>() { 256 @Override 257 protected MainScriptRunner initialValue() { 258 final ScriptEngine engine = manager.getEngineByName(script.getLanguage()); 259 return new MainScriptRunner(engine, script); 260 } 261 }; 262 263 public ThreadLocalScriptRunner(final AbstractScript script) { 264 this.script = script; 265 } 266 267 @Override 268 public Object execute(final Bindings bindings) { 269 return runners.get().execute(bindings); 270 } 271 272 @Override 273 public AbstractScript getScript() { 274 return script; 275 } 276 277 @Override 278 public ScriptEngine getScriptEngine() { 279 return runners.get().getScriptEngine(); 280 } 281 } 282 283 private ScriptRunner getScriptRunner(final AbstractScript script) { 284 return scriptRunners.get(script.getName()); 285 } 286}