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