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