1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.script;
18
19 import java.io.File;
20 import java.io.Serializable;
21 import java.nio.file.Path;
22 import java.security.AccessController;
23 import java.security.PrivilegedAction;
24 import java.util.List;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.ConcurrentMap;
27
28 import javax.script.Bindings;
29 import javax.script.Compilable;
30 import javax.script.CompiledScript;
31 import javax.script.ScriptEngine;
32 import javax.script.ScriptEngineFactory;
33 import javax.script.ScriptEngineManager;
34 import javax.script.ScriptException;
35 import javax.script.SimpleBindings;
36
37 import org.apache.logging.log4j.Logger;
38 import org.apache.logging.log4j.core.config.Configuration;
39 import org.apache.logging.log4j.core.util.FileWatcher;
40 import org.apache.logging.log4j.core.util.WatchManager;
41 import org.apache.logging.log4j.status.StatusLogger;
42 import org.apache.logging.log4j.util.Strings;
43
44
45
46
47 public class ScriptManager implements FileWatcher, Serializable {
48
49 private abstract class AbstractScriptRunner implements ScriptRunner {
50
51 private static final String KEY_STATUS_LOGGER = "statusLogger";
52 private static final String KEY_CONFIGURATION = "configuration";
53
54 @Override
55 public Bindings createBindings() {
56 final SimpleBindings bindings = new SimpleBindings();
57 bindings.put(KEY_CONFIGURATION, configuration);
58 bindings.put(KEY_STATUS_LOGGER, logger);
59 return bindings;
60 }
61
62 }
63
64 private static final long serialVersionUID = -2534169384971965196L;
65 private static final String KEY_THREADING = "THREADING";
66 private static final Logger logger = StatusLogger.getLogger();
67
68 private final Configuration configuration;
69 private final ScriptEngineManager manager = new ScriptEngineManager();
70 private final ConcurrentMap<String, ScriptRunner> scriptRunners = new ConcurrentHashMap<>();
71 private final String languages;
72 private final WatchManager watchManager;
73
74 public ScriptManager(final Configuration configuration, final WatchManager watchManager) {
75 this.configuration = configuration;
76 this.watchManager = watchManager;
77 final List<ScriptEngineFactory> factories = manager.getEngineFactories();
78 if (logger.isDebugEnabled()) {
79 final StringBuilder sb = new StringBuilder();
80 final int factorySize = factories.size();
81 logger.debug("Installed {} script engine{}", factorySize, factorySize != 1 ? "s" : Strings.EMPTY);
82 for (final ScriptEngineFactory factory : factories) {
83 String threading = (String) factory.getParameter(KEY_THREADING);
84 if (threading == null) {
85 threading = "Not Thread Safe";
86 }
87 final StringBuilder names = new StringBuilder();
88 final List<String> languageNames = factory.getNames();
89 for (final String name : languageNames) {
90 if (names.length() > 0) {
91 names.append(", ");
92 }
93 names.append(name);
94 }
95 if (sb.length() > 0) {
96 sb.append(", ");
97 }
98 sb.append(names);
99 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
211
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 }