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.config.plugins;
18  
19  import java.io.BufferedInputStream;
20  import java.io.BufferedOutputStream;
21  import java.io.DataInputStream;
22  import java.io.DataOutputStream;
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.net.URL;
28  import java.text.DecimalFormat;
29  import java.util.Enumeration;
30  import java.util.HashMap;
31  import java.util.Map;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ConcurrentMap;
34  import java.util.concurrent.CopyOnWriteArrayList;
35  
36  import org.apache.logging.log4j.Logger;
37  import org.apache.logging.log4j.core.helpers.Closer;
38  import org.apache.logging.log4j.core.helpers.Loader;
39  import org.apache.logging.log4j.status.StatusLogger;
40  
41  /**
42   * Loads and manages all the plugins.
43   */
44  public class PluginManager {
45  
46      private static final long NANOS_PER_SECOND = 1000000000L;
47  
48      private static ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> pluginTypeMap =
49          new ConcurrentHashMap<String, ConcurrentMap<String, PluginType<?>>>();
50  
51      private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<String>();
52      private static final String PATH = "org/apache/logging/log4j/core/config/plugins/";
53      private static final String FILENAME = "Log4j2Plugins.dat";
54      private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
55  
56      private static final Logger LOGGER = StatusLogger.getLogger();
57  
58      private static String rootDir;
59  
60      private Map<String, PluginType<?>> plugins = new HashMap<String, PluginType<?>>();
61      private final String type;
62      private final Class<?> clazz;
63  
64      /**
65       * Constructor that takes only a type name.
66       * @param type The type name.
67       */
68      public PluginManager(final String type) {
69          this.type = type;
70          this.clazz = null;
71      }
72  
73      /**
74       * Constructor that takes a type name and a Class.
75       * @param type The type that must be matched.
76       * @param clazz The Class each match must be an instance of.
77       */
78      public PluginManager(final String type, final Class<?> clazz) {
79          this.type = type;
80          this.clazz = clazz;
81      }
82  
83      public static void main(final String[] args) throws Exception {
84          if (args == null || args.length < 1) {
85              System.err.println("A target directory must be specified");
86              System.exit(-1);
87          }
88          rootDir = args[0].endsWith("/") || args[0].endsWith("\\") ? args[0] : args[0] + "/";
89  
90          final PluginManager manager = new PluginManager("Core");
91          final String packages = args.length == 2 ? args[1] : null;
92  
93          manager.collectPlugins(false, packages);
94          encode(pluginTypeMap);
95      }
96  
97      /**
98       * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected.
99       * @param p The package name.
100      */
101     public static void addPackage(final String p) {
102         if (PACKAGES.addIfAbsent(p))
103         {
104             //set of available plugins could have changed, reset plugin cache for newly-retrieved managers
105             pluginTypeMap.clear();
106         }
107     }
108 
109     /**
110      * Returns the type of a specified plugin.
111      * @param name The name of the plugin.
112      * @return The plugin's type.
113      */
114     public PluginType<?> getPluginType(final String name) {
115         return plugins.get(name.toLowerCase());
116     }
117 
118     /**
119      * Returns all the matching plugins.
120      * @return A Map containing the name of the plugin and its type.
121      */
122     public Map<String, PluginType<?>> getPlugins() {
123         return plugins;
124     }
125 
126     /**
127      * Locates all the plugins.
128      */
129     public void collectPlugins() {
130         collectPlugins(true, null);
131     }
132 
133     /**
134      * Collects plugins, optionally obtaining them from a preload map.
135      * @param preLoad if true, plugins will be obtained from the preload map.
136      * @param pkgs A comma separated list of package names to scan for plugins. If
137      * null the default Log4j package name will be used.
138      */
139     @SuppressWarnings({ "unchecked", "rawtypes" })
140     public void collectPlugins(boolean preLoad, final String pkgs) {
141         if (pluginTypeMap.containsKey(type)) {
142             plugins = pluginTypeMap.get(type);
143             preLoad = false;
144         }
145         final long start = System.nanoTime();
146         final ResolverUtil resolver = new ResolverUtil();
147         final ClassLoader classLoader = Loader.getClassLoader();
148         if (classLoader != null) {
149             resolver.setClassLoader(classLoader);
150         }
151         if (preLoad) {
152             final ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> map = decode(classLoader);
153             if (map != null) {
154                 pluginTypeMap = map;
155                 plugins = map.get(type);
156             } else {
157                 LOGGER.warn("Plugin preloads not available from class loader {}", classLoader);
158             }
159         }
160         if (plugins == null || plugins.size() == 0) {
161             if (pkgs == null) {
162                 if (!PACKAGES.contains(LOG4J_PACKAGES)) {
163                     PACKAGES.add(LOG4J_PACKAGES);
164                 }
165             } else {
166                 final String[] names = pkgs.split(",");
167                 for (final String name : names) {
168                     PACKAGES.add(name);
169                 }
170             }
171         }
172         final ResolverUtil.Test test = new PluginTest(clazz);
173         for (final String pkg : PACKAGES) {
174             resolver.findInPackage(test, pkg);
175         }
176         for (final Class<?> clazz : resolver.getClasses()) {
177             final Plugin plugin = clazz.getAnnotation(Plugin.class);
178             final String pluginCategory = plugin.category();
179             if (!pluginTypeMap.containsKey(pluginCategory)) {
180                 pluginTypeMap.putIfAbsent(pluginCategory, new ConcurrentHashMap<String, PluginType<?>>());
181             }
182             final Map<String, PluginType<?>> map = pluginTypeMap.get(pluginCategory);
183             String type = plugin.elementType().equals(Plugin.EMPTY) ? plugin.name() : plugin.elementType();
184             PluginType pluginType = new PluginType(clazz, type, plugin.printObject(), plugin.deferChildren());
185             map.put(plugin.name().toLowerCase(), pluginType);
186             final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
187             if (pluginAliases != null) {
188                 for (String alias : pluginAliases.value()) {
189                     type =  plugin.elementType().equals(Plugin.EMPTY) ? alias : plugin.elementType();
190                     pluginType = new PluginType(clazz, type, plugin.printObject(), plugin.deferChildren());
191                     map.put(alias.trim().toLowerCase(), pluginType);
192                 }
193             }
194         }
195         long elapsed = System.nanoTime() - start;
196         plugins = pluginTypeMap.get(type);
197         final StringBuilder sb = new StringBuilder("Generated plugins");
198         sb.append(" in ");
199         DecimalFormat numFormat = new DecimalFormat("#0");
200         final long seconds = elapsed / NANOS_PER_SECOND;
201         elapsed %= NANOS_PER_SECOND;
202         sb.append(numFormat.format(seconds)).append('.');
203         numFormat = new DecimalFormat("000000000");
204         sb.append(numFormat.format(elapsed)).append(" seconds");
205         LOGGER.debug(sb.toString());
206     }
207 
208     @SuppressWarnings({ "unchecked" })
209     private static ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> decode(final ClassLoader classLoader) {
210         Enumeration<URL> resources;
211         try {
212             resources = classLoader.getResources(PATH + FILENAME);
213         } catch (final IOException ioe) {
214             LOGGER.warn("Unable to preload plugins", ioe);
215             return null;
216         }
217         final ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> map =
218             new ConcurrentHashMap<String, ConcurrentMap<String, PluginType<?>>>();
219         while (resources.hasMoreElements()) {
220             DataInputStream dis = null;
221             try {
222                 final URL url = resources.nextElement();
223                 LOGGER.debug("Found Plugin Map at {}", url.toExternalForm());
224                 final InputStream is = url.openStream();
225                 final BufferedInputStream bis = new BufferedInputStream(is);
226                 dis = new DataInputStream(bis);
227                 final int count = dis.readInt();
228                 for (int j = 0; j < count; ++j) {
229                     final String type = dis.readUTF();
230                     final int entries = dis.readInt();
231                     ConcurrentMap<String, PluginType<?>> types = map.get(type);
232                     if (types == null) {
233                         types = new ConcurrentHashMap<String, PluginType<?>>(count);
234                     }
235                     for (int i = 0; i < entries; ++i) {
236                         final String key = dis.readUTF();
237                         final String className = dis.readUTF();
238                         final String name = dis.readUTF();
239                         final boolean printable = dis.readBoolean();
240                         final boolean defer = dis.readBoolean();
241                         final Class<?> clazz = Class.forName(className);
242                         types.put(key, new PluginType(clazz, name, printable, defer));
243                     }
244                     map.putIfAbsent(type, types);
245                 }
246             } catch (final Exception ex) {
247                 LOGGER.warn("Unable to preload plugins", ex);
248                 return null;
249             } finally {
250                 Closer.closeSilent(dis);
251             }
252         }
253         return map.size() == 0 ? null : map;
254     }
255 
256     private static void encode(final ConcurrentMap<String, ConcurrentMap<String, PluginType<?>>> map) {
257         final String fileName = rootDir + PATH + FILENAME;
258         DataOutputStream dos = null;
259         try {
260             final File file = new File(rootDir + PATH);
261             file.mkdirs();
262             final FileOutputStream fos = new FileOutputStream(fileName);
263             final BufferedOutputStream bos = new BufferedOutputStream(fos);
264             dos = new DataOutputStream(bos);
265             dos.writeInt(map.size());
266             for (final Map.Entry<String, ConcurrentMap<String, PluginType<?>>> outer : map.entrySet()) {
267                 dos.writeUTF(outer.getKey());
268                 dos.writeInt(outer.getValue().size());
269                 for (final Map.Entry<String, PluginType<?>> entry : outer.getValue().entrySet()) {
270                     dos.writeUTF(entry.getKey());
271                     final PluginType<?> pt = entry.getValue();
272                     dos.writeUTF(pt.getPluginClass().getName());
273                     dos.writeUTF(pt.getElementName());
274                     dos.writeBoolean(pt.isObjectPrintable());
275                     dos.writeBoolean(pt.isDeferChildren());
276                 }
277             }
278         } catch (final Exception ex) {
279             ex.printStackTrace();
280         } finally {
281             Closer.closeSilent(dos);
282         }
283     }
284 
285     /**
286      * A Test that checks to see if each class is annotated with a specific annotation. If it
287      * is, then the test returns true, otherwise false.
288      */
289     public static class PluginTest extends ResolverUtil.ClassTest {
290         private final Class<?> isA;
291 
292         /**
293          * Constructs an AnnotatedWith test for the specified annotation type.
294          * @param isA The class to compare against.
295          */
296         public PluginTest(final Class<?> isA) {
297             this.isA = isA;
298         }
299 
300         /**
301          * Returns true if the type is annotated with the class provided to the constructor.
302          * @param type The type to check for.
303          * @return true if the Class is of the specified type.
304          */
305         @Override
306         public boolean matches(final Class<?> type) {
307             return type != null && type.isAnnotationPresent(Plugin.class) &&
308                 (isA == null || isA.isAssignableFrom(type));
309         }
310 
311         @Override
312         public String toString() {
313             final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName());
314             if (isA != null) {
315                 msg.append(" is assignable to " + isA.getSimpleName());
316             }
317             return msg.toString();
318         }
319     }
320 
321 }