Repository: logging-log4j2 Updated Branches: refs/heads/LOG4J2-745 [created] f49273645
Import existing patches for LOG4J2-745 - Thanks to Scott Harrington for 99% of the code. Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/f4927364 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/f4927364 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/f4927364 Branch: refs/heads/LOG4J2-745 Commit: f492736456620513b7aec6c45822cf93ab0b0ed0 Parents: 7be5f6e Author: Matt Sicker <[email protected]> Authored: Mon Sep 1 11:29:23 2014 -0500 Committer: Matt Sicker <[email protected]> Committed: Mon Sep 1 11:29:23 2014 -0500 ---------------------------------------------------------------------- .../core/config/AbstractConfiguration.java | 41 +-- .../log4j/core/config/Configuration.java | 13 +- .../log4j/core/config/PropertiesPlugin.java | 8 +- .../core/config/json/JsonConfiguration.java | 28 +- .../config/plugins/processor/PluginCache.java | 48 ++- .../plugins/processor/PluginProcessor.java | 23 +- .../core/config/plugins/util/PluginManager.java | 243 +++------------ .../config/plugins/util/PluginRegistry.java | 300 ++++++++++++++++--- .../core/config/plugins/util/PluginType.java | 48 ++- .../log4j/core/config/xml/XmlConfiguration.java | 40 ++- .../logging/log4j/core/lookup/Interpolator.java | 24 +- .../logging/log4j/core/osgi/Activator.java | 20 +- .../log4j/core/pattern/PatternParser.java | 28 +- .../plugins/processor/PluginProcessorTest.java | 10 +- src/site/xdoc/manual/plugins.xml | 20 +- 15 files changed, 521 insertions(+), 373 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index 6e9ed34..75ec5fe 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -16,20 +16,6 @@ */ package org.apache.logging.log4j.core.config; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArrayList; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; @@ -59,6 +45,21 @@ import org.apache.logging.log4j.core.util.NameUtil; import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.util.PropertiesUtil; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + /** * The base Configuration. Many configuration implementations will extend this class. */ @@ -102,6 +103,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement private final StrSubstitutor subst = new StrSubstitutor(tempLookup); private LoggerConfig root = new LoggerConfig(); private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>(); + protected final List<String> pluginPackages = new ArrayList<String>(); protected PluginManager pluginManager; private final ConfigurationSource configurationSource; @@ -121,6 +123,11 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement } @Override + public List<String> getPluginPackages() { + return pluginPackages; + } + + @Override public Map<String, String> getProperties() { return properties; } @@ -132,9 +139,9 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement public void start() { LOGGER.debug("Starting configuration {}", this); this.setStarting(); - pluginManager.collectPlugins(); + pluginManager.collectPlugins(pluginPackages); final PluginManager levelPlugins = new PluginManager("Level"); - levelPlugins.collectPlugins(); + levelPlugins.collectPlugins(pluginPackages); final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins(); if (plugins != null) { for (final PluginType<?> type : plugins.values()) { @@ -336,7 +343,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement } else { final Map<String, String> map = (Map<String, String>) componentMap.get(CONTEXT_PROPERTIES); final StrLookup lookup = map == null ? null : new MapLookup(map); - subst.setVariableResolver(new Interpolator(lookup)); + subst.setVariableResolver(new Interpolator(lookup, pluginPackages)); } boolean setLoggers = false; http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java index c50fa81..ae2e99f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.config; -import java.util.Map; - import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; @@ -26,6 +24,9 @@ import org.apache.logging.log4j.core.filter.Filterable; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Advertiser; +import java.util.List; +import java.util.Map; + /** * Interface that must be implemented to create a configuration. */ @@ -75,6 +76,14 @@ public interface Configuration extends Filterable { void removeLogger(final String name); + /** + * Returns the list of packages to scan for plugins for this Configuration. + * + * @return the list of plugin packages. + * @since 2.1 + */ + List<String> getPluginPackages(); + Map<String, String> getProperties(); void addListener(ConfigurationListener listener); http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java index 5b53c30..6b2226d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java @@ -16,9 +16,6 @@ */ package org.apache.logging.log4j.core.config; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginElement; @@ -27,6 +24,9 @@ import org.apache.logging.log4j.core.lookup.Interpolator; import org.apache.logging.log4j.core.lookup.MapLookup; import org.apache.logging.log4j.core.lookup.StrLookup; +import java.util.HashMap; +import java.util.Map; + /** * Handles properties defined in the configuration. */ @@ -54,6 +54,6 @@ public final class PropertiesPlugin { map.put(prop.getName(), prop.getValue()); } - return new Interpolator(new MapLookup(map)); + return new Interpolator(new MapLookup(map), config.getPluginPackages()); } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java index 478155f..6b5dbfd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java @@ -16,31 +16,29 @@ */ package org.apache.logging.log4j.core.config.json; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.core.config.AbstractConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.FileConfigurationMonitor; import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Patterns; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; /** * Creates a Node hierarchy from a JSON file. @@ -86,7 +84,7 @@ public class JsonConfiguration extends AbstractConfiguration implements Reconfig } else if ("verbose".equalsIgnoreCase(entry.getKey())) { statusConfig.withVerbosity(value); } else if ("packages".equalsIgnoreCase(key)) { - PluginManager.addPackages(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); + pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); } else if ("name".equalsIgnoreCase(key)) { setName(value); } else if ("monitorInterval".equalsIgnoreCase(key)) { http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java index fefed12..a18deb5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java @@ -17,6 +17,8 @@ package org.apache.logging.log4j.core.config.plugins.processor; +import org.apache.logging.log4j.core.util.Closer; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; @@ -25,16 +27,25 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.util.Enumeration; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.ConcurrentMap; - -import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry; /** * */ public class PluginCache { - private final transient PluginRegistry<PluginEntry> pluginCategories = new PluginRegistry<PluginEntry>(); + private final Map<String, Map<String, PluginEntry>> categories = + new LinkedHashMap<String, Map<String, PluginEntry>>(); + + /** + * Returns all categories of plugins in this cache. + * + * @return all categories of plugins in this cache. + * @since 2.1 + */ + public Map<String, Map<String, PluginEntry>> getAllCategories() { + return categories; + } /** * Gets or creates a category of plugins. @@ -42,8 +53,12 @@ public class PluginCache { * @param category name of category to look up. * @return plugin mapping of names to plugin entries. */ - public ConcurrentMap<String, PluginEntry> getCategory(final String category) { - return pluginCategories.getCategory(category); + public Map<String, PluginEntry> getCategory(final String category) { + final String key = category.toLowerCase(); + if (!categories.containsKey(key)) { + categories.put(key, new LinkedHashMap<String, PluginEntry>()); + } + return categories.get(key); } /** @@ -52,11 +67,14 @@ public class PluginCache { * @param os destination to save cache to. * @throws IOException */ + // NOTE: if this file format is to be changed, the filename should change and this format should still be readable public void writeCache(final OutputStream os) throws IOException { final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(os)); try { - out.writeInt(pluginCategories.getCategoryCount()); - for (final Map.Entry<String, ConcurrentMap<String, PluginEntry>> category : pluginCategories.getCategories()) { + // See PluginManager.readFromCacheFiles for the corresponding decoder. Format may not be changed + // without breaking existing Log4j2Plugins.dat files. + out.writeInt(categories.size()); + for (final Map.Entry<String, Map<String, PluginEntry>> category : categories.entrySet()) { out.writeUTF(category.getKey()); final Map<String, PluginEntry> m = category.getValue(); out.writeInt(m.size()); @@ -70,7 +88,7 @@ public class PluginCache { } } } finally { - out.close(); + Closer.closeSilently(out); } } @@ -81,7 +99,7 @@ public class PluginCache { * @throws IOException */ public void loadCacheFiles(final Enumeration<URL> resources) throws IOException { - pluginCategories.clear(); + categories.clear(); while (resources.hasMoreElements()) { final URL url = resources.nextElement(); final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream())); @@ -89,7 +107,7 @@ public class PluginCache { final int count = in.readInt(); for (int i = 0; i < count; i++) { final String category = in.readUTF(); - final ConcurrentMap<String, PluginEntry> m = pluginCategories.getCategory(category); + final Map<String, PluginEntry> m = getCategory(category); final int entries = in.readInt(); for (int j = 0; j < entries; j++) { final PluginEntry entry = new PluginEntry(); @@ -99,11 +117,13 @@ public class PluginCache { entry.setPrintable(in.readBoolean()); entry.setDefer(in.readBoolean()); entry.setCategory(category); - m.putIfAbsent(entry.getKey(), entry); + if (!m.containsKey(entry.getKey())) { + m.put(entry.getKey(), entry); + } } } } finally { - in.close(); + Closer.closeSilently(in); } } } @@ -114,6 +134,6 @@ public class PluginCache { * @return number of plugin categories in cache. */ public int size() { - return pluginCategories.getCategoryCount(); + return categories.size(); } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java index a657158..1213936 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java @@ -17,13 +17,9 @@ package org.apache.logging.log4j.core.config.plugins.processor; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAliases; +import org.apache.logging.log4j.util.Strings; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -37,10 +33,13 @@ import javax.lang.model.util.SimpleElementVisitor6; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.StandardLocation; - -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.util.Strings; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; /** * Annotation processor for pre-scanning Log4j 2 plugins. @@ -92,7 +91,7 @@ public class PluginProcessor extends AbstractProcessor { for (final Element element : elements) { final Plugin plugin = element.getAnnotation(Plugin.class); final PluginEntry entry = element.accept(pluginVisitor, plugin); - final ConcurrentMap<String, PluginEntry> category = pluginCache.getCategory(entry.getCategory()); + final Map<String, PluginEntry> category = pluginCache.getCategory(entry.getCategory()); category.put(entry.getKey(), entry); final Collection<PluginEntry> entries = element.accept(pluginAliasesVisitor, plugin); for (final PluginEntry pluginEntry : entries) { http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java index 487f4c7..ffe7bcf 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java @@ -16,38 +16,22 @@ */ package org.apache.logging.log4j.core.config.plugins.util; -import java.io.BufferedInputStream; -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 org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; + import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; 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; -import org.apache.logging.log4j.core.util.Loader; -import org.apache.logging.log4j.core.util.ResourceLoader; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; - /** * Loads and manages all the plugins. */ public class PluginManager { - // TODO: re-use PluginCache code from plugin processor - private static final PluginRegistry<PluginType<?>> REGISTRY = new PluginRegistry<PluginType<?>>(); private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<String>(); private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core"; @@ -91,10 +75,7 @@ public class PluginManager { if (Strings.isBlank(p)) { return; } - 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 - } + PACKAGES.addIfAbsent(p); } /** @@ -133,196 +114,62 @@ public class PluginManager { * Locates all the plugins. */ public void collectPlugins() { - collectPlugins(true); + collectPlugins(null); } /** - * Collects plugins, optionally obtaining them from a preload map. - * - * @param preLoad if true, plugins will be obtained from the preload map. + * Locates all the plugins including search of specific packages. Warns about name collisions. * + * @param packages the list of packages to scan for plugins + * @since 2.1 */ - public void collectPlugins(boolean preLoad) { - if (REGISTRY.hasCategory(category)) { - 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); + public void collectPlugins(List<String> packages) { + final String categoryLowerCase = category.toLowerCase(); + final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<String, PluginType<?>>(); - long elapsed = System.nanoTime() - start; - reportPluginLoadDuration(preLoad, elapsed); - } - - @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<String, PluginType<?>> 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); - } - } + // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH + Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader(); + if (builtInPlugins.isEmpty()) { + // If we didn't find any plugins above, someone must have messed with the log4j-core.jar. + // Search the standard package in the hopes we can find our core plugins. + builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES); } - plugins = REGISTRY.getCategory(category); - } + mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase)); - 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<PluginType<?>> registry = decode(loader); - if (registry != null) { - for (final Map.Entry<String, ConcurrentMap<String, PluginType<?>>> entry : registry.getCategories()) { - REGISTRY.getCategory(entry.getKey()).putAll(entry.getValue()); - } - } else { - LOGGER.info("Plugin preloads not available from class loader {}", loader); + // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles + for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) { + mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase)); } - } - private static PluginRegistry<PluginType<?>> decode(final ResourceLoader loader) { - final Enumeration<URL> resources; - try { - resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE); - if (resources == null) { - return null; - } - } catch (final IOException ioe) { - LOGGER.warn("Unable to preload plugins", ioe); - return null; + // Next iterate any packages passed to the static addPackage method. + for (final String pkg : PACKAGES) { + mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); } - final PluginRegistry<PluginType<?>> map = new PluginRegistry<PluginType<?>>(); - while (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - LOGGER.debug("Found Plugin Map at {}", url.toExternalForm()); - final InputStream is; - try { - is = url.openStream(); - } catch (final IOException e) { - LOGGER.warn("Unable to open {}", url.toExternalForm(), e); - continue; - } - final DataInputStream dis = new DataInputStream(new BufferedInputStream(is)); - try { - final int count = dis.readInt(); - for (int j = 0; j < count; ++j) { - final String category = dis.readUTF(); - final int entries = dis.readInt(); - final Map<String, PluginType<?>> types = map.getCategory(category); - for (int i = 0; i < entries; ++i) { - final String key = dis.readUTF(); - final String className = dis.readUTF(); - final String name = dis.readUTF(); - final boolean printable = dis.readBoolean(); - final boolean defer = dis.readBoolean(); - try { - final Class<?> clazz = loader.loadClass(className); - @SuppressWarnings({ "unchecked", "rawtypes" }) - final PluginType<?> pluginType = new PluginType(clazz, name, printable, defer); - types.put(key, pluginType); - } catch (final ClassNotFoundException e) { - LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e); - } catch (final VerifyError e) { - LOGGER.info("Plugin [{}] could not be loaded due to verification error.", className, e); - } - } - } - } catch (final IOException ex) { - LOGGER.warn("Unable to preload plugins", ex); - } finally { - Closer.closeSilently(dis); + // Finally iterate any packages provided in the configuration (note these can be changed at runtime). + if (packages != null) { + for (final String pkg : packages) { + mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); } } - 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; + LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size()); - /** - * Constructs an AnnotatedWith test for the specified annotation type. - * @param isA The class to compare against. - */ - public PluginTest(final Class<?> isA) { - this.isA = isA; - } + plugins = newPlugins; + } - /** - * 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)); + private static void mergeByName(Map<String, PluginType<?>> newPlugins, List<PluginType<?>> plugins) { + if (plugins == null) { + return; } - - @Override - public String toString() { - final StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName()); - if (isA != null) { - msg.append(" is assignable to " + isA.getSimpleName()); + for (final PluginType<?> pluginType : plugins) { + final String key = pluginType.getKey(); + final PluginType<?> existing = newPlugins.get(key); + if (existing == null) { + newPlugins.put(key, pluginType); + } else if (!existing.getPluginClass().equals(pluginType.getPluginClass())) { + LOGGER.warn("Plugin [{}] is already mapped to {}, ignoring {}", + key, existing.getPluginClass(), pluginType.getPluginClass()); } - 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; } } - } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java index 4f04303..e7cd19c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java @@ -17,86 +17,290 @@ package org.apache.logging.log4j.core.config.plugins.util; -import java.io.Serializable; +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.PluginCache; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; +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.Loader; +import org.apache.logging.log4j.core.util.ResourceLoader; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; /** - * Registry for PluginType maps partitioned by category names. - * - * @param <T> plugin information object such as PluginType or PluginEntry. + * Registry singleton for PluginType maps partitioned by source type and then by category names. */ -public class PluginRegistry<T extends Serializable> { - private final ConcurrentMap<String, ConcurrentMap<String, T>> categories = - new ConcurrentHashMap<String, ConcurrentMap<String, T>>(); +public class PluginRegistry { + + private static final Logger LOGGER = StatusLogger.getLogger(); /** - * Gets or creates a plugin category if not already available. Category names are case-insensitive. The - * ConcurrentMap that is returned should also be treated as a case-insensitive plugin map where names should be - * converted to lowercase before retrieval or storage. - * - * @param category the plugin category to look up or create. - * @return the plugin map for the given category name. - * @throws IllegalArgumentException if the argument is {@code null} + * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH. */ - public ConcurrentMap<String, T> getCategory(final String category) { - if (category == null) { - throw new IllegalArgumentException("Category name cannot be null."); - } - final String key = category.toLowerCase(); - categories.putIfAbsent(key, new ConcurrentHashMap<String, T>()); - return categories.get(key); - } + private final AtomicReference<Map<String, List<PluginType<?>>>> pluginsByCategoryRef = + new AtomicReference<Map<String, List<PluginType<?>>>>(); /** - * Returns the number of plugin categories currently available. This is primarily useful for serialization. - * - * @return the number of plugin categories. + * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles. */ - public int getCategoryCount() { - return categories.size(); + private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> pluginsByCategoryByBundleId = + new ConcurrentHashMap<Long, Map<String, List<PluginType<?>>>>(); + + /** + * Contains plugins found by searching for annotated classes at runtime. + */ + private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> pluginsByCategoryByPackage = + new ConcurrentHashMap<String, Map<String, List<PluginType<?>>>>(); + + private PluginRegistry() { + } + + private static class Holder { + // the usual initialization-on-demand holder idiom + // https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom + private static final PluginRegistry INSTANCE = new PluginRegistry(); } /** - * Indicates whether or not any plugin categories have been registered. Note that this does not necessarily - * indicate if any plugins are registered as categories may be empty. + * Returns the global PluginRegistry instance. * - * @return {@code true} if there any categories registered. + * @return the global PluginRegistry instance. + * @since 2.1 */ - public boolean isEmpty() { - return categories.isEmpty(); + public static PluginRegistry getInstance() { + return Holder.INSTANCE; } /** * Resets the registry to an empty state. */ public void clear() { - categories.clear(); + pluginsByCategoryRef.set(null); + pluginsByCategoryByPackage.clear(); + pluginsByCategoryByBundleId.clear(); } /** - * Indicates whether or not the given category name is registered and has plugins in that category. - * - * @param category the plugin category name to check. - * @return {@code true} if the category exists and has plugins registered. - * @throws IllegalArgumentException if the argument is {@code null} + * @since 2.1 + */ + public Map<Long, Map<String, List<PluginType<?>>>> getPluginsByCategoryByBundleId() { + return pluginsByCategoryByBundleId; + } + + /** + * @since 2.1 + */ + @SuppressWarnings("unchecked") + public Map<String, List<PluginType<?>>> loadFromMainClassLoader() { + final Map<String, List<PluginType<?>>> existing = pluginsByCategoryRef.get(); + if (existing != null) { + // already loaded + return existing; + } + final ResourceLoader loader = new ClassLoaderResourceLoader(Loader.getClassLoader()); + final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(loader); + + // Note multiple threads could be calling this method concurrently. Both will do the work, + // but only one will be allowed to store the result in the AtomicReference. + // Return the map produced by whichever thread won the race, so all callers will get the same result. + if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) { + return newPluginsByCategory; + } + return pluginsByCategoryRef.get(); + } + + /** + * @since 2.1 + */ + public void clearBundlePlugins(final long bundleId) { + pluginsByCategoryByBundleId.remove(bundleId); + } + + /** + * @since 2.1 */ - public boolean hasCategory(final String category) { - if (category == null) { - throw new IllegalArgumentException("Category name cannot be null."); + @SuppressWarnings("unchecked") + public Map<String, List<PluginType<?>>> loadFromBundle(final long bundleId, final ResourceLoader loader) { + Map<String, List<PluginType<?>>> existing = pluginsByCategoryByBundleId.get(bundleId); + if (existing != null) { + // already loaded from this classloader + return existing; } - final String key = category.toLowerCase(); - return categories.containsKey(key) && !categories.get(key).isEmpty(); + final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(loader); + + // Note multiple threads could be calling this method concurrently. Both will do the work, + // but only one will be allowed to store the result in the outer map. + // Return the inner map produced by whichever thread won the race, so all callers will get the same result. + existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, newPluginsByCategory); + if (existing != null) { + return existing; + } + return newPluginsByCategory; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Map<String, List<PluginType<?>>> decodeCacheFiles(final ResourceLoader loader) { + final long startTime = System.nanoTime(); + final PluginCache cache = new PluginCache(); + try { + final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE); + if (resources == null) { + LOGGER.info("Plugin preloads not available from class loader {}", loader); + } else { + cache.loadCacheFiles(resources); + } + } catch (final IOException ioe) { + LOGGER.warn("Unable to preload plugins", ioe); + } + final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<String, List<PluginType<?>>>(); + int pluginCount = 0; + for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) { + final String categoryLowerCase = outer.getKey(); + final List<PluginType<?>> types = new ArrayList<PluginType<?>>(outer.getValue().size()); + newPluginsByCategory.put(categoryLowerCase, types); + for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) { + final PluginEntry entry = inner.getValue(); + final String className = entry.getClassName(); + try { + final Class<?> clazz = loader.loadClass(className); + types.add(new PluginType(entry, clazz, entry.getName())); + ++pluginCount; + } catch (final ClassNotFoundException e) { + LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e); + } catch (final VerifyError e) { + LOGGER.info("Plugin [{}] could not be loaded due to verification error.", className, e); + } + } + } + + final long endTime = System.nanoTime(); + final DecimalFormat numFormat = new DecimalFormat("#0.000000"); + final double seconds = (endTime - startTime) * 1e-9; + LOGGER.debug("Took {} seconds to load {} plugins from {}", + numFormat.format(seconds), pluginCount, loader); + return newPluginsByCategory; } /** - * Gets an entry set for iterating over the registered plugin categories. + * @since 2.1 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) { + if (Strings.isBlank(pkg)) { + // happens when splitting an empty string + return Collections.emptyMap(); + } + Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg); + if (existing != null) { + // already loaded this package + return existing; + } + + final long startTime = System.nanoTime(); + final ResolverUtil resolver = new ResolverUtil(); + final ClassLoader classLoader = Loader.getClassLoader(); + if (classLoader != null) { + resolver.setClassLoader(classLoader); + } + resolver.findInPackage(new PluginTest(), pkg); + + final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<String, List<PluginType<?>>>(); + for (final Class<?> clazz : resolver.getClasses()) { + final Plugin plugin = clazz.getAnnotation(Plugin.class); + final String categoryLowerCase = plugin.category().toLowerCase(); + List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase); + if (list == null) { + newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<PluginType<?>>()); + } + final PluginEntry mainEntry = new PluginEntry(); + final String mainElementName = plugin.elementType().equals( + Plugin.EMPTY) ? plugin.name() : plugin.elementType(); + mainEntry.setKey(plugin.name().toLowerCase()); + mainEntry.setName(plugin.name()); + mainEntry.setCategory(plugin.category()); + mainEntry.setClassName(clazz.getName()); + mainEntry.setPrintable(plugin.printObject()); + mainEntry.setDefer(plugin.deferChildren()); + list.add(new PluginType(mainEntry, clazz, mainElementName)); + final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class); + if (pluginAliases != null) { + for (String alias : pluginAliases.value()) { + final PluginEntry aliasEntry = new PluginEntry(); + final String aliasElementName = plugin.elementType().equals( + Plugin.EMPTY) ? alias.trim() : plugin.elementType(); + aliasEntry.setKey(alias.trim().toLowerCase()); + aliasEntry.setName(plugin.name()); + aliasEntry.setCategory(plugin.category()); + aliasEntry.setClassName(clazz.getName()); + aliasEntry.setPrintable(plugin.printObject()); + aliasEntry.setDefer(plugin.deferChildren()); + list.add(new PluginType(aliasEntry, clazz, aliasElementName)); + } + } + } + + final long endTime = System.nanoTime(); + final DecimalFormat numFormat = new DecimalFormat("#0.000000"); + final double seconds = (endTime - startTime) * 1e-9; + LOGGER.debug("Took {} seconds to load {} plugins from package {}", + numFormat.format(seconds), resolver.getClasses().size(), pkg); + + // Note multiple threads could be calling this method concurrently. Both will do the work, + // but only one will be allowed to store the result in the outer map. + // Return the inner map produced by whichever thread won the race, so all callers will get the same result. + existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory); + if (existing != null) { + return existing; + } + return newPluginsByCategory; + } + + /** + * A Test that checks to see if each class is annotated with the 'Plugin' annotation. If it + * is, then the test returns true, otherwise false. * - * @return an entry set of the registered plugin categories. + * @since 2.1 */ - public Set<Map.Entry<String, ConcurrentMap<String, T>>> getCategories() { - return categories.entrySet(); + public static class PluginTest implements ResolverUtil.Test { + @Override + public boolean matches(final Class<?> type) { + return type != null && type.isAnnotationPresent(Plugin.class); + } + + @Override + public String toString() { + return "annotated with @" + Plugin.class.getSimpleName(); + } + + @Override + public boolean matches(final URI resource) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean doesMatchClass() { + return true; + } + + @Override + public boolean doesMatchResource() { + return false; + } } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java index 8c04785..cc6bc66 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java @@ -17,7 +17,7 @@ package org.apache.logging.log4j.core.config.plugins.util; -import java.io.Serializable; +import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; /** * Plugin Descriptor. This is a memento object for Plugin annotations paired to their annotated classes. @@ -25,20 +25,19 @@ import java.io.Serializable; * @param <T> The plug-in class, which can be any kind of class. * @see org.apache.logging.log4j.core.config.plugins.Plugin */ -public class PluginType<T> implements Serializable { - - private static final long serialVersionUID = 4743255148794846612L; +public class PluginType<T> { + private final PluginEntry pluginEntry; private final Class<T> pluginClass; private final String elementName; - private final boolean printObject; - private final boolean deferChildren; - public PluginType(final Class<T> clazz, final String name, final boolean printObj, final boolean deferChildren) { - this.pluginClass = clazz; - this.elementName = name; - this.printObject = printObj; - this.deferChildren = deferChildren; + /** + * @since 2.1 + */ + public PluginType(final PluginEntry pluginEntry, final Class<T> pluginClass, final String elementName) { + this.pluginEntry = pluginEntry; + this.pluginClass = pluginClass; + this.elementName = elementName; } public Class<T> getPluginClass() { @@ -49,17 +48,36 @@ public class PluginType<T> implements Serializable { return this.elementName; } + /** + * @since 2.1 + */ + public String getKey() { + return this.pluginEntry.getKey(); + } + public boolean isObjectPrintable() { - return this.printObject; + return this.pluginEntry.isPrintable(); } public boolean isDeferChildren() { - return this.deferChildren; + return this.pluginEntry.isDefer(); + } + + /** + * @since 2.1 + */ + public String getCategory() { + return this.pluginEntry.getCategory(); } @Override public String toString() { - return "PluginType [pluginClass=" + this.pluginClass + ", elementName=" + this.elementName + ", printObject=" - + this.printObject + ", deferChildren=" + this.deferChildren + "]"; + return "PluginType [pluginClass=" + pluginClass + + ", key=" + pluginEntry.getKey() + + ", elementName=" + pluginEntry.getName() + + ", isObjectPrintable=" + pluginEntry.isPrintable() + + ", isDeferChildren==" + pluginEntry.isDefer() + + ", category=" + pluginEntry.getCategory() + + "]"; } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java index 1e9c982..8962443 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java @@ -16,32 +16,12 @@ */ package org.apache.logging.log4j.core.config.xml; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import javax.xml.XMLConstants; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; - import org.apache.logging.log4j.core.config.AbstractConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.FileConfigurationMonitor; import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; import org.apache.logging.log4j.core.config.status.StatusConfiguration; @@ -57,6 +37,24 @@ import org.w3c.dom.Text; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + /** * Creates a Node hierarchy from an XML file. */ @@ -153,7 +151,7 @@ public class XmlConfiguration extends AbstractConfiguration implements Reconfigu } else if ("verbose".equalsIgnoreCase(key)) { statusConfig.withVerbosity(value); } else if ("packages".equalsIgnoreCase(key)) { - PluginManager.addPackages(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); + pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); } else if ("name".equalsIgnoreCase(key)) { setName(value); } else if ("strict".equalsIgnoreCase(key)) { http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java index 4eda8b7..d315d4e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java @@ -16,9 +16,6 @@ */ package org.apache.logging.log4j.core.lookup; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.util.PluginManager; @@ -26,6 +23,10 @@ import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.status.StatusLogger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Proxies all the other {@link StrLookup}s. */ @@ -41,15 +42,26 @@ public class Interpolator implements StrLookup { private final StrLookup defaultLookup; public Interpolator(final StrLookup defaultLookup) { + this(defaultLookup, null); + } + + /** + * Constructs an Interpolator using a given StrLookup and a list of packages to find Lookup plugins in. + * + * @param defaultLookup the default StrLookup to use as a fallback + * @param pluginPackages a list of packages to scan for Lookup plugins + * @since 2.1 + */ + public Interpolator(final StrLookup defaultLookup, final List<String> pluginPackages) { this.defaultLookup = defaultLookup == null ? new MapLookup(new HashMap<String, String>()) : defaultLookup; final PluginManager manager = new PluginManager("Lookup"); - manager.collectPlugins(); + manager.collectPlugins(pluginPackages); final Map<String, PluginType<?>> plugins = manager.getPlugins(); for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) { - @SuppressWarnings("unchecked") - final Class<? extends StrLookup> clazz = (Class<? extends StrLookup>) entry.getValue().getPluginClass(); try { + @SuppressWarnings("unchecked") + final Class<? extends StrLookup> clazz = (Class<? extends StrLookup>) entry.getValue().getPluginClass(); lookups.put(entry.getKey(), clazz.getConstructor().newInstance()); } catch (final Exception ex) { LOGGER.error("Unable to create Lookup for {}", entry.getKey(), ex); http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java index 2581f00..85939be 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java @@ -17,10 +17,8 @@ package org.apache.logging.log4j.core.osgi; -import java.util.concurrent.atomic.AtomicReference; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; +import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry; import org.apache.logging.log4j.core.util.BundleResourceLoader; import org.apache.logging.log4j.status.StatusLogger; import org.osgi.framework.Bundle; @@ -29,6 +27,8 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.SynchronousBundleListener; +import java.util.concurrent.atomic.AtomicReference; + /** * OSGi BundleActivator. */ @@ -59,22 +59,34 @@ public final class Activator implements BundleActivator, SynchronousBundleListen private static void scanBundleForPlugins(final Bundle bundle) { LOGGER.trace("Scanning bundle [{}] for plugins.", bundle.getSymbolicName()); - PluginManager.loadPlugins(new BundleResourceLoader(bundle)); + PluginRegistry.getInstance().loadFromBundle(bundle.getBundleId(), new BundleResourceLoader(bundle)); + } + + private static void stopBundlePlugins(final Bundle bundle) { + LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName()); + // TODO: plugin lifecycle code + PluginRegistry.getInstance().clearBundlePlugins(bundle.getBundleId()); } @Override public void stop(final BundleContext context) throws Exception { // not much can be done that isn't already automated by the framework this.context.compareAndSet(context, null); + // TODO: shut down log4j } @Override public void bundleChanged(BundleEvent event) { switch (event.getType()) { + // FIXME: STARTING instead of STARTED? case BundleEvent.STARTED: scanBundleForPlugins(event.getBundle()); break; + case BundleEvent.STOPPING: + stopBundlePlugins(event.getBundle()); + break; + default: break; } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java index 5ed260c..531ae2e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java @@ -16,14 +16,6 @@ */ package org.apache.logging.log4j.core.pattern; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.util.PluginManager; @@ -31,6 +23,14 @@ import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + /** * Most of the work of the {@link org.apache.logging.log4j.core.layout.PatternLayout} class is delegated to the * PatternParser class. @@ -126,9 +126,9 @@ public final class PatternParser { final Class<?> filterClass) { this.config = config; final PluginManager manager = new PluginManager(converterKey); - manager.collectPlugins(); + manager.collectPlugins(config == null ? null : config.getPluginPackages()); final Map<String, PluginType<?>> plugins = manager.getPlugins(); - final Map<String, Class<PatternConverter>> converters = new HashMap<String, Class<PatternConverter>>(); + final Map<String, Class<PatternConverter>> converters = new LinkedHashMap<String, Class<PatternConverter>>(); for (final PluginType<?> type : plugins.values()) { try { @@ -140,7 +140,13 @@ public final class PatternParser { final ConverterKeys keys = clazz.getAnnotation(ConverterKeys.class); if (keys != null) { for (final String key : keys.value()) { - converters.put(key, clazz); + if (converters.containsKey(key)) { + LOGGER.warn("Converter key '{}' is already mapped to '{}'. " + + "Sorry, Dave, I can't let you do that! Ignoring plugin [{}].", + key, converters.get(key), clazz); + } else { + converters.put(key, clazz); + } } } } catch (final Exception ex) { http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java index 06ebded..100e859 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java @@ -17,10 +17,6 @@ package org.apache.logging.log4j.core.config.plugins.processor; -import java.net.URL; -import java.util.Enumeration; -import java.util.concurrent.ConcurrentMap; - import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAliases; import org.junit.BeforeClass; @@ -28,6 +24,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.net.URL; +import java.util.Enumeration; +import java.util.Map; + import static org.junit.Assert.*; @RunWith(JUnit4.class) @@ -47,7 +47,7 @@ public class PluginProcessorTest { @Test public void testTestCategoryFound() throws Exception { assertNotNull("No plugin annotation on FakePlugin.", p); - final ConcurrentMap<String, PluginEntry> testCategory = pluginCache.getCategory(p.category()); + final Map<String, PluginEntry> testCategory = pluginCache.getCategory(p.category()); assertNotEquals("No plugins were found.", 0, pluginCache.size()); assertNotNull("The category '" + p.category() + "' was not found.", testCategory); assertFalse(testCategory.isEmpty()); http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f4927364/src/site/xdoc/manual/plugins.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/manual/plugins.xml b/src/site/xdoc/manual/plugins.xml index c2fc338..eae201f 100644 --- a/src/site/xdoc/manual/plugins.xml +++ b/src/site/xdoc/manual/plugins.xml @@ -38,15 +38,26 @@ <p> In Log4j 2 a plugin is declared by adding a <code>@Plugin</code> annotation to the class declaration. During initialization the Configuration will invoke the PluginManager to load the built-in Log4j plugins - as well as any custom plugins. The PluginManager locates plugins by looking in two places: + as well as any custom plugins. The PluginManager locates plugins by looking in four places: </p> <ul> <li>Serialized plugin listing files on the classpath. These files are generated automatically during the build (more details below).</li> + <li>A comma-separated list of packages specified by the <code>log4j.plugin.packages</code> + system property.</li> + <li>Packages passed to the static <code>PluginManager.addPackages</code> method (before Log4j configuration + occurs).</li> <li>The <a href="./configuration.html#ConfigurationSyntax">packages</a> declared in your log4j2 configuration file.</li> </ul> <p> + If multiple Plugins specify the same (case-insensitive) <code>name</code>, then the load order above + determines which one will be used. For example, to override the <code>File</code> plugin which is provided + by the built-in <code>FileAppender</code> class, you would need to place your plugin in a JAR file in the + CLASSPATH ahead of<code>log4j-core.jar</code>. This is not recommended; plugin name collisions will cause a + warning to be emitted. + </p> + <p> Serialized plugin listing files are generated by an annotation processor contained in the log4j-core artifact which will automatically scan your code for Log4j 2 plugins and output a metadata file in your processed classes. There is nothing extra that needs to be done to enable this; the Java @@ -132,6 +143,13 @@ in the same fashion as a LogEventPatternConverter. These Converters are typically used by the RollingFileAppender to construct the name of the file to log to. </p> + <p> + If multiple Converters specify the same <code>ConverterKeys</code>, then the load order above determines + which one will be used. For example, to override the <code>%date</code> converter which is provided by the + built-in <code>DatePatternConverter</code> class, you would need to place your plugin in a JAR file in the + CLASSPATH ahead of<code>log4j-core.jar</code>. This is not recommended; pattern ConverterKeys collisions + will cause a warning to be emitted. Try to use unique ConverterKeys for your custom pattern converters. + </p> </subsection> <subsection name="KeyProviders"> <a name="KeyProviders"/>
