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  
43  /**
44   * Manages the scripts use by the Configuration.
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                                 /* ScriptException is what really should be caught here. However, beanshell's
207                                  * ScriptEngine implements Compilable but then throws Error when the compile method
208                                  * is called!
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 }