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(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 ScriptFile scriptFile = (ScriptFile)script; 112 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 ScriptRunner runner = scripts.get(name); 121 return runner != null ? runner.getScript() : null; 122 } 123 124 @Override 125 public void fileModified(final File file) { 126 ScriptRunner runner = scripts.get(file.toString()); 127 if (runner == null) { 128 logger.info("{} is not a running script"); 129 return; 130 } 131 ScriptEngine engine = runner.getScriptEngine(); 132 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 public Object execute(Bindings bindings); 158 159 public AbstractScript getScript(); 160 161 public 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}