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
36 import org.apache.logging.log4j.Logger;
37 import org.apache.logging.log4j.core.util.FileWatcher;
38 import org.apache.logging.log4j.core.util.WatchManager;
39 import org.apache.logging.log4j.status.StatusLogger;
40
41
42
43
44 public class ScriptManager implements FileWatcher, Serializable {
45 private static final long serialVersionUID = -2534169384971965196L;
46 private static final String KEY_THREADING = "THREADING";
47 private static final Logger logger = StatusLogger.getLogger();
48
49 private final ScriptEngineManager manager = new ScriptEngineManager();
50 private final ConcurrentMap<String, ScriptRunner> scripts = new ConcurrentHashMap<>();
51 private final String languages;
52 private final WatchManager watchManager;
53 private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
54
55 public ScriptManager(final WatchManager watchManager) {
56 this.watchManager = watchManager;
57 final List<ScriptEngineFactory> factories = manager.getEngineFactories();
58 if (logger.isDebugEnabled()) {
59 final StringBuilder sb = new StringBuilder();
60 logger.debug("Installed script engines");
61 for (final ScriptEngineFactory factory : factories) {
62 String threading = (String) factory.getParameter(KEY_THREADING);
63 if (threading == null) {
64 threading = "Not Thread Safe";
65 }
66 final StringBuilder names = new StringBuilder();
67 for (final String name : factory.getNames()) {
68 if (names.length() > 0) {
69 names.append(", ");
70 }
71 names.append(name);
72 }
73 if (sb.length() > 0) {
74 sb.append(", ");
75 }
76 sb.append(names);
77 final boolean compiled = factory.getScriptEngine() instanceof Compilable;
78 logger.debug(factory.getEngineName() + " Version: " + factory.getEngineVersion() +
79 ", Language: " + factory.getLanguageName() + ", Threading: " + threading +
80 ", Compile: " + compiled + ", Names: {" + names.toString() + "}");
81 }
82 languages = sb.toString();
83 } else {
84 final StringBuilder names = new StringBuilder();
85 for (final ScriptEngineFactory factory : factories) {
86 for (final String name : factory.getNames()) {
87 if (names.length() > 0) {
88 names.append(", ");
89 }
90 names.append(name);
91 }
92 }
93 languages = names.toString();
94 }
95 }
96
97 public void addScript(final AbstractScript script) {
98 final ScriptEngine engine = manager.getEngineByName(script.getLanguage());
99 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 final ScriptFile scriptFile = (ScriptFile)script;
112 final 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 final ScriptRunner runner = scripts.get(name);
121 return runner != null ? runner.getScript() : null;
122 }
123
124 @Override
125 public void fileModified(final File file) {
126 final ScriptRunner runner = scripts.get(file.toString());
127 if (runner == null) {
128 logger.info("{} is not a running script");
129 return;
130 }
131 final ScriptEngine engine = runner.getScriptEngine();
132 final 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 Object execute(Bindings bindings);
158
159 AbstractScript getScript();
160
161 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
183
184
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 }