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