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
43
44
45
46 public class ScriptManager implements FileWatcher, Serializable {
47
48 private abstract class AbstractScriptRunner implements ScriptRunner {
49
50 private static final String KEY_STATUS_LOGGER = "statusLogger";
51 private static final String KEY_CONFIGURATION = "configuration";
52
53 @Override
54 public Bindings createBindings() {
55 final SimpleBindings bindings = new SimpleBindings();
56 bindings.put(KEY_CONFIGURATION, configuration);
57 bindings.put(KEY_STATUS_LOGGER, logger);
58 return bindings;
59 }
60
61 }
62
63 private static final long serialVersionUID = -2534169384971965196L;
64 private static final String KEY_THREADING = "THREADING";
65 private static final Logger logger = StatusLogger.getLogger();
66
67 private final Configuration configuration;
68 private final ScriptEngineManager manager = new ScriptEngineManager();
69 private final ConcurrentMap<String, ScriptRunner> scriptRunners = new ConcurrentHashMap<>();
70 private final String languages;
71 private final WatchManager watchManager;
72
73 public ScriptManager(final Configuration configuration, final WatchManager watchManager) {
74 this.configuration = configuration;
75 this.watchManager = watchManager;
76 final List<ScriptEngineFactory> factories = manager.getEngineFactories();
77 if (logger.isDebugEnabled()) {
78 final StringBuilder sb = new StringBuilder();
79 logger.debug("Installed script engines");
80 for (final ScriptEngineFactory factory : factories) {
81 String threading = (String) factory.getParameter(KEY_THREADING);
82 if (threading == null) {
83 threading = "Not Thread Safe";
84 }
85 final StringBuilder names = new StringBuilder();
86 for (final String name : factory.getNames()) {
87 if (names.length() > 0) {
88 names.append(", ");
89 }
90 names.append(name);
91 }
92 if (sb.length() > 0) {
93 sb.append(", ");
94 }
95 sb.append(names);
96 final boolean compiled = factory.getScriptEngine() instanceof Compilable;
97 logger.debug(factory.getEngineName() + " Version: " + factory.getEngineVersion() +
98 ", Language: " + factory.getLanguageName() + ", Threading: " + threading +
99 ", Compile: " + compiled + ", Names: {" + names.toString() + "}");
100 }
101 languages = sb.toString();
102 } else {
103 final StringBuilder names = new StringBuilder();
104 for (final ScriptEngineFactory factory : factories) {
105 for (final String name : factory.getNames()) {
106 if (names.length() > 0) {
107 names.append(", ");
108 }
109 names.append(name);
110 }
111 }
112 languages = names.toString();
113 }
114 }
115
116 public void addScript(final AbstractScript script) {
117 final ScriptEngine engine = manager.getEngineByName(script.getLanguage());
118 if (engine == null) {
119 logger.error("No ScriptEngine found for language " + script.getLanguage() + ". Available languages are: " +
120 languages);
121 return;
122 }
123 if (engine.getFactory().getParameter(KEY_THREADING) == null) {
124 scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script));
125 } else {
126 scriptRunners.put(script.getName(), new MainScriptRunner(engine, script));
127 }
128
129 if (script instanceof ScriptFile) {
130 final ScriptFile scriptFile = (ScriptFile) script;
131 final Path path = scriptFile.getPath();
132 if (scriptFile.isWatched() && path != null) {
133 watchManager.watchFile(path.toFile(), this);
134 }
135 }
136 }
137
138 public Bindings createBindings(final AbstractScript script) {
139 return getScriptRunner(script).createBindings();
140 }
141
142 public AbstractScript getScript(final String name) {
143 final ScriptRunner runner = scriptRunners.get(name);
144 return runner != null ? runner.getScript() : null;
145 }
146
147 @Override
148 public void fileModified(final File file) {
149 final ScriptRunner runner = scriptRunners.get(file.toString());
150 if (runner == null) {
151 logger.info("{} is not a running script");
152 return;
153 }
154 final ScriptEngine engine = runner.getScriptEngine();
155 final AbstractScript script = runner.getScript();
156 if (engine.getFactory().getParameter(KEY_THREADING) == null) {
157 scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script));
158 } else {
159 scriptRunners.put(script.getName(), new MainScriptRunner(engine, script));
160 }
161
162 }
163
164 public Object execute(final String name, final Bindings bindings) {
165 final ScriptRunner scriptRunner = scriptRunners.get(name);
166 if (scriptRunner == null) {
167 logger.warn("No script named {} could be found");
168 return null;
169 }
170 return AccessController.doPrivileged(new PrivilegedAction<Object>() {
171 @Override
172 public Object run() {
173 return scriptRunner.execute(bindings);
174 }
175 });
176 }
177
178 private interface ScriptRunner {
179
180 Bindings createBindings();
181
182 Object execute(Bindings bindings);
183
184 AbstractScript getScript();
185
186 ScriptEngine getScriptEngine();
187 }
188
189 private class MainScriptRunner extends AbstractScriptRunner {
190 private final AbstractScript script;
191 private final CompiledScript compiledScript;
192 private final ScriptEngine scriptEngine;
193
194 public MainScriptRunner(final ScriptEngine scriptEngine, final AbstractScript script) {
195 this.script = script;
196 this.scriptEngine = scriptEngine;
197 CompiledScript compiled = null;
198 if (scriptEngine instanceof Compilable) {
199 logger.debug("Script {} is compilable", script.getName());
200 compiled = AccessController.doPrivileged(new PrivilegedAction<CompiledScript>() {
201 @Override
202 public CompiledScript run() {
203 try {
204 return ((Compilable) scriptEngine).compile(script.getScriptText());
205 } catch (final Throwable ex) {
206
207
208
209
210 logger.warn("Error compiling script", ex);
211 return null;
212 }
213 }
214 });
215 }
216 compiledScript = compiled;
217 }
218
219 @Override
220 public ScriptEngine getScriptEngine() {
221 return this.scriptEngine;
222 }
223
224 @Override
225 public Object execute(final Bindings bindings) {
226 if (compiledScript != null) {
227 try {
228 return compiledScript.eval(bindings);
229 } catch (final ScriptException ex) {
230 logger.error("Error running script " + script.getName(), ex);
231 return null;
232 }
233 }
234 try {
235 return scriptEngine.eval(script.getScriptText(), bindings);
236 } catch (final ScriptException ex) {
237 logger.error("Error running script " + script.getName(), ex);
238 return null;
239 }
240 }
241
242 @Override
243 public AbstractScript getScript() {
244 return script;
245 }
246 }
247
248 private class ThreadLocalScriptRunner extends AbstractScriptRunner {
249 private final AbstractScript script;
250
251 private final ThreadLocal<MainScriptRunner> runners = new ThreadLocal<MainScriptRunner>() {
252 @Override protected MainScriptRunner initialValue() {
253 final ScriptEngine engine = manager.getEngineByName(script.getLanguage());
254 return new MainScriptRunner(engine, script);
255 }
256 };
257
258 public ThreadLocalScriptRunner(final AbstractScript script) {
259 this.script = script;
260 }
261
262 @Override
263 public Object execute(final Bindings bindings) {
264 return runners.get().execute(bindings);
265 }
266
267 @Override
268 public AbstractScript getScript() {
269 return script;
270 }
271 @Override
272 public ScriptEngine getScriptEngine() {
273 return runners.get().getScriptEngine();
274 }
275 }
276
277 private ScriptRunner getScriptRunner(final AbstractScript script) {
278 return scriptRunners.get(script.getName());
279 }
280 }