Index: log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java =================================================================== --- log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java (revision 1613155) +++ log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java (working copy) @@ -20,13 +20,18 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.net.URL; +import java.text.DecimalFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAliases; import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor; import org.apache.logging.log4j.core.util.ClassLoaderResourceLoader; import org.apache.logging.log4j.core.util.Closer; @@ -40,8 +45,9 @@ public class PluginManager { // TODO: re-use PluginCache code from plugin processor - private static final PluginRegistry> REGISTRY = - new PluginRegistry>(); + private static final PluginRegistry> REGISTRY = new PluginRegistry>(); + private static final CopyOnWriteArrayList PACKAGES = new CopyOnWriteArrayList(); + private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core"; private static final Logger LOGGER = StatusLogger.getLogger(); @@ -50,6 +56,7 @@ /** * Constructs a PluginManager for the plugin category name given. + * * @param category The plugin category name. */ public PluginManager(final String category) { @@ -58,12 +65,14 @@ /** * Process annotated plugins. - * @deprecated Use {@link org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor} instead. To do - * so, simply include {@code log4j-core} in your dependencies and make sure annotation processing is not disabled. - * By default, supported Java compilers will automatically use that plugin processor provided {@code log4j-core} - * is on the classpath. + * + * @deprecated Use {@link org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor} instead. To do so, + * simply include {@code log4j-core} in your dependencies and make sure annotation processing is not + * disabled. By default, supported Java compilers will automatically use that plugin processor provided + * {@code log4j-core} is on the classpath. */ - @Deprecated // use PluginProcessor instead + @Deprecated + // use PluginProcessor instead public static void main(final String[] args) { System.err.println("WARNING: this tool is superseded by the annotation processor included in log4j-core."); System.exit(-1); @@ -71,14 +80,19 @@ /** * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected. + * * @param p The package name. */ - @Deprecated // no more need for this method due to PluginProcessor public static void addPackage(final String p) { + if (PACKAGES.addIfAbsent(p)) { + // set of available plugins could have changed, reset plugin cache for newly-retrieved managers + REGISTRY.clear(); // TODO confirm if this is correct + } } /** * Returns the type of a specified plugin. + * * @param name The name of the plugin. * @return The plugin's type. */ @@ -88,6 +102,7 @@ /** * Returns all the matching plugins. + * * @return A Map containing the name of the plugin and its type. */ public Map> getPlugins() { @@ -103,6 +118,7 @@ /** * Collects plugins, optionally obtaining them from a preload map. + * * @param preLoad if true, plugins will be obtained from the preload map. * */ @@ -111,13 +127,65 @@ plugins = REGISTRY.getCategory(category); preLoad = false; } + long start = System.nanoTime(); if (preLoad) { final ResourceLoader loader = new ClassLoaderResourceLoader(Loader.getClassLoader()); loadPlugins(loader); } plugins = REGISTRY.getCategory(category); + loadFromPackages(start, preLoad); } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void loadFromPackages(final long start, final boolean preLoad) { + if (plugins == null || plugins.size() == 0) { + if (!PACKAGES.contains(LOG4J_PACKAGES)) { + PACKAGES.add(LOG4J_PACKAGES); + } + } + final ResolverUtil resolver = new ResolverUtil(); + final ClassLoader classLoader = Loader.getClassLoader(); + if (classLoader != null) { + resolver.setClassLoader(classLoader); + } + final Class cls = null; + final ResolverUtil.Test test = new PluginTest(cls); + for (final String pkg : PACKAGES) { + resolver.findInPackage(test, pkg); + } + for (final Class clazz : resolver.getClasses()) { + final Plugin plugin = clazz.getAnnotation(Plugin.class); + final String pluginCategory = plugin.category(); + final Map> map = REGISTRY.getCategory(pluginCategory); + String type = plugin.elementType().equals(Plugin.EMPTY) ? plugin.name() : plugin.elementType(); + PluginType pluginType = new PluginType(clazz, type, plugin.printObject(), plugin.deferChildren()); + map.put(plugin.name().toLowerCase(), pluginType); + final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class); + if (pluginAliases != null) { + for (String alias : pluginAliases.value()) { + type = plugin.elementType().equals(Plugin.EMPTY) ? alias : plugin.elementType(); + pluginType = new PluginType(clazz, type, plugin.printObject(), plugin.deferChildren()); + map.put(alias.trim().toLowerCase(), pluginType); + } + } + } + plugins = REGISTRY.getCategory(category); + long elapsed = System.nanoTime() - start; + reportPluginLoadDuration(preLoad, elapsed); + } + private void reportPluginLoadDuration(final boolean preLoad, long elapsed) { + final StringBuilder sb = new StringBuilder("Generated plugins in "); + DecimalFormat numFormat = new DecimalFormat("#0.000000"); + final double seconds = elapsed / (1000.0 * 1000.0 * 1000.0); + sb.append(numFormat.format(seconds)).append(" seconds, packages: "); + sb.append(PACKAGES); + sb.append(", preload: "); + sb.append(preLoad); + sb.append("."); + LOGGER.debug(sb.toString()); + } + public static void loadPlugins(final ResourceLoader loader) { final PluginRegistry> registry = decode(loader); if (registry != null) { @@ -166,7 +234,7 @@ final boolean defer = dis.readBoolean(); try { final Class clazz = loader.loadClass(className); - @SuppressWarnings({"unchecked","rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) final PluginType pluginType = new PluginType(clazz, name, printable, defer); types.put(key, pluginType); } catch (final ClassNotFoundException e) { @@ -185,4 +253,55 @@ return map.isEmpty() ? null : map; } + /** + * A Test that checks to see if each class is annotated with a specific annotation. If it + * is, then the test returns true, otherwise false. + */ + public static class PluginTest implements ResolverUtil.Test { + private final Class isA; + + /** + * Constructs an AnnotatedWith test for the specified annotation type. + * @param isA The class to compare against. + */ + public PluginTest(final Class isA) { + this.isA = isA; + } + + /** + * Returns true if the type is annotated with the class provided to the constructor. + * @param type The type to check for. + * @return true if the Class is of the specified type. + */ + @Override + public boolean matches(final Class type) { + return type != null && type.isAnnotationPresent(Plugin.class) && + (isA == null || isA.isAssignableFrom(type)); + } + + @Override + public String toString() { + final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName()); + if (isA != null) { + msg.append(" is assignable to " + isA.getSimpleName()); + } + return msg.toString(); + } + + @Override + public boolean matches(final URI resource) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean doesMatchClass() { + return true; + } + + @Override + public boolean doesMatchResource() { + return false; + } + } + }