You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rg...@apache.org on 2019/06/09 17:50:07 UTC

[logging-log4j2] branch LOG4J2-2621 updated: LOG4J2-2621 - OSGi tests pass

This is an automated email from the ASF dual-hosted git repository.

rgoers pushed a commit to branch LOG4J2-2621
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/LOG4J2-2621 by this push:
     new b0eb3f9  LOG4J2-2621 - OSGi tests pass
b0eb3f9 is described below

commit b0eb3f950105ebb8389dbc0e87bb4e51a5396910
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Sun Jun 9 10:49:54 2019 -0700

    LOG4J2-2621 - OSGi tests pass
---
 .../java/org/apache/logging/log4j/LogManager.java  | 22 ++++-
 .../logging/log4j/spi/LoggerContextFactory.java    | 33 ++++++++
 log4j-core/pom.xml                                 |  6 ++
 .../log4j/core/impl/Log4jContextFactory.java       | 22 +++++
 .../apache/logging/log4j/core/osgi/Activator.java  | 62 +++------------
 .../log4j/core/osgi/BundleContextSelector.java     | 77 ++++++++++++++++++
 .../log4j/core/selector/BasicContextSelector.java  | 11 +++
 .../core/selector/ClassLoaderContextSelector.java  | 47 +++++++++++
 .../log4j/core/selector/ContextSelector.java       | 31 ++++++++
 .../log4j/core/selector/JndiContextSelector.java   | 36 ++++++++-
 .../org/apache/logging/log4j/core/util/Loader.java | 56 ++++++++-----
 .../log4j/osgi/tests/AbstractLoadBundleTest.java   | 67 +++++++++++-----
 log4j-plugins/pom.xml                              |  5 +-
 .../logging/log4j/plugins/osgi/Activator.java      | 93 ++++++++++++++++++----
 .../log4j/plugins/processor/PluginService.java     | 27 ++++---
 .../logging/log4j/plugins/util/PluginRegistry.java | 19 +++++
 .../logging/log4j/plugins/util/PluginType.java     |  4 +
 .../plugins/processor/PluginProcessorTest.java     | 26 +++++-
 18 files changed, 516 insertions(+), 128 deletions(-)

diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
index ecff443..2da1e84 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
@@ -387,7 +387,27 @@ public class LogManager {
      * @since 2.6
      */
     public static void shutdown(final boolean currentContext) {
-        shutdown(getContext(currentContext));
+        factory.shutdown(FQCN, null, currentContext, false);
+    }
+
+    /**
+     * Shutdown the logging system if the logging system supports it.
+     * This is equivalent to calling {@code LogManager.shutdown(LogManager.getContext(currentContext))}.
+     *
+     * This call is synchronous and will block until shut down is complete.
+     * This may include flushing pending log events over network connections.
+     *
+     * @param currentContext if true a default LoggerContext (may not be the LoggerContext used to create a Logger
+     *            for the calling class) will be used.
+     *            If false the LoggerContext appropriate for the caller of this method is used. For
+     *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
+     *            used and if the caller is a class in the container's classpath then a different LoggerContext may
+     *            be used.
+     * @param allContexts if true all LoggerContexts that can be located will be shutdown.
+     * @since 3.0
+     */
+    public static void shutdown(final boolean currentContext, final boolean allContexts) {
+        factory.shutdown(FQCN, null, currentContext, allContexts);
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
index 235ad0c..14aaa37 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.spi;
 
 import java.net.URI;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Implemented by factories that create {@link LoggerContext} objects.
@@ -24,6 +25,38 @@ import java.net.URI;
 public interface LoggerContextFactory {
 
     /**
+     * Shuts down the LoggerContext.
+     * @param fqcn The fully qualified class name of the caller.
+     * @param loader The ClassLoader to use or null.
+     * @param currentContext If true shuts down the current Context, if false shuts down the Context appropriate
+     * for the caller if a more appropriate Context can be determined.
+     * @param allContexts if true all LoggerContexts that can be located will be shutdown.
+     * @return true if a LoggerContext has been installed, false otherwise.
+     * @since 3.0
+     */
+    default void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+        if (hasContext(fqcn, loader, currentContext)) {
+            LoggerContext ctx = getContext(fqcn, loader, null, currentContext);
+            if (ctx instanceof Terminable) {
+                ((Terminable) ctx).terminate();
+            }
+        }
+    }
+
+    /**
+     * Checks to see if a LoggerContext is installed. The default implementation returns false.
+     * @param fqcn The fully qualified class name of the caller.
+     * @param loader The ClassLoader to use or null.
+     * @param currentContext If true returns the current Context, if false returns the Context appropriate
+     * for the caller if a more appropriate Context can be determined.
+     * @return true if a LoggerContext has been installed, false otherwise.
+     * @since 3.0
+     */
+    default boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+        return false;
+    }
+
+    /**
      * Creates a {@link LoggerContext}.
      *
      * @param fqcn The fully qualified class name of the caller.
diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index fcabc0b..4ac43c1 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -444,6 +444,12 @@
             <Import-Package>
               sun.reflect;resolution:=optional,
               org.apache.logging.log4j.util,
+              org.apache.logging.log4j.plugins,
+              org.apache.logging.log4j.plugins.convert,
+              org.apache.logging.log4j.plugins.processor,
+              org.apache.logging.log4j.plugins.util,
+              org.apache.logging.log4j.plugins.validation,
+              org.apache.logging.log4j.plugins.visitors,
               *
             </Import-Package>
             <Bundle-Activator>org.apache.logging.log4j.core.osgi.Activator</Bundle-Activator>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
index 42bd52a..e00eb29 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
@@ -36,6 +36,7 @@ import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
 import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.apache.logging.log4j.spi.Terminable;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.LoaderUtil;
 import org.apache.logging.log4j.util.PropertiesUtil;
@@ -284,6 +285,27 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba
         return ctx;
     }
 
+    @Override
+    public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+        if (selector.hasContext(fqcn, loader, currentContext)) {
+            selector.shutdown(fqcn, loader, currentContext, allContexts);
+        }
+    }
+
+    /**
+     * Checks to see if a LoggerContext is installed.
+     * @param fqcn The fully qualified class name of the caller.
+     * @param loader The ClassLoader to use or null.
+     * @param currentContext If true returns the current Context, if false returns the Context appropriate
+     * for the caller if a more appropriate Context can be determined.
+     * @return true if a LoggerContext has been installed, false otherwise.
+     * @since 3.0
+     */
+    @Override
+    public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+        return selector.hasContext(fqcn, loader, currentContext);
+    }
+
     /**
      * Returns the ContextSelector.
      * @return The ContextSelector.
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 afbefa5..4671f63 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
@@ -22,33 +22,33 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.plugins.util.PluginRegistry;
 import org.apache.logging.log4j.core.impl.Log4jProvider;
+import org.apache.logging.log4j.core.plugins.Log4jPlugins;
 import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.plugins.processor.PluginService;
 import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.PropertiesUtil;
-import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
 import org.osgi.framework.ServiceRegistration;
-import org.osgi.framework.SynchronousBundleListener;
-import org.osgi.framework.wiring.BundleWiring;
 
 /**
  * OSGi BundleActivator.
  */
