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"/>

Reply via email to