View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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   * Manages the scripts use by the Configuration.
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                              * ScriptException is what really should be caught here. However, beanshell's ScriptEngine
211                              * implements Compilable but then throws Error when the compile method is called!
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 }