-public final class Activator implements BundleActivator, SynchronousBundleListener {
+public final class Activator implements BundleActivator {
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private final AtomicReference<BundleContext> contextRef = new AtomicReference<>();
 
     ServiceRegistration provideRegistration = null;
+    ServiceRegistration pluginRegistration = null;
 
     @Override
     public void start(final BundleContext context) throws Exception {
+        pluginRegistration = context.registerService(PluginService.class.getName(), new Log4jPlugins(),
+                new Hashtable<>());
         final Provider provider = new Log4jProvider();
         final Hashtable<String, String> props = new Hashtable<>();
         props.put("APIVersion", "2.60");
@@ -57,58 +57,14 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
         if (PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR) == null) {
             System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, BundleContextSelector.class.getName());
         }
-        if (this.contextRef.compareAndSet(null, context)) {
-            context.addBundleListener(this);
-            // done after the BundleListener as to not miss any new bundle installs in the interim
-            scanInstalledBundlesForPlugins(context);
-        }
-    }
-
-    private static void scanInstalledBundlesForPlugins(final BundleContext context) {
-        final Bundle[] bundles = context.getBundles();
-        for (final Bundle bundle : bundles) {
-            // TODO: bundle state can change during this
-            scanBundleForPlugins(bundle);
-        }
-    }
-
-    private static void scanBundleForPlugins(final Bundle bundle) {
-        final long bundleId = bundle.getBundleId();
-        // LOG4J2-920: don't scan system bundle for plugins
-        if (bundle.getState() == Bundle.ACTIVE && bundleId != 0) {
-            LOGGER.trace("Scanning bundle [{}, id=%d] for plugins.", bundle.getSymbolicName(), bundleId);
-            PluginRegistry.getInstance().loadFromBundle(bundleId,
-                    bundle.adapt(BundleWiring.class).getClassLoader());
-        }
-    }
-
-    private static void stopBundlePlugins(final Bundle bundle) {
-        LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName());
-        // TODO: plugin lifecycle code
-        PluginRegistry.getInstance().clearBundlePlugins(bundle.getBundleId());
+        contextRef.compareAndSet(null, context);
     }
 
     @Override
     public void stop(final BundleContext context) throws Exception {
         provideRegistration.unregister();
+        pluginRegistration.unregister();
         this.contextRef.compareAndSet(context, null);
-        LogManager.shutdown();
-    }
-
-    @Override
-    public void bundleChanged(final 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;
-        }
+        LogManager.shutdown(false, true);
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
index 5e22922..7cb433f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.core.osgi;
 import java.lang.ref.WeakReference;
 import java.net.URI;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.logging.log4j.core.LoggerContext;
