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