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