@@ -39,6 +40,76 @@ import org.osgi.framework.FrameworkUtil;
 public class BundleContextSelector extends ClassLoaderContextSelector {
 
     @Override
+    public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
+                         final boolean allContexts) {
+        LoggerContext ctx = null;
+        Bundle bundle = null;
+        if (currentContext) {
+            ctx = ContextAnchor.THREAD_CONTEXT.get();
+            ContextAnchor.THREAD_CONTEXT.remove();
+        }
+        if (ctx == null && loader instanceof BundleReference) {
+            bundle = ((BundleReference) loader).getBundle();
+            ctx = getLoggerContext(bundle);
+            removeLoggerContext(ctx);
+        }
+        if (ctx == null) {
+            final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
+            if (callerClass != null) {
+                bundle = FrameworkUtil.getBundle(callerClass);
+                ctx = getLoggerContext(FrameworkUtil.getBundle(callerClass));
+                removeLoggerContext(ctx);
+            }
+        }
+        if (ctx == null) {
+            ctx = ContextAnchor.THREAD_CONTEXT.get();
+            ContextAnchor.THREAD_CONTEXT.remove();
+        }
+        if (ctx != null) {
+            ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+        if (bundle != null && allContexts) {
+            final Bundle[] bundles = bundle.getBundleContext().getBundles();
+            for (final Bundle bdl : bundles) {
+                ctx = getLoggerContext(bdl);
+                if (ctx != null) {
+                    ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+                }
+            }
+        }
+
+
+    }
+
+    private LoggerContext getLoggerContext(final Bundle bundle) {
+        final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
+        final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
+        if (ref != null && ref.get() != null) {
+           return ref.get().get();
+        }
+        return null;
+    }
+
+    private void removeLoggerContext(LoggerContext context) {
+        CONTEXT_MAP.remove(context.getName());
+    }
+
+    @Override
+    public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
+        if (currentContext && ContextAnchor.THREAD_CONTEXT.get() != null) {
+            return true;
+        }
+        if (loader instanceof BundleReference) {
+            return hasContext(((BundleReference) loader).getBundle());
+        }
+        final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
+        if (callerClass != null) {
+            return hasContext(FrameworkUtil.getBundle(callerClass));
+        }
+        return ContextAnchor.THREAD_CONTEXT.get() != null;
+    }
+
+    @Override
     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
                                     final URI configLocation) {
         if (currentContext) {
@@ -60,6 +131,12 @@ public class BundleContextSelector extends ClassLoaderContextSelector {
         return lc == null ? getDefault() : lc;
     }
 
+    private static boolean hasContext(final Bundle bundle) {
+        final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
+        final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
+        return ref != null && ref.get() != null;
+    }
+
     private static LoggerContext locateContext(final Bundle bundle, final URI configLocation) {
         final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
         final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
index 502084d..1006a60 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
@@ -20,6 +20,7 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.impl.ContextAnchor;
@@ -32,6 +33,16 @@ public class BasicContextSelector implements ContextSelector {
     private static final LoggerContext CONTEXT = new LoggerContext("Default");
 
     @Override
+    public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+        ContextAnchor.THREAD_CONTEXT.get().stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+        return ContextAnchor.THREAD_CONTEXT.get() != null;
+    }
+
+    @Override
     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
 
         final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
index 8ffe5a5..3204e7e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.logging.log4j.core.LoggerContext;
@@ -53,6 +54,52 @@ public class ClassLoaderContextSelector implements ContextSelector {
             new ConcurrentHashMap<>();
 
     @Override
+    public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
+                         final boolean allContexts) {
+        LoggerContext ctx = null;
+        if (currentContext) {
+            ctx = ContextAnchor.THREAD_CONTEXT.get();
+        } else if (loader != null) {
+            ctx = findContext(loader);
+        } else {
+            final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
+            if (clazz != null) {
+                ctx = findContext(clazz.getClassLoader());
+            }
+            ctx = ContextAnchor.THREAD_CONTEXT.get();
+        }
+        if (ctx != null) {
+            ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    @Override
+    public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
+        if (currentContext) {
+            return ContextAnchor.THREAD_CONTEXT.get() != null;
+        } else if (loader != null) {
+            return findContext(loader) != null;
+        } else {
+            final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
+            if (clazz != null) {
+                return findContext(clazz.getClassLoader()) != null;
+            }
+            return ContextAnchor.THREAD_CONTEXT.get() != null;
+        }
+    }
+
+    private LoggerContext findContext(ClassLoader loaderOrNull) {
+        final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
+        final String name = toContextMapKey(loader);
+        AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
+        if (ref != null) {
+            final WeakReference<LoggerContext> weakRef = ref.get();
+            return weakRef.get();
+        }
+        return null;
+    }
+
+    @Override
     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
         return getContext(fqcn, loader, currentContext, null);
     }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
index 65c4dd7..41f7d39 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.selector;
 
 import java.net.URI;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.logging.log4j.core.LoggerContext;
 
@@ -26,6 +27,36 @@ import org.apache.logging.log4j.core.LoggerContext;
  */
 public interface ContextSelector {
 
+    long DEFAULT_STOP_TIMEOUT = 50;
+
+    /**
+     * Shuts down the LoggerContext.
+     * @param fqcn The fully qualified class name of the caller.
+     * @param loader The ClassLoader to use or null.
+     * @param currentContext If true returns the current Context, if false returns the Context appropriate
+     * @param allContexts if true all LoggerContexts that can be located will be shutdown.
+     * @since 3.0
+     */
+    default void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
+                          final boolean allContexts) {
+        if (hasContext(fqcn, loader, currentContext)) {
+           getContext(fqcn, loader, currentContext).stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    /**
+     * Checks to see if a LoggerContext is installed. The default implementation returns false.
+     * @param fqcn The fully qualified class name of the caller.
+     * @param loader The ClassLoader to use or null.
+     * @param currentContext If true returns the current Context, if false returns the Context appropriate
+     * for the caller if a more appropriate Context can be determined.
+     * @return true if a LoggerContext has been installed, false otherwise.
+     * @since 3.0
+     */
+    default boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+        return false;
+    }
+
     /**
      * Returns the LoggerContext.
      * @param fqcn The fully qualified class name of the caller.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
index b790eaf..67c32ae 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 import javax.naming.NamingException;
 
 import org.apache.logging.log4j.core.LoggerContext;
@@ -93,6 +94,32 @@ public class JndiContextSelector implements NamedContextSelector {
     private static final StatusLogger LOGGER = StatusLogger.getLogger();
 
     @Override
+    public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+        LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+        if (ctx == null) {
+            String loggingContextName = getContextName();
+            if (loggingContextName != null) {
+                ctx = CONTEXT_MAP.get(loggingContextName);
+            }
+        }
+        if (ctx != null) {
+            ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    @Override
+    public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+        if (ContextAnchor.THREAD_CONTEXT.get() != null) {
+            return true;
+        }
+        String loggingContextName = getContextName();
+        if (loggingContextName == null) {
+            return false;
+        }
+        return CONTEXT_MAP.containsKey(loggingContextName);
+    }
+
+    @Override
     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
         return getContext(fqcn, loader, currentContext, null);
     }
@@ -106,6 +133,12 @@ public class JndiContextSelector implements NamedContextSelector {
             return lc;
         }
 
+        String loggingContextName = getContextName();
+
+        return loggingContextName == null ? CONTEXT : locateContext(loggingContextName, null, configLocation);
+    }
+
+    private String getContextName() {
         String loggingContextName = null;
 
         try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
@@ -113,8 +146,7 @@ public class JndiContextSelector implements NamedContextSelector {
         } catch (final NamingException ne) {
             LOGGER.error("Unable to lookup {}", Constants.JNDI_CONTEXT_NAME, ne);
         }
-
-        return loggingContextName == null ? CONTEXT : locateContext(loggingContextName, null, configLocation);
+        return loggingContextName;
     }
 
     @Override
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
index 009116d..86932f6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
@@ -34,6 +34,9 @@ public final class Loader {
 
     private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
 
+    final static Boolean ignoreTccl =
+        Boolean.valueOf(PropertiesUtil.getProperties().getStringProperty(LoaderUtil.IGNORE_TCCL_PROPERTY, null));
+
     private Loader() {
     }
 
@@ -42,7 +45,7 @@ public final class Loader {
      * @return the ClassLoader.
      */
     public static ClassLoader getClassLoader() {
-        return LoaderUtil.getClassLoader(LoaderUtil.class, null);
+        return Loader.getClassLoader(Loader.class, null);
     }
 
     // TODO: this method could use some explanation
@@ -241,21 +244,12 @@ public final class Loader {
      * @throws InstantiationException if there was an exception whilst instantiating the class
      * @throws NoSuchMethodException if there isn't a no-args constructor on the class
      * @throws InvocationTargetException if there was an exception whilst constructing the class
+     * @since 2.1
      */
     @SuppressWarnings("unchecked")
-    public static <T> T newInstanceOf(final String className)
-        throws ClassNotFoundException,
-        IllegalAccessException,
-        InstantiationException,
-        NoSuchMethodException,
-        InvocationTargetException {
-        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-        try {
-            Thread.currentThread().setContextClassLoader(getClassLoader());
-            return LoaderUtil.newInstanceOf(className);
-        } finally {
-            Thread.currentThread().setContextClassLoader(contextClassLoader);
-        }
+    public static <T> T newInstanceOf(final String className) throws ClassNotFoundException, IllegalAccessException,
+            InstantiationException, NoSuchMethodException, InvocationTargetException {
+        return newInstanceOf((Class<T>) loadClass(className));
     }
 
     /**
@@ -281,7 +275,7 @@ public final class Loader {
         ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(getClassLoader());
-            return LoaderUtil.newCheckedInstanceOf(className, clazz);
+            return clazz.cast(newInstanceOf(className));
         } finally {
             Thread.currentThread().setContextClassLoader(contextClassLoader);
         }
@@ -315,6 +309,26 @@ public final class Loader {
     }
 
     /**
+     * Loads and instantiates a Class using the default constructor.
+     *
+     * @param clazz The class.
+     * @return new instance of the class.
+     * @throws IllegalAccessException if the class can't be instantiated through a public constructor
+     * @throws InstantiationException if there was an exception whilst instantiating the class
+     * @throws InvocationTargetException if there was an exception whilst constructing the class
+     * @since 2.7
+     */
+    public static <T> T newInstanceOf(final Class<T> clazz)
+            throws InstantiationException, IllegalAccessException, InvocationTargetException {
+        try {
+            return clazz.getConstructor().newInstance();
+        } catch (final NoSuchMethodException ignored) {
+            // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above
+            return clazz.newInstance();
+        }
+    }
+
+    /**
      * Determines if a named Class can be loaded or not.
      *
      * @param className The class name.
@@ -343,13 +357,13 @@ public final class Loader {
      * @throws ClassNotFoundException if the specified class name could not be found
      */
     public static Class<?> loadClass(final String className) throws ClassNotFoundException {
-
-        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        if (ignoreTccl) {
+            return Class.forName(className);
+        }
         try {
-            Thread.currentThread().setContextClassLoader(getClassLoader());
-            return LoaderUtil.loadClass(className);
-        } finally {
-            Thread.currentThread().setContextClassLoader(contextClassLoader);
+            return getClassLoader().loadClass(className);
+        } catch (final Throwable ignored) {
+            return Class.forName(className);
         }
     }
 }
diff --git a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
index cd7ba25..3d09f6b 100644
--- a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
+++ b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
@@ -37,6 +37,11 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
         return getBundleContext().installBundle(apiPath.toUri().toString());
     }
 
+    private Bundle getPluginsBundle() throws BundleException {
+        final Path apiPath = getHere().resolveSibling("log4j-plugins").resolve("target").resolve(getBundleTestInfo().buildJarFileName("log4j-plugins"));
+        return getBundleContext().installBundle(apiPath.toUri().toString());
+    }
+
 
     private Bundle getCoreBundle() throws BundleException {
         final Path corePath = getHere().resolveSibling("log4j-core").resolve("target").resolve(getBundleTestInfo().buildJarFileName("log4j-core"));
@@ -93,21 +98,24 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
         return oldStream;
     }
 
-    private void start(final Bundle api, final Bundle core, final Bundle dummy) throws BundleException {
+    private void start(final Bundle api, final Bundle plugins, final Bundle core, final Bundle dummy) throws BundleException {
         api.start();
+        plugins.start();
         core.start();
         dummy.start();        
     }
 
-    private void stop(final Bundle api, final Bundle core, final Bundle dummy) throws BundleException {
+    private void stop(final Bundle api, final Bundle plugins, final Bundle core, final Bundle dummy) throws BundleException {
         dummy.stop();
         core.stop();
+        plugins.stop();
         api.stop();
     }
     
-    private void uninstall(final Bundle api, final Bundle core, final Bundle dummy) throws BundleException {
+    private void uninstall(final Bundle api, final Bundle plugins, final Bundle core, final Bundle dummy) throws BundleException {
         dummy.uninstall();
         core.uninstall();
+        plugins.uninstall();
         api.uninstall();
     }
 
@@ -118,39 +126,51 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
     public void testApiCoreStartStopStartStop() throws BundleException {
 
         final Bundle api = getApiBundle();
+        final Bundle plugins = getPluginsBundle();
         final Bundle core = getCoreBundle();
         
         Assert.assertEquals("api is not in INSTALLED state", Bundle.INSTALLED, api.getState());
+        Assert.assertEquals("plugins is not in INSTALLED state", Bundle.INSTALLED, plugins.getState());
         Assert.assertEquals("core is not in INSTALLED state", Bundle.INSTALLED, core.getState());
 
         api.start();
+        plugins.start();
         core.start();
         
-        Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());        
+        Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());
+        Assert.assertEquals("plugins is not in ACTIVE state", Bundle.ACTIVE, plugins.getState());
         Assert.assertEquals("core is not in ACTIVE state", Bundle.ACTIVE, core.getState());        
         
         core.stop();
+        plugins.stop();
         api.stop();
         
         Assert.assertEquals("api is not in RESOLVED state", Bundle.RESOLVED, api.getState());
+        Assert.assertEquals("plugins is not in RESOLVED state", Bundle.RESOLVED, plugins.getState());
         Assert.assertEquals("core is not in RESOLVED state", Bundle.RESOLVED, core.getState());
-        
+
         api.start();
+        plugins.start();
         core.start();
-        
-        Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());        
-        Assert.assertEquals("core is not in ACTIVE state", Bundle.ACTIVE, core.getState());        
-        
+
+        Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());
+        Assert.assertEquals("plugins is not in ACTIVE state", Bundle.ACTIVE, plugins.getState());
+        Assert.assertEquals("core is not in ACTIVE state", Bundle.ACTIVE, core.getState());
+
         core.stop();
+        plugins.stop();
         api.stop();
-        
+
         Assert.assertEquals("api is not in RESOLVED state", Bundle.RESOLVED, api.getState());
+        Assert.assertEquals("plugins is not in RESOLVED state", Bundle.RESOLVED, plugins.getState());
         Assert.assertEquals("core is not in RESOLVED state", Bundle.RESOLVED, core.getState());
         
         core.uninstall();
+        plugins.uninstall();
         api.uninstall();
         
         Assert.assertEquals("api is not in UNINSTALLED state", Bundle.UNINSTALLED, api.getState());
+        Assert.assertEquals("plugins is not in UNINSTALLED state", Bundle.UNINSTALLED, plugins.getState());
         Assert.assertEquals("core is not in UNINSTALLED state", Bundle.UNINSTALLED, core.getState());
     }
 
@@ -161,9 +181,11 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
     public void testClassNotFoundErrorLogger() throws BundleException {
 
         final Bundle api = getApiBundle();
+        final Bundle plugins = getPluginsBundle();
         final Bundle core = getCoreBundle();
 
         api.start();
+        plugins.start();
         // fails if LOG4J2-1637 is not fixed
         try {
             core.start();
@@ -187,9 +209,11 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
         }
 
         core.stop();
+        plugins.stop();
         api.stop();
         
         core.uninstall();
+        plugins.uninstall();
         api.uninstall();
     }
 
@@ -200,10 +224,11 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
     public void testLoadingOfConfigurableCoreClasses() throws BundleException, ReflectiveOperationException {
 
         final Bundle api = getApiBundle();
+        final Bundle plugins = getPluginsBundle();
         final Bundle core = getCoreBundle();
         final Bundle dummy = getDummyBundle();
 
-        start(api, core, dummy);
+        start(api, plugins, core, dummy);
 
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
         final PrintStream logStream = new PrintStream(baos);
@@ -218,8 +243,8 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
         final boolean result = baos.toString().contains("BundleContextSelector cannot be found");
         Assert.assertFalse("Core class BundleContextSelector cannot be loaded in OSGI setup", result);
 
-        stop(api, core, dummy);
-        uninstall(api, core, dummy);
+        stop(api, plugins, core, dummy);
+        uninstall(api, plugins, core, dummy);
     }
 
     /**
@@ -229,10 +254,11 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
     public void testSimpleLogInAnOsgiContext() throws BundleException, ReflectiveOperationException {
 
         final Bundle api = getApiBundle();
+        final Bundle plugins = getPluginsBundle();
         final Bundle core = getCoreBundle();
         final Bundle dummy = getDummyBundle();
 
-        start(api, core, dummy);
+        start(api, plugins, core, dummy);
 
         final PrintStream bakStream = System.out;
         try {
@@ -244,14 +270,15 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
 
             final String result = baos.toString().substring(
                 12).trim(); // remove the instant then the spaces at start and end, that are non constant
-            Assert.assertEquals("[main] ERROR org.apache.logging.log4j.configuration.CustomConfiguration - Test OK",
-                result);
+            String expected = "[main] ERROR org.apache.logging.log4j.configuration.CustomConfiguration - Test OK";
+            Assert.assertTrue("Incorrect string. Expected string ends woth: " + expected + " Actual: " + result,
+                    result.endsWith(expected));
         } finally {
             System.setOut(bakStream);
         }
 
-        stop(api, core, dummy);
-        uninstall(api, core, dummy);
+        stop(api, plugins, core, dummy);
+        uninstall(api, plugins, core, dummy);
     }
 
 
@@ -263,10 +290,12 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
     public void testLog4J12Fragement() throws BundleException, ReflectiveOperationException {
 
         final Bundle api = getApiBundle();
+        final Bundle plugins = getPluginsBundle();
         final Bundle core = getCoreBundle();
         final Bundle compat = get12ApiBundle();
 
         api.start();
+        plugins.start();
         core.start();
         
         final Class<?> coreClassFromCore = core.loadClass("org.apache.logging.log4j.core.Core");
@@ -279,7 +308,7 @@ public abstract class AbstractLoadBundleTest extends AbstractOsgiTest {
         core.stop();
         api.stop();
 
-        uninstall(api, core, compat);
+        uninstall(api, plugins, core, compat);
     }
 
 }
diff --git a/log4j-plugins/pom.xml b/log4j-plugins/pom.xml
index 3551d51..5008c07 100644
--- a/log4j-plugins/pom.xml
+++ b/log4j-plugins/pom.xml
@@ -224,9 +224,10 @@
             <!-- TODO: exclude internal classes from export -->
             <Export-Package>org.apache.logging.log4j.plugins.*</Export-Package>
             <Import-Package>
-              sun.reflect;resolution:=optional,
+              org.apache.logging.log4j,
+              org.apache.logging.log4j.status,
               org.apache.logging.log4j.util,
-              *
+              org.osgi.framework.*
             </Import-Package>
             <Bundle-Activator>org.apache.logging.log4j.plugins.osgi.Activator</Bundle-Activator>
           </instructions>
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.java
index 06fb124..2364aa9 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.java
@@ -17,16 +17,16 @@
 
 package org.apache.logging.log4j.plugins.osgi;
 
-import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.plugins.processor.PluginService;
 import org.apache.logging.log4j.plugins.util.PluginRegistry;
-import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.PropertiesUtil;
 import org.osgi.framework.*;
 import org.osgi.framework.wiring.BundleWiring;
 
+import java.security.Permission;
+import java.util.Collection;
 import java.util.Hashtable;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -37,21 +37,77 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
-    private final AtomicReference<BundleContext> contextRef = new AtomicReference<>();
+    private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
 
-    ServiceRegistration provideRegistration = null;
+    private final AtomicReference<BundleContext> contextRef = new AtomicReference<>();
 
     @Override
-    public void start(final BundleContext context) throws Exception { /*
-        final PluginService pluginService = new Log4jProvider();
-        final Hashtable<String, String> props = new Hashtable<>();
-        props.put("APIVersion", "3.0");
-        provideRegistration = context.registerService(pluginService.class.getName(), provider, props);
-        if (this.contextRef.compareAndSet(null, context)) {
-            context.addBundleListener(this);
-            // done after the BundleListener as to not miss any new bundle installs in the interim
-            scanInstalledBundlesForPlugins(context);
-        } */
+    public void start(final BundleContext bundleContext) throws Exception {
+        loadPlugins(bundleContext);
+        bundleContext.addBundleListener(this);
+        final Bundle[] bundles = bundleContext.getBundles();
+        for (final Bundle bundle : bundles) {
+            loadPlugins(bundle);
+        }
+        scanInstalledBundlesForPlugins(bundleContext);
+        this.contextRef.compareAndSet(null, bundleContext);
+    }
+
+    private void loadPlugins(final BundleContext bundleContext) {
+        try {
+            final Collection<ServiceReference<PluginService>> serviceReferences = bundleContext.getServiceReferences(PluginService.class, null);
+            for (final ServiceReference<PluginService> serviceReference : serviceReferences) {
+                final PluginService pluginService = bundleContext.getService(serviceReference);
+                PluginRegistry.getInstance().loadFromBundle(pluginService.getCategories(), bundleContext.getBundle().getBundleId());
+            }
+        } catch (final InvalidSyntaxException ex) {
+            LOGGER.error("Error accessing Plugins", ex);
+        }
+    }
+
+    private void loadPlugins(final Bundle bundle) {
+        if (bundle.getState() == Bundle.UNINSTALLED) {
+            return;
+        }
+        try {
+            checkPermission(new AdminPermission(bundle, AdminPermission.RESOURCE));
+            checkPermission(new AdaptPermission(BundleWiring.class.getName(), bundle, AdaptPermission.ADAPT));
+            final BundleContext bundleContext = bundle.getBundleContext();
+            if (bundleContext == null) {
+                LOGGER.debug("Bundle {} has no context (state={}), skipping loading plugins", bundle.getSymbolicName(), toStateString(bundle.getState()));
+            } else {
+                loadPlugins(bundleContext);
+            }
+        } catch (final SecurityException e) {
+            LOGGER.debug("Cannot access bundle [{}] contents. Ignoring.", bundle.getSymbolicName(), e);
+        } catch (final Exception e) {
+            LOGGER.warn("Problem checking bundle {} for Log4j 2 provider.", bundle.getSymbolicName(), e);
+        }
+    }
+
+    private static void checkPermission(final Permission permission) {
+        if (SECURITY_MANAGER != null) {
+            SECURITY_MANAGER.checkPermission(permission);
+        }
+    }
+
+    private String toStateString(final int state) {
+        switch (state) {
+            case Bundle.UNINSTALLED:
+                return "UNINSTALLED";
+            case Bundle.INSTALLED:
+                return "INSTALLED";
+            case Bundle.RESOLVED:
+                return "RESOLVED";
+            case Bundle.STARTING:
+                return "STARTING";
+            case Bundle.STOPPING:
+                return "STOPPING";
+            case Bundle.ACTIVE:
+                return "ACTIVE";
+            default:
+                return Integer.toString(state);
+        }
     }
 
     private static void scanInstalledBundlesForPlugins(final BundleContext context) {
@@ -80,7 +136,11 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
 
     @Override
     public void stop(final BundleContext context) throws Exception {
-        provideRegistration.unregister();
+        final Bundle[] bundles = context.getBundles();
+        for (final Bundle bundle : bundles) {
+            stopBundlePlugins(bundle);
+        }
+        stopBundlePlugins(context.getBundle());
         this.contextRef.compareAndSet(context, null);
     }
 
@@ -89,6 +149,7 @@ public final class Activator implements BundleActivator, SynchronousBundleListen
         switch (event.getType()) {
             // FIXME: STARTING instead of STARTED?
             case BundleEvent.STARTED:
+                loadPlugins(event.getBundle());
                 scanBundleForPlugins(event.getBundle());
                 break;
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
index 5042456..ae9d094 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
@@ -16,37 +16,44 @@
  */
 package org.apache.logging.log4j.plugins.processor;
 
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import org.apache.logging.log4j.plugins.util.PluginType;
+
+import java.util.*;
 
 /**
  * Class Description goes here.
  */
 public abstract class PluginService {
 
-    private final Map<String, Map<String, PluginEntry>> categories = new LinkedHashMap<>();
+    private final Map<String, List<PluginType<?>>> categories = new LinkedHashMap<>();
 
     public PluginService() {
         PluginEntry[] entries = getEntries();
         for (PluginEntry entry : entries) {
             String category = entry.getCategory().toLowerCase();
             if (!categories.containsKey(category)) {
-                categories.put(category, new LinkedHashMap<>());
+                categories.put(category, new LinkedList<>());
+            }
+            try {
+                Class<?> clazz = this.getClass().getClassLoader().loadClass(entry.getClassName());
+                List<PluginType<?>> list = categories.get(category);
+                PluginType<?> type = new PluginType<>(entry, clazz, entry.getName());
+                list.add(type);
+            } catch (ClassNotFoundException ex) {
+                throw new IllegalStateException("No class named " + entry.getClassName() +
+                        " located for element " + entry.getName(), ex);
             }
-            Map<String, PluginEntry> map = categories.get(category);
-            map.put(entry.getKey(), entry);
         }
     }
 
     public abstract PluginEntry[] getEntries();
 
-    public Map<String, Map<String, PluginEntry>> getCategories() {
+    public Map<String, List<PluginType<?>>> getCategories() {
         return Collections.unmodifiableMap(categories);
     }
 
-    public Map<String, PluginEntry> getCategory(String category) {
-        return Collections.unmodifiableMap(categories.get(category.toLowerCase()));
+    public List<PluginType<?>> getCategory(String category) {
+        return Collections.unmodifiableList(categories.get(category.toLowerCase()));
     }
 
     public long size() {
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java
index 9e8c6e2..a759cf3 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java
@@ -36,6 +36,8 @@ import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Registry singleton for PluginType maps partitioned by source type and then by category names.
@@ -46,6 +48,7 @@ public class PluginRegistry {
 
     private static volatile PluginRegistry INSTANCE;
     private static final Object INSTANCE_LOCK = new Object();
+    protected static final Lock STARTUP_LOCK = new ReentrantLock();
 
     /**
      * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH.
@@ -154,6 +157,22 @@ public class PluginRegistry {
     }
 
     /**
+     * Loads all the plugins in a Bundle.
+     * @param categories All the categories in the bundle.
+     * @since 3.0
+     */
+    public void loadFromBundle(Map<String, List<PluginType<?>>> categories, Long bundleId) {
+        pluginsByCategoryByBundleId.put(bundleId, categories);
+        for (Map.Entry<String, List<PluginType<?>>> entry: categories.entrySet()) {
+            if (!categories.containsKey(entry.getKey())) {
+
+                categories.put(entry.getKey(), new LinkedList<>());
+            }
+            categories.get(entry.getKey()).addAll(entry.getValue());
+        }
+    }
+
+    /**
      * @since 3.0
      */
     public void loadPlugins(Map<String, List<PluginType<?>>> map) {
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.java
index c213f64..cd3fa49 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.java
@@ -40,6 +40,10 @@ public class PluginType<T> {
         this.elementName = elementName;
     }
 
+    public PluginEntry getPluginEntry() {
+        return this.pluginEntry;
+    }
+
     public Class<T> getPluginClass() {
         return this.pluginClass;
     }
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.java
index 034705c..b3bd425 100644
--- a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.java
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.java
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.plugins.processor;
 
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.util.PluginType;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,7 +50,7 @@ public class PluginProcessorTest {
     @Test
     public void testTestCategoryFound() throws Exception {
         assertNotNull("No plugin annotation on FakePlugin.", p);
-        final Map<String, PluginEntry> testCategory = pluginService.getCategory(p.category());
+        final List<PluginType<?>> testCategory = pluginService.getCategory(p.category());
         assertNotEquals("No plugins were found.", 0, pluginService.size());
         assertNotNull("The category '" + p.category() + "' was not found.", testCategory);
         assertFalse(testCategory.isEmpty());
@@ -57,7 +58,10 @@ public class PluginProcessorTest {
 
     @Test
     public void testFakePluginFoundWithCorrectInformation() throws Exception {
-        final PluginEntry fake = pluginService.getCategory(p.category()).get(p.name().toLowerCase());
+        final List<PluginType<?>> list = pluginService.getCategory(p.category());
+        assertNotNull(list);
+        final PluginEntry fake = getEntry(list, p.name());
+        assertNotNull(fake);
         verifyFakePluginEntry(p.name(), fake);
     }
 
@@ -65,7 +69,10 @@ public class PluginProcessorTest {
     public void testFakePluginAliasesContainSameInformation() throws Exception {
         final PluginAliases aliases = FakePlugin.class.getAnnotation(PluginAliases.class);
         for (final String alias : aliases.value()) {
-            final PluginEntry fake = pluginService.getCategory(p.category()).get(alias.toLowerCase());
+            final List<PluginType<?>> list = pluginService.getCategory(p.category());
+            assertNotNull(list);
+            final PluginEntry fake = getEntry(list, alias);
+            assertNotNull(fake);
             verifyFakePluginEntry(alias, fake);
         }
     }
@@ -83,7 +90,9 @@ public class PluginProcessorTest {
     @Test
     public void testNestedPlugin() throws Exception {
         final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class);
-        final PluginEntry nested = pluginService.getCategory(p.category()).get(p.name().toLowerCase());
+        final List<PluginType<?>> list = pluginService.getCategory(p.category());
+        assertNotNull(list);
+        final PluginEntry nested = getEntry(list, p.name());
         assertNotNull(nested);
         assertEquals(p.name().toLowerCase(), nested.getKey());
         assertEquals(FakePlugin.Nested.class.getName(), nested.getClassName());
@@ -92,4 +101,13 @@ public class PluginProcessorTest {
         assertEquals(p.printObject(), nested.isPrintable());
         assertEquals(p.deferChildren(), nested.isDefer());
     }
+
+    private PluginEntry getEntry(List<PluginType<?>> list, String name) {
+        for (PluginType<?> type : list) {
+            if (type.getPluginEntry().getName().equalsIgnoreCase(name)) {
+                return type.getPluginEntry();
+            }
+        }
+        return null;
+    }
 }