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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6802596386 LOG4J2-857 - Support PluginOrder annotation and cleanups
6802596386 is described below

commit 68025963868f1e17e7eab293112abfd67c5e2a67
Author: Matt Sicker <[email protected]>
AuthorDate: Sat May 14 13:16:33 2022 -0500

    LOG4J2-857 - Support PluginOrder annotation and cleanups
    
    - PluginManager is effectively renamed to PluginCategory with plugin 
collection logic moved to PluginRegistry
    - PluginBundle replaces a map of category names to plugin type lists
    - PluginOrder added for a generic plugin ordering annotation
    - MergeStrategy updated to be injectable
    
    Signed-off-by: Matt Sicker <[email protected]>
---
 .../org/apache/log4j/builders/BuilderManager.java  |  13 +-
 .../validators/RequiredValidatorTest.java          |   6 +-
 .../validators/ValidHostValidatorTest.java         |   6 +-
 .../validators/ValidPortValidatorTest.java         |   6 +-
 .../ValidatingPluginWithFailoverTest.java          |  10 +-
 .../ValidatingPluginWithGenericBuilderTest.java    |   6 +-
 ...ngPluginWithGenericSubclassFoo1BuilderTest.java |   6 +-
 .../ValidatingPluginWithTypedBuilderTest.java      |   6 +-
 .../java/org/apache/logging/log4j/core/Core.java   |   4 +-
 .../log4j/core/config/AbstractConfiguration.java   |  45 +++---
 .../log4j/core/config/ConfigurationFactory.java    |   8 +-
 .../core/config/DefaultConfigurationFactory.java   |  60 ++++---
 .../config/builder/impl/BuiltConfiguration.java    |   8 +-
 .../config/composite/CompositeConfiguration.java   |  20 +--
 .../config/composite/DefaultMergeStrategy.java     |  32 ++--
 .../log4j/core/config/composite/MergeStrategy.java |  13 +-
 .../log4j/core/config/jason/JsonConfiguration.java |   4 +-
 .../log4j/core/config/json/JsonConfiguration.java  |   4 +-
 .../log4j/core/config/xml/XmlConfiguration.java    |   2 +-
 .../logging/log4j/core/impl/DefaultCallback.java   |   8 +-
 .../logging/log4j/core/lookup/Interpolator.java    |  12 +-
 .../logging/log4j/core/lookup/StrLookup.java       |   4 +-
 .../logging/log4j/core/pattern/PatternParser.java  |  14 +-
 .../logging/log4j/core/util/SecretKeyProvider.java |   4 +-
 .../apache/logging/log4j/core/util/Watcher.java    |   4 +-
 .../logging/log4j/core/util/WatcherFactory.java    |   6 +-
 .../flume/appender/FlumePersistentManager.java     |  11 +-
 .../json/resolver/TemplateResolverFactories.java   |  25 ++-
 .../json/resolver/TemplateResolverFactory.java     |   4 +-
 .../json/resolver/TemplateResolverInterceptor.java |   4 +-
 .../resolver/TemplateResolverInterceptors.java     |  26 ++--
 .../log4j/plugin/processor/package-info.java       |   3 +-
 .../plugin/processor/PluginProcessorTest.java      |  54 +++----
 .../apache/logging/log4j/plugins/PluginOrder.java  |  57 +++++++
 .../log4j/plugins/convert/TypeConverter.java       |   4 +-
 .../logging/log4j/plugins/di/DefaultInjector.java  |  23 +--
 .../apache/logging/log4j/plugins/di/Injector.java  |   1 +
 .../logging/log4j/plugins/di/LookupSelector.java   |  25 ---
 .../logging/log4j/plugins/osgi/Activator.java      |  17 +-
 .../log4j/plugins/processor/PluginService.java     |  26 ++--
 .../logging/log4j/plugins/util/PluginBundle.java   |  88 +++++++++++
 .../logging/log4j/plugins/util/PluginCategory.java | 173 +++++++++++++++++++++
 .../logging/log4j/plugins/util/PluginManager.java  | 167 --------------------
 .../logging/log4j/plugins/util/PluginRegistry.java | 134 ++++++++--------
 .../logging/log4j/plugins/util/PluginType.java     |   5 +-
 src/site/asciidoc/manual/plugins.adoc              |  27 +---
 46 files changed, 639 insertions(+), 546 deletions(-)

diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java
index 249ca940fd..29a71bae85 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java
@@ -34,13 +34,13 @@ import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.w3c.dom.Element;
 
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
 import java.util.function.Function;
@@ -56,18 +56,18 @@ public class BuilderManager {
     public static final Filter INVALID_FILTER = new FilterWrapper(null);
     public static final Layout INVALID_LAYOUT = new LayoutWrapper(null);
     public static final RewritePolicy INVALID_REWRITE_POLICY = new 
RewritePolicyWrapper(null);
-    public static final Key<PluginManager> PLUGIN_MANAGER_KEY = new 
@Named(CATEGORY) Key<>() {};
+    public static final Key<PluginCategory> PLUGIN_CATEGORY_KEY = new 
@Named(CATEGORY) Key<>() {};
     private static final Logger LOGGER = StatusLogger.getLogger();
     private static final Class<?>[] CONSTRUCTOR_PARAMS = new Class[] { 
String.class, Properties.class };
     private final Injector injector;
-    private final Map<String, PluginType<?>> plugins;
+    private final PluginCategory plugins;
 
     /**
      * Constructs a new instance.
      */
     public BuilderManager(final Injector injector) {
         this.injector = injector;
-        plugins = injector.getInstance(PLUGIN_MANAGER_KEY).getPlugins();
+        plugins = injector.getInstance(PLUGIN_CATEGORY_KEY);
     }
 
     private <T extends Builder<U>, U> T createBuilder(final PluginType<T> 
plugin, final String prefix, final Properties props) {
@@ -92,7 +92,6 @@ public class BuilderManager {
         }
     }
 
-    @SuppressWarnings("unchecked")
     private <T> PluginType<T> getPlugin(final String className) {
         Objects.requireNonNull(plugins, "plugins");
         Objects.requireNonNull(className, "className");
@@ -101,7 +100,7 @@ public class BuilderManager {
         if (pluginType == null) {
             LOGGER.warn("Unable to load plugin class name {} with key {}", 
className, key);
         }
-        return (PluginType<T>) pluginType;
+        return TypeUtil.cast(pluginType);
     }
 
     private <T extends Builder<U>, U> U newInstance(final PluginType<T> 
plugin, final Function<T, U> consumer,
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
index 68f513750b..4d28b88ef9 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
 import org.apache.logging.log4j.plugins.test.validation.ValidatingPlugin;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
 import org.junit.jupiter.api.BeforeEach;
@@ -41,8 +41,8 @@ public class RequiredValidatorTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = injector.getInstance(new 
@Named("Test") Key<>() {});
-        final PluginType<?> pluginType = 
pluginManager.getPluginType("Validator");
+        final PluginCategory category = injector.getInstance(new 
@Named("Test") Key<>() {});
+        final PluginType<?> pluginType = category.get("Validator");
         assertNotNull(pluginType, "Rebuild this module to make sure annotation 
processing kicks in.");
         node = new Node(null, "Validator", pluginType);
     }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
index 3b152f19ab..6a3be20e34 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
 import org.apache.logging.log4j.plugins.test.validation.HostAndPort;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
 import org.junit.jupiter.api.BeforeEach;
@@ -41,8 +41,8 @@ public class ValidHostValidatorTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = injector.getInstance(new 
@Named("Test") Key<>() {});
-        PluginType<?> plugin = pluginManager.getPluginType("HostAndPort");
+        final PluginCategory category = injector.getInstance(new 
@Named("Test") Key<>() {});
+        PluginType<?> plugin = category.get("HostAndPort");
         assertNotNull(plugin, "Rebuild this module to ensure annotation 
processing has been done.");
         node = new Node(null, "HostAndPort", plugin);
     }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
index 3df06e9b70..1a17bb1ca9 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
@@ -22,7 +22,7 @@ import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
 import org.junit.jupiter.api.BeforeEach;
@@ -40,8 +40,8 @@ public class ValidPortValidatorTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = injector.getInstance(new 
@Named("Test") Key<>() {});
-        PluginType<?> plugin = pluginManager.getPluginType("HostAndPort");
+        final PluginCategory category = injector.getInstance(new 
@Named("Test") Key<>() {});
+        PluginType<?> plugin = category.get("HostAndPort");
         assertNotNull(plugin, "Rebuild this module to ensure annotation 
processing has been done.");
         node = new Node(null, "HostAndPort", plugin);
         node.getAttributes().put("host", "localhost");
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java
index a619c0144b..b2e0b3d0dc 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java
@@ -28,7 +28,7 @@ import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.status.StatusData;
 import org.apache.logging.log4j.status.StatusListener;
@@ -51,14 +51,14 @@ public class ValidatingPluginWithFailoverTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = 
injector.getInstance(Core.PLUGIN_MANAGER_KEY);
-        PluginType<?> plugin = pluginManager.getPluginType("Failover");
+        final PluginCategory category = 
injector.getInstance(Core.PLUGIN_CATEGORY_KEY);
+        PluginType<?> plugin = category.get("Failover");
         assertNotNull(plugin, "Rebuild this module to make sure annotation 
processing kicks in.");
 
         AppenderRef appenderRef = AppenderRef.createAppenderRef("List", 
Level.ALL, null);
         node = new Node(null, "failover", plugin);
-        Node failoversNode = new Node(node, "Failovers", 
pluginManager.getPluginType("Failovers"));
-        Node appenderRefNode  = new Node(failoversNode, "appenderRef", 
pluginManager.getPluginType("appenderRef"));
+        Node failoversNode = new Node(node, "Failovers", 
category.get("Failovers"));
+        Node appenderRefNode  = new Node(failoversNode, "appenderRef", 
category.get("appenderRef"));
         appenderRefNode.getAttributes().put("ref", "file");
         appenderRefNode.setObject(appenderRef);
         failoversNode.getChildren().add(appenderRefNode);
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
index bf5fc57dfc..cd3fbd0b6c 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
 import 
org.apache.logging.log4j.plugins.test.validation.ValidatingPluginWithGenericBuilder;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -40,8 +40,8 @@ public class ValidatingPluginWithGenericBuilderTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = injector.getInstance(new 
@Named("Test") Key<>() {});
-        final var plugin = 
pluginManager.getPluginType("ValidatingPluginWithGenericBuilder");
+        final PluginCategory category = injector.getInstance(new 
@Named("Test") Key<>() {});
+        final var plugin = category.get("ValidatingPluginWithGenericBuilder");
         assertNotNull(plugin, "Rebuild this module to make sure annotation 
processing kicks in.");
         node = new Node(null, "Validator", plugin);
     }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
index 313ea56724..25c5d1ca43 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
 import 
org.apache.logging.log4j.plugins.test.validation.PluginWithGenericSubclassFoo1Builder;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -40,8 +40,8 @@ public class 
ValidatingPluginWithGenericSubclassFoo1BuilderTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = injector.getInstance(new 
@Named("Test") Key<>() {});
-        final var plugin = 
pluginManager.getPluginType("PluginWithGenericSubclassFoo1Builder");
+        final PluginCategory category = injector.getInstance(new 
@Named("Test") Key<>() {});
+        final var plugin = 
category.get("PluginWithGenericSubclassFoo1Builder");
         assertNotNull(plugin, "Rebuild this module to make sure annotation 
processing kicks in.");
         node = new Node(null, "Validator", plugin);
     }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
index 6e284c7ad4..2a4b536d97 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
@@ -23,7 +23,7 @@ import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
 import 
org.apache.logging.log4j.plugins.test.validation.ValidatingPluginWithTypedBuilder;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.test.junit.StatusLoggerLevel;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -40,8 +40,8 @@ public class ValidatingPluginWithTypedBuilderTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        final PluginManager pluginManager = injector.getInstance(new 
@Named("Test") Key<>() {});
-        final var plugin = 
pluginManager.getPluginType("ValidatingPluginWithTypedBuilder");
+        final PluginCategory category = injector.getInstance(new 
@Named("Test") Key<>() {});
+        final var plugin = category.get("ValidatingPluginWithTypedBuilder");
         assertNotNull(plugin, "Rebuild this module to make sure annotation 
processing kicks in.");
         node = new Node(null, "Validator", plugin);
     }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java
index 0886a81337..831db35bab 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java
@@ -19,12 +19,12 @@ package org.apache.logging.log4j.core;
 
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 public class Core {
 
     public static final String CATEGORY_NAME = "Core";
 
-    public static final Key<PluginManager> PLUGIN_MANAGER_KEY = new 
@Named(CATEGORY_NAME) Key<>() {};
+    public static final Key<PluginCategory> PLUGIN_CATEGORY_KEY = new 
@Named(CATEGORY_NAME) Key<>() {};
 
 }
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 c575b45bd3..a3b00119e2 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
@@ -57,7 +57,7 @@ import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
 import org.apache.logging.log4j.plugins.di.Keys;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.util.LazyValue;
@@ -106,9 +106,9 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
     protected final List<String> pluginPackages = new ArrayList<>();
 
     /**
-     * The plugin manager.
+     * Core plugins.
      */
-    protected PluginManager pluginManager;
+    protected PluginCategory corePlugins;
 
     /**
      * Shutdown hook is enabled by default.
@@ -200,13 +200,13 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
         injector.registerBinding(ScriptManager.KEY, this::getScriptManager);
     }
 
-    public PluginManager getPluginManager() {
-        return pluginManager;
+    public PluginCategory getCorePlugins() {
+        return corePlugins;
     }
 
-    public void setPluginManager(final PluginManager pluginManager) {
-        this.pluginManager = pluginManager;
-        injector.registerBinding(Core.PLUGIN_MANAGER_KEY, 
this::getPluginManager);
+    public void setCorePlugins(final PluginCategory corePlugins) {
+        this.corePlugins = corePlugins;
+        injector.registerBinding(Core.PLUGIN_CATEGORY_KEY, 
this::getCorePlugins);
     }
 
     @Override
@@ -249,21 +249,18 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
         configurationStrSubstitutor.setConfiguration(this);
         initializeScriptManager();
         injector.registerBindingIfAbsent(Keys.PLUGIN_PACKAGES_KEY, 
this::getPluginPackages);
-        pluginManager = injector.getInstance(Core.PLUGIN_MANAGER_KEY);
-        final PluginManager levelPlugins = injector.getInstance(new 
@Named(Level.CATEGORY) Key<>() {});
-        final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
-        if (plugins != null) {
-            for (final PluginType<?> type : plugins.values()) {
-                final Class<?> pluginClass = type.getPluginClass();
-                try {
-                    // Cause the class to be initialized if it isn't already.
-                    Class.forName(pluginClass.getName(), true, 
pluginClass.getClassLoader());
-                } catch (final Exception e) {
-                    LOGGER.error("Unable to initialize {} due to {}", 
pluginClass.getName(), e.getClass()
-                            .getSimpleName(), e);
-                }
+        corePlugins = injector.getInstance(Core.PLUGIN_CATEGORY_KEY);
+        final PluginCategory levelPlugins = injector.getInstance(new 
@Named(Level.CATEGORY) Key<>() {});
+        levelPlugins.forEach(type -> {
+            final Class<?> pluginClass = type.getPluginClass();
+            try {
+                // Cause the class to be initialized if it isn't already.
+                Class.forName(pluginClass.getName(), true, 
pluginClass.getClassLoader());
+            } catch (final Exception e) {
+                LOGGER.error("Unable to initialize {} due to {}", 
pluginClass.getName(), e.getClass()
+                        .getSimpleName(), e);
             }
-        }
+        });
         setup();
         setupAdvertisement();
         doConfigure();
@@ -312,7 +309,7 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
             final Source cfgSource = new Source(configSource);
             final Key<WatcherFactory> key = Key.forClass(WatcherFactory.class);
             injector.registerBindingIfAbsent(key, LazyValue.from(() ->
-                    new 
WatcherFactory(injector.getInstance(Watcher.PLUGIN_MANAGER_KEY).getPlugins())));
+                    new 
WatcherFactory(injector.getInstance(Watcher.PLUGIN_CATEGORY_KEY))));
             final Watcher watcher = injector.getInstance(key)
                     .newWatcher(cfgSource, this, reconfigurable, listeners, 
configSource.getLastModified());
             if (watcher != null) {
@@ -512,7 +509,7 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
     private void setupAdvertisement() {
         if (advertiserNode != null) {
             final String nodeName = advertiserNode.getName();
-            final PluginType<?> type = pluginManager.getPluginType(nodeName);
+            final PluginType<?> type = corePlugins.get(nodeName);
             if (type != null) {
                 advertiser = 
injector.getInstance(type.getPluginClass().asSubclass(Advertiser.class));
                 advertisement = 
advertiser.advertise(advertiserNode.getAttributes());
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
index e764231c0e..c856a5c97e 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
@@ -16,8 +16,6 @@
  */
 package org.apache.logging.log4j.core.config;
 
-import java.net.URI;
-
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import 
org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
@@ -28,11 +26,13 @@ import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.LoaderUtil;
 import org.apache.logging.log4j.util.PropertiesUtil;
 
+import java.net.URI;
+
 /**
  * Factory class for parsed {@link Configuration} objects from a configuration 
file.
  * ConfigurationFactory allows the configuration implementation to be
@@ -84,7 +84,7 @@ public abstract class ConfigurationFactory extends 
ConfigurationBuilderFactory {
 
     public static final Key<ConfigurationFactory> KEY = new Key<>() {};
 
-    public static final Key<PluginManager> PLUGIN_MANAGER_KEY = new 
@Named(CATEGORY) Key<>() {};
+    public static final Key<PluginCategory> PLUGIN_CATEGORY_KEY = new 
@Named(CATEGORY) Key<>() {};
 
     /**
      * Allows subclasses access to the status logger without creating another 
instance.
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java
index 3f0a7902f0..bdcad7fb94 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java
@@ -37,8 +37,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * Default factory for using a plugin selected based on the configuration 
source.
@@ -294,44 +292,38 @@ public class DefaultConfigurationFactory extends 
ConfigurationFactory {
     }
 
     private static List<ConfigurationFactory> loadConfigurationFactories(final 
Injector injector) {
-        final Stream<? extends ConfigurationFactory> 
primaryConfigurationFactoryStream =
-                
Optional.ofNullable(PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY))
-                        
.flatMap(DefaultConfigurationFactory::tryLoadFactoryClass)
-                        .map(clazz -> {
-                            try {
-                                return injector.getInstance(clazz);
-                            } catch (final Exception ex) {
-                                LOGGER.error("Unable to create instance of 
{}", clazz, ex);
-                                return null;
-                            }
-                        })
-                        .stream();
+        final List<ConfigurationFactory> factories = new ArrayList<>();
 
-        final Stream<? extends ConfigurationFactory> 
pluginConfigurationFactoryStream = injector.getInstance(PLUGIN_MANAGER_KEY)
-                .getPlugins()
-                .values()
-                .stream()
-                .flatMap(type -> {
-                    try {
-                        final Class<? extends ConfigurationFactory> clazz =
-                                
type.getPluginClass().asSubclass(ConfigurationFactory.class);
-                        return Stream.of(clazz);
-                    } catch (final Exception ex) {
-                        LOGGER.warn("Unable to add class {}", 
type.getPluginClass(), ex);
-                        return Stream.empty();
-                    }
-                })
-                .sorted(OrderComparator.getInstance())
-                .flatMap(clazz -> {
+        
Optional.ofNullable(PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY))
+                .flatMap(DefaultConfigurationFactory::tryLoadFactoryClass)
+                .map(clazz -> {
                     try {
-                        return Stream.of(injector.getInstance(clazz));
+                        return injector.getInstance(clazz);
                     } catch (final Exception ex) {
                         LOGGER.error("Unable to create instance of {}", clazz, 
ex);
-                        return Stream.empty();
+                        return null;
                     }
-                });
+                })
+                .ifPresent(factories::add);
+
+        final List<Class<? extends ConfigurationFactory>> 
configurationFactoryPluginClasses = new ArrayList<>();
+        injector.getInstance(PLUGIN_CATEGORY_KEY).forEach(type -> {
+            try {
+                
configurationFactoryPluginClasses.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
+            } catch (final Exception ex) {
+                LOGGER.warn("Unable to add class {}", type.getPluginClass(), 
ex);
+            }
+        });
+        configurationFactoryPluginClasses.sort(OrderComparator.getInstance());
+        configurationFactoryPluginClasses.forEach(clazz -> {
+            try {
+                factories.add(injector.getInstance(clazz));
+            } catch (final Exception ex) {
+                LOGGER.error("Unable to create instance of {}", clazz, ex);
+            }
+        });
 
-        return Stream.concat(primaryConfigurationFactoryStream, 
pluginConfigurationFactoryStream).collect(Collectors.toList());
+        return factories;
     }
 
     private static Optional<Class<? extends ConfigurationFactory>> 
tryLoadFactoryClass(final String factoryClass) {
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
index 7ee1d71876..76b27ad0e9 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
@@ -24,7 +24,6 @@ import 
org.apache.logging.log4j.core.config.builder.api.Component;
 import org.apache.logging.log4j.core.config.status.StatusConfiguration;
 import org.apache.logging.log4j.core.util.Patterns;
 import org.apache.logging.log4j.plugins.Node;
-import org.apache.logging.log4j.plugins.util.PluginManager;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.plugins.util.ResolverUtil;
 
@@ -154,14 +153,9 @@ public class BuiltConfiguration extends 
AbstractConfiguration {
         }
     }
 
-    @Override
-    public PluginManager getPluginManager() {
-        return pluginManager;
-    }
-
     protected Node convertToNode(final Node parent, final Component component) 
{
         final String name = component.getPluginType();
-        final PluginType<?> pluginType = pluginManager.getPluginType(name);
+        final PluginType<?> pluginType = corePlugins.get(name);
         final Node node = new Node(parent, name, pluginType);
         node.getAttributes().putAll(component.getAttributes());
         node.setValue(component.getValue());
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
index 3a947d3a2f..536101d223 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
@@ -23,16 +23,13 @@ import 
org.apache.logging.log4j.core.config.ConfigurationFactory;
 import org.apache.logging.log4j.core.config.ConfigurationSource;
 import org.apache.logging.log4j.core.config.Reconfigurable;
 import org.apache.logging.log4j.core.config.status.StatusConfiguration;
-import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.core.util.Patterns;
 import org.apache.logging.log4j.core.util.Source;
 import org.apache.logging.log4j.core.util.WatchManager;
 import org.apache.logging.log4j.core.util.Watcher;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.util.ResolverUtil;
-import org.apache.logging.log4j.util.PropertiesUtil;
 
-import java.lang.reflect.InvocationTargetException;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -53,7 +50,7 @@ public class CompositeConfiguration extends 
AbstractConfiguration implements Rec
 
     private final List<? extends AbstractConfiguration> configurations;
 
-    private MergeStrategy mergeStrategy;
+    private final MergeStrategy mergeStrategy;
 
     /**
      * Construct the CompositeConfiguration.
@@ -64,14 +61,7 @@ public class CompositeConfiguration extends 
AbstractConfiguration implements Rec
         super(configurations.get(0).getLoggerContext(), 
ConfigurationSource.COMPOSITE_SOURCE);
         rootNode = configurations.get(0).getRootNode();
         this.configurations = configurations;
-        final String mergeStrategyClassName = 
PropertiesUtil.getProperties().getStringProperty(MERGE_STRATEGY_PROPERTY,
-                DefaultMergeStrategy.class.getName());
-        try {
-            mergeStrategy = Loader.newInstanceOf(mergeStrategyClassName);
-        } catch (final ClassNotFoundException | IllegalAccessException | 
InvocationTargetException |
-                InstantiationException ex) {
-            mergeStrategy = new DefaultMergeStrategy();
-        }
+        mergeStrategy = getComponent(MergeStrategy.KEY);
         for (final AbstractConfiguration config : configurations) {
             mergeStrategy.mergeRootProperties(rootNode, config);
         }
@@ -116,7 +106,7 @@ public class CompositeConfiguration extends 
AbstractConfiguration implements Rec
         for (final AbstractConfiguration sourceConfiguration : 
configurations.subList(1, configurations.size())) {
             staffChildConfiguration(sourceConfiguration);
             final Node sourceRoot = sourceConfiguration.getRootNode();
-            mergeStrategy.mergeConfigurations(rootNode, sourceRoot, 
getPluginManager());
+            mergeStrategy.mergeConfigurations(rootNode, sourceRoot, 
corePlugins);
             if (LOGGER.isEnabled(Level.ALL)) {
                 final StringBuilder sb = new StringBuilder();
                 printNodes("", rootNode, sb);
@@ -164,7 +154,7 @@ public class CompositeConfiguration extends 
AbstractConfiguration implements Rec
     }
 
     private void staffChildConfiguration(final AbstractConfiguration 
childConfiguration) {
-        childConfiguration.setPluginManager(pluginManager);
+        childConfiguration.setCorePlugins(corePlugins);
         childConfiguration.setScriptManager(scriptManager);
         childConfiguration.setup();
     }
@@ -181,7 +171,7 @@ public class CompositeConfiguration extends 
AbstractConfiguration implements Rec
     public String toString() {
         return getClass().getName() + "@" + Integer.toHexString(hashCode()) + 
" [configurations=" + configurations
                 + ", mergeStrategy=" + mergeStrategy + ", rootNode=" + 
rootNode + ", listeners=" + listeners
-                + ", pluginPackages=" + pluginPackages + ", pluginManager=" + 
pluginManager + ", isShutdownHookEnabled="
+                + ", pluginPackages=" + pluginPackages + ", corePlugins=" + 
corePlugins + ", isShutdownHookEnabled="
                 + isShutdownHookEnabled + ", shutdownTimeoutMillis=" + 
shutdownTimeoutMillis + ", scriptManager="
                 + scriptManager + "]";
     }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java
index 343d315f67..c040343fb1 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java
@@ -16,19 +16,19 @@
  */
 package org.apache.logging.log4j.core.config.composite;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.config.AbstractConfiguration;
 import org.apache.logging.log4j.core.filter.CompositeFilter;
 import org.apache.logging.log4j.plugins.Node;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * The default merge strategy for composite configurations.
  * <p>
@@ -108,19 +108,19 @@ public class DefaultMergeStrategy implements 
MergeStrategy {
     /**
      * Merge the source Configuration into the target Configuration.
      *
-     * @param target        The target node to merge into.
-     * @param source        The source node.
-     * @param pluginManager The PluginManager.
+     * @param target      The target node to merge into.
+     * @param source      The source node.
+     * @param corePlugins The Core plugins to use.
      */
     @Override
-    public void mergeConfigurations(final Node target, final Node source, 
final PluginManager pluginManager) {
+    public void mergeConfigurations(final Node target, final Node source, 
final PluginCategory corePlugins) {
         for (final Node sourceChildNode : source.getChildren()) {
             final boolean isFilter = isFilterNode(sourceChildNode);
             boolean isMerged = false;
             for (final Node targetChildNode : target.getChildren()) {
                 if (isFilter) {
                     if (isFilterNode(targetChildNode)) {
-                        updateFilterNode(target, targetChildNode, 
sourceChildNode, pluginManager);
+                        updateFilterNode(target, targetChildNode, 
sourceChildNode, corePlugins);
                         isMerged = true;
                         break;
                     }
@@ -163,7 +163,7 @@ public class DefaultMergeStrategy implements MergeStrategy {
                                         for (final Node targetChild : 
targetNode.getChildren()) {
                                             if (isFilterNode(targetChild)) {
                                                 updateFilterNode(loggerNode, 
targetChild, sourceLoggerChild,
-                                                        pluginManager);
+                                                        corePlugins);
                                                 foundFilter = true;
                                                 break;
                                             }
@@ -240,14 +240,14 @@ public class DefaultMergeStrategy implements 
MergeStrategy {
     }
 
     private void updateFilterNode(final Node target, final Node 
targetChildNode, final Node sourceChildNode,
-            final PluginManager pluginManager) {
+            final PluginCategory corePlugins) {
         if 
(CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass()))
 {
             final Node node = new Node(targetChildNode, 
sourceChildNode.getName(), sourceChildNode.getType());
             node.getChildren().addAll(sourceChildNode.getChildren());
             node.getAttributes().putAll(sourceChildNode.getAttributes());
             targetChildNode.getChildren().add(node);
         } else {
-            final PluginType pluginType = pluginManager.getPluginType(FILTERS);
+            final PluginType<?> pluginType = corePlugins.get(FILTERS);
             final Node filtersNode = new Node(targetChildNode, FILTERS, 
pluginType);
             final Node node = new Node(filtersNode, sourceChildNode.getName(), 
sourceChildNode.getType());
             node.getAttributes().putAll(sourceChildNode.getAttributes());
@@ -266,11 +266,11 @@ public class DefaultMergeStrategy implements 
MergeStrategy {
 
     private boolean isSameName(final Node node1, final Node node2) {
         final String value = node1.getAttributes().get(NAME);
-        return value != null && 
value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase());
+        return value != null && 
value.equalsIgnoreCase(node2.getAttributes().get(NAME));
     }
 
     private boolean isSameReference(final Node node1, final Node node2) {
         final String value = node1.getAttributes().get(REF);
-        return value != null && 
value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase());
+        return value != null && 
value.equalsIgnoreCase(node2.getAttributes().get(REF));
     }
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java
index 415c1de138..bb3014b572 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java
@@ -18,13 +18,16 @@ package org.apache.logging.log4j.core.config.composite;
 
 import org.apache.logging.log4j.core.config.AbstractConfiguration;
 import org.apache.logging.log4j.plugins.Node;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 /**
  * Merges two configurations together
  */
 public interface MergeStrategy {
 
+    Key<MergeStrategy> KEY = new Key<>() {};
+
     /**
      * Merge the root node properties into the configuration.
      * @param rootNode The composite root node.
@@ -34,8 +37,10 @@ public interface MergeStrategy {
 
     /**
      * Merge the source node tree into the target node tree.
-     * @param target The target Node tree.
-     * @param source The source Node tree.
+     *
+     * @param target      The target Node tree.
+     * @param source      The source Node tree.
+     * @param corePlugins The Core plugins to merge.
      */
-    void mergeConfigurations(Node target, Node source, PluginManager 
pluginManager);
+    void mergeConfigurations(Node target, Node source, PluginCategory 
corePlugins);
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java
index c50c156d92..d18ba75862 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java
@@ -113,7 +113,7 @@ public class JsonConfiguration extends 
AbstractConfiguration implements Reconfig
     }
 
     private Node constructNode(final String key, final Node parent, final 
Map<String, Object> value) {
-        final PluginType<?> pluginType = pluginManager.getPluginType(key);
+        final PluginType<?> pluginType = corePlugins.get(key);
         final Node node = new Node(parent, key, pluginType);
         processAttributes(node, value);
         final List<Node> children = node.getChildren();
@@ -133,7 +133,7 @@ public class JsonConfiguration extends 
AbstractConfiguration implements Reconfig
                     if (object instanceof Map<?, ?>) {
                         final Map<String, Object> map = TypeUtil.cast(object);
                         final String type = getType(map).orElse(k);
-                        final PluginType<?> entryType = 
pluginManager.getPluginType(type);
+                        final PluginType<?> entryType = corePlugins.get(type);
                         final Node child = new Node(node, k, entryType);
                         processAttributes(child, map);
                         if (type.equalsIgnoreCase(k)) {
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 7406448c68..268c2bbdd5 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
@@ -144,7 +144,7 @@ public class JsonConfiguration extends 
AbstractConfiguration implements Reconfig
     }
 
     private Node constructNode(final String name, final Node parent, final 
JsonNode jsonNode) {
-        final PluginType<?> type = pluginManager.getPluginType(name);
+        final PluginType<?> type = corePlugins.get(name);
         final Node node = new Node(parent, name, type);
         processAttributes(node, jsonNode);
         final Iterator<Map.Entry<String, JsonNode>> iter = jsonNode.fields();
@@ -160,7 +160,7 @@ public class JsonConfiguration extends 
AbstractConfiguration implements Reconfig
                     LOGGER.debug("Processing node for array {}", 
entry.getKey());
                     for (int i = 0; i < n.size(); ++i) {
                         final String pluginType = getType(n.get(i), 
entry.getKey());
-                        final PluginType<?> entryType = 
pluginManager.getPluginType(pluginType);
+                        final PluginType<?> entryType = 
corePlugins.get(pluginType);
                         final Node item = new Node(node, entry.getKey(), 
entryType);
                         processAttributes(item, n.get(i));
                         if (pluginType.equals(entry.getKey())) {
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 9440ec1d98..25ae2a715e 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
@@ -287,7 +287,7 @@ public class XmlConfiguration extends AbstractConfiguration 
implements Reconfigu
             if (w3cNode instanceof Element) {
                 final Element child = (Element) w3cNode;
                 final String name = getType(child);
-                final PluginType<?> type = pluginManager.getPluginType(name);
+                final PluginType<?> type = corePlugins.get(name);
                 final Node childNode = new Node(node, name, type);
                 constructHierarchy(childNode, child);
                 if (type == null) {
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java
index aa83f153d5..d1872e5d86 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java
@@ -23,6 +23,9 @@ import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
 import org.apache.logging.log4j.core.config.DefaultConfigurationFactory;
+import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
+import org.apache.logging.log4j.core.config.composite.DefaultMergeStrategy;
+import org.apache.logging.log4j.core.config.composite.MergeStrategy;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.InterpolatorFactory;
 import org.apache.logging.log4j.core.lookup.StrLookup;
@@ -126,10 +129,13 @@ public class DefaultCallback implements InjectorCallback {
                                         DefaultLogEventFactory.class))
                 
.registerBindingIfAbsent(Key.forClass(InterpolatorFactory.class),
                         () -> defaultLookup -> new Interpolator(defaultLookup,
-                                
injector.getInstance(StrLookup.PLUGIN_MANAGER_KEY).getPlugins(), 
injector::getInstance))
+                                
injector.getInstance(StrLookup.PLUGIN_CATEGORY_KEY), injector::getInstance))
                 .registerBindingIfAbsent(Key.forClass(StrSubstitutor.class),
                         () -> new 
StrSubstitutor(injector.getInstance(InterpolatorFactory.class).newInterpolator(null)))
                 .registerBindingIfAbsent(ConfigurationFactory.KEY, 
injector.getFactory(DefaultConfigurationFactory.class))
+                .registerBindingIfAbsent(MergeStrategy.KEY,
+                        () -> 
loader.getInstance(CompositeConfiguration.MERGE_STRATEGY_PROPERTY, 
MergeStrategy.class,
+                                () -> DefaultMergeStrategy.class))
                 .registerBindingIfAbsent(Constants.DEFAULT_STATUS_LEVEL_KEY, 
() -> {
                     final String statusLevel =
                             
properties.getStringProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL, 
Level.ERROR.name());
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 387b0e0969..561e596ed1 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
@@ -22,7 +22,7 @@ import 
org.apache.logging.log4j.core.config.ConfigurationAware;
 import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Keys;
-import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.LazyValue;
 
@@ -75,12 +75,10 @@ public class Interpolator extends 
AbstractConfigurationAwareLookup {
         this.defaultLookup = defaultLookup == null ? new 
PropertiesLookup(Map.of()) : defaultLookup;
         final Injector injector = DI.createInjector();
         injector.registerBinding(Keys.PLUGIN_PACKAGES_KEY, () -> 
pluginPackages);
-        injector.getInstance(PLUGIN_MANAGER_KEY)
-                .getPlugins()
+        injector.getInstance(PLUGIN_CATEGORY_KEY)
                 .forEach((key, value) -> {
                     try {
-                        strLookups.put(key.toLowerCase(Locale.ROOT),
-                                
injector.getFactory(value.getPluginClass().asSubclass(StrLookup.class)));
+                        strLookups.put(key, 
injector.getFactory(value.getPluginClass().asSubclass(StrLookup.class)));
                     } catch (final Throwable t) {
                         handleError(key, t);
                     }
@@ -88,13 +86,13 @@ public class Interpolator extends 
AbstractConfigurationAwareLookup {
     }
 
     public Interpolator(
-            final StrLookup defaultLookup, final Map<String, PluginType<?>> 
strLookupPlugins,
+            final StrLookup defaultLookup, final PluginCategory 
strLookupPlugins,
             final Function<Class<? extends StrLookup>, StrLookup> 
pluginLoader) {
         this.defaultLookup = defaultLookup == null ? new 
PropertiesLookup(Map.of()) : defaultLookup;
         strLookupPlugins.forEach((key, value) -> {
             try {
                 final Class<? extends StrLookup> strLookupClass = 
value.getPluginClass().asSubclass(StrLookup.class);
-                strLookups.put(key.toLowerCase(Locale.ROOT), LazyValue.from(() 
-> pluginLoader.apply(strLookupClass)));
+                strLookups.put(key, LazyValue.from(() -> 
pluginLoader.apply(strLookupClass)));
             } catch (final Throwable t) {
                 handleError(key, t);
             }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java
index 5ef6886964..f5e571085f 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java
@@ -19,7 +19,7 @@ package org.apache.logging.log4j.core.lookup;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 /**
  * Lookup a String key to a String value.
@@ -42,7 +42,7 @@ public interface StrLookup {
      */
     String CATEGORY = "Lookup";
 
-    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+    Key<PluginCategory> PLUGIN_CATEGORY_KEY = new @Named(CATEGORY) Key<>() {};
 
     /**
      * Looks up a String key to a String value.
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 03627b6f44..9b6713b5ff 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
@@ -22,7 +22,7 @@ import org.apache.logging.log4j.core.time.SystemNanoClock;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.DI;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
@@ -88,7 +88,7 @@ public final class PatternParser {
 
     private static final int DECIMAL = 10;
 
-    private static final Key<PluginManager> PLUGIN_MANAGER_KEY = 
Key.forClass(PluginManager.class).withQualifierType(Named.class);
+    private static final Key<PluginCategory> PLUGIN_CATEGORY_KEY = 
Key.forClass(PluginCategory.class).withQualifierType(Named.class);
 
     private final Configuration config;
 
@@ -133,17 +133,17 @@ public final class PatternParser {
     public PatternParser(final Configuration config, final String 
converterKey, final Class<?> expectedClass,
             final Class<?> filterClass) {
         this.config = config;
-        final Map<String, PluginType<?>> plugins;
-        final Key<PluginManager> pluginManagerKey = 
PLUGIN_MANAGER_KEY.withName(converterKey);
+        final PluginCategory plugins;
+        final Key<PluginCategory> pluginCategoryKey = 
PLUGIN_CATEGORY_KEY.withName(converterKey);
         if (config == null) {
-            plugins = 
DI.createInjector().getInstance(pluginManagerKey).getPlugins();
+            plugins = DI.createInjector().getInstance(pluginCategoryKey);
         } else {
-            plugins = config.getComponent(pluginManagerKey).getPlugins();
+            plugins = config.getComponent(pluginCategoryKey);
         }
 
         final Map<String, Class<? extends PatternConverter>> converters = new 
LinkedHashMap<>();
 
-        for (final PluginType<?> type : plugins.values()) {
+        for (final PluginType<?> type : plugins) {
             try {
                 final Class<? extends PatternConverter> clazz = 
type.getPluginClass().asSubclass(PatternConverter.class);
                 if (filterClass != null && 
!filterClass.isAssignableFrom(clazz)) {
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SecretKeyProvider.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SecretKeyProvider.java
index 58dae4bda6..66a87ec586 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SecretKeyProvider.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SecretKeyProvider.java
@@ -18,7 +18,7 @@ package org.apache.logging.log4j.core.util;
 
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 import javax.crypto.SecretKey;
 
@@ -29,7 +29,7 @@ public interface SecretKeyProvider {
 
     String CATEGORY = "KeyProvider";
 
-    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+    Key<PluginCategory> PLUGIN_CATEGORY_KEY = new @Named(CATEGORY) Key<>() {};
 
     /**
      * Returns this SecretKey.
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
index e02bb09fc0..b4d2be06a7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
@@ -20,7 +20,7 @@ import 
org.apache.logging.log4j.core.config.ConfigurationListener;
 import org.apache.logging.log4j.core.config.Reconfigurable;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 import java.util.List;
 
@@ -32,7 +32,7 @@ import java.util.List;
 public interface Watcher {
 
     String CATEGORY = "Watcher";
-    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+    Key<PluginCategory> PLUGIN_CATEGORY_KEY = new @Named(CATEGORY) Key<>() {};
     String ELEMENT_TYPE = "watcher";
 
     /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
index 343815ce8a..e692a9f2b9 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
@@ -21,13 +21,13 @@ import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationFileWatcher;
 import org.apache.logging.log4j.core.config.ConfigurationListener;
 import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.status.StatusLogger;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -37,9 +37,9 @@ public class WatcherFactory {
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
-    private final Map<String, PluginType<?>> plugins;
+    private final PluginCategory plugins;
 
-    public WatcherFactory(final Map<String, PluginType<?>> watcherPlugins) {
+    public WatcherFactory(final PluginCategory watcherPlugins) {
         plugins = watcherPlugins;
     }
 
diff --git 
a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
 
b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
index 8ddf166315..bb58c0ad0c 100644
--- 
a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
+++ 
b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
@@ -39,6 +39,7 @@ import org.apache.logging.log4j.core.util.Log4jThread;
 import org.apache.logging.log4j.core.util.Log4jThreadFactory;
 import org.apache.logging.log4j.core.util.SecretKeyProvider;
 import org.apache.logging.log4j.plugins.di.Injector;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.util.Strings;
 
@@ -433,14 +434,14 @@ public class FlumePersistentManager extends 
FlumeAvroManager {
                 }
                 if (key != null) {
                     final Injector injector = data.injector;
-                    final Map<String, PluginType<?>> plugins =
-                            
injector.getInstance(SecretKeyProvider.PLUGIN_MANAGER_KEY).getPlugins();
+                    final PluginCategory plugins =
+                            
injector.getInstance(SecretKeyProvider.PLUGIN_CATEGORY_KEY);
                     if (plugins != null) {
                         boolean found = false;
-                        for (final Map.Entry<String, PluginType<?>> entry : 
plugins.entrySet()) {
-                            if (entry.getKey().equalsIgnoreCase(key)) {
+                        for (final PluginType<?> type : plugins) {
+                            if (key.equalsIgnoreCase(type.getKey())) {
                                 found = true;
-                                final Class<?> cl = 
entry.getValue().getPluginClass();
+                                final Class<?> cl = type.getPluginClass();
                                 try {
                                     final SecretKeyProvider provider =
                                             
injector.getInstance(cl.asSubclass(SecretKeyProvider.class));
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
index 2afa8449a9..57c0187143 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactories.java
@@ -19,12 +19,13 @@ package 
org.apache.logging.log4j.layout.template.json.resolver;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.status.StatusLogger;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Utility class for {@link TemplateResolverFactory}.
@@ -47,24 +48,24 @@ public final class TemplateResolverFactories {
             final Class<C> contextClass) {
 
         // Populate template resolver factories.
-        final Map<String, PluginType<?>> pluginTypeByName =
-                
configuration.getComponent(TemplateResolverFactory.PLUGIN_MANAGER_KEY).getPlugins();
+        final PluginCategory factoryPlugins =
+                
configuration.getComponent(TemplateResolverFactory.PLUGIN_CATEGORY_KEY);
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(
                     "found {} plugins of category \"{}\": {}",
-                    pluginTypeByName.size(),
+                    factoryPlugins.size(),
                     TemplateResolverFactory.CATEGORY,
-                    pluginTypeByName.keySet());
+                    factoryPlugins.getPluginKeys());
         }
 
         // Filter matching resolver factories.
         final Map<String, F> factoryByName =
-                populateFactoryByName(pluginTypeByName, configuration, 
valueClass, contextClass);
+                populateFactoryByName(factoryPlugins, configuration, 
valueClass, contextClass);
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(
                     "matched {} resolver factories out of {} for value class 
{} and context class {}: {}",
                     factoryByName.size(),
-                    pluginTypeByName.size(),
+                    factoryPlugins.size(),
                     valueClass,
                     contextClass,
                     factoryByName.keySet());
@@ -74,14 +75,12 @@ public final class TemplateResolverFactories {
     }
 
     private static <V, C extends TemplateResolverContext<V, C>, F extends 
TemplateResolverFactory<V, C>> Map<String, F> populateFactoryByName(
-            final Map<String, PluginType<?>> pluginTypeByName,
+            final PluginCategory factoryPlugins,
             final Configuration configuration,
             final Class<V> valueClass,
             final Class<C> contextClass) {
         final Map<String, F> factoryByName = new LinkedHashMap<>();
-        final Set<String> pluginNames = pluginTypeByName.keySet();
-        for (final String pluginName : pluginNames) {
-            final PluginType<?> pluginType = pluginTypeByName.get(pluginName);
+        for (final PluginType<?> pluginType : factoryPlugins) {
             final Class<?> pluginClass = pluginType.getPluginClass();
             final boolean pluginClassMatched =
                     
TemplateResolverFactory.class.isAssignableFrom(pluginClass);
@@ -111,9 +110,7 @@ public final class TemplateResolverFactories {
         final boolean factoryContextClassMatched =
                 contextClass.isAssignableFrom(factoryContextClass);
         if (factoryValueClassMatched && factoryContextClassMatched) {
-            @SuppressWarnings("unchecked")
-            final F typedFactory = (F) factory;
-            return typedFactory;
+            return TypeUtil.cast(factory);
         }
         return null;
     }
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
index 2553f07fe3..170bc46378 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverFactory.java
@@ -18,7 +18,7 @@ package 
org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 /**
  * {@link TemplateResolver} factory.
@@ -33,7 +33,7 @@ public interface TemplateResolverFactory<V, C extends 
TemplateResolverContext<V,
      */
     String CATEGORY = "JsonTemplateResolverFactory";
 
-    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+    Key<PluginCategory> PLUGIN_CATEGORY_KEY = new @Named(CATEGORY) Key<>() {};
 
     /**
      * The targeted value class.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.java
index d11178ea1d..ad30d684d4 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptor.java
@@ -18,7 +18,7 @@ package 
org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 
 /**
  * Main {@link TemplateResolver} compilation interception interface.
@@ -33,7 +33,7 @@ public interface TemplateResolverInterceptor<V, C extends 
TemplateResolverContex
      */
     String CATEGORY = "JsonTemplateResolverInterceptor";
 
-    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+    Key<PluginCategory> PLUGIN_CATEGORY_KEY = new @Named(CATEGORY) Key<>() {};
 
     /**
      * The targeted value class.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java
index 340d3cd1c8..d793c8f139 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolverInterceptors.java
@@ -19,13 +19,13 @@ package 
org.apache.logging.log4j.layout.template.json.resolver;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.plugins.di.Key;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.status.StatusLogger;
 
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * Utility class for {@link TemplateResolverInterceptor}.
@@ -48,23 +48,23 @@ public class TemplateResolverInterceptors {
             final Class<C> contextClass) {
 
         // Populate interceptors.
-        final Map<String, PluginType<?>> pluginTypeByName =
-                
configuration.getComponent(TemplateResolverInterceptor.PLUGIN_MANAGER_KEY).getPlugins();
+        final PluginCategory interceptorPlugins =
+                
configuration.getComponent(TemplateResolverInterceptor.PLUGIN_CATEGORY_KEY);
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(
                     "found {} plugins of category \"{}\": {}",
-                    pluginTypeByName.size(),
+                    interceptorPlugins.size(),
                     TemplateResolverInterceptor.CATEGORY,
-                    pluginTypeByName.keySet());
+                    interceptorPlugins.getPluginKeys());
         }
 
         // Filter matching interceptors.
         final List<I> interceptors =
-                populateInterceptors(pluginTypeByName, configuration, 
valueClass, contextClass);
+                populateInterceptors(interceptorPlugins, configuration, 
valueClass, contextClass);
         LOGGER.debug(
                 "{} interceptors matched out of {} for value class {} and 
context class {}",
                 interceptors.size(),
-                pluginTypeByName.size(),
+                interceptorPlugins.size(),
                 valueClass,
                 contextClass);
         return interceptors;
@@ -72,14 +72,12 @@ public class TemplateResolverInterceptors {
     }
 
     private static <V, C extends TemplateResolverContext<V, C>, I extends 
TemplateResolverInterceptor<V, C>> List<I> populateInterceptors(
-            final Map<String, PluginType<?>> pluginTypeByName,
+            final PluginCategory interceptorPlugins,
             final Configuration configuration,
             final Class<V> valueClass,
             final Class<C> contextClass) {
         final List<I> interceptors = new LinkedList<>();
-        final Set<String> pluginNames = pluginTypeByName.keySet();
-        for (final String pluginName : pluginNames) {
-            final PluginType<?> pluginType = pluginTypeByName.get(pluginName);
+        for (final PluginType<?> pluginType : interceptorPlugins) {
             final Class<?> pluginClass = pluginType.getPluginClass();
             final boolean pluginClassMatched =
                     
TemplateResolverInterceptor.class.isAssignableFrom(pluginClass);
@@ -110,9 +108,7 @@ public class TemplateResolverInterceptors {
         final boolean interceptorContextClassMatched =
                 contextClass.isAssignableFrom(interceptorContextClass);
         if (interceptorValueClassMatched && interceptorContextClassMatched) {
-            @SuppressWarnings({"unchecked"})
-            final I typedInterceptor = (I) interceptor;
-            return typedInterceptor;
+            return TypeUtil.cast(interceptor);
         }
         return null;
     }
diff --git 
a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/package-info.java
 
b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/package-info.java
index b67bf604e0..5e4b0ae449 100644
--- 
a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/package-info.java
+++ 
b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/package-info.java
@@ -16,7 +16,6 @@
  */
 
 /**
- * Java annotation processor for pre-scanning Log4j 2 plugins. This is 
provided as an alternative to using the
- * executable {@link org.apache.logging.log4j.plugins.util.PluginManager} 
class in your build process.
+ * Java annotation processor for pre-scanning Log4j 2 plugins.
  */
 package org.apache.logging.log4j.plugin.processor;
diff --git 
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
index 8d8f02e97a..c3e03c468b 100644
--- 
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
+++ 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugin/processor/PluginProcessorTest.java
@@ -19,7 +19,6 @@ package org.apache.logging.log4j.plugin.processor;
 
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginAliases;
-import org.apache.logging.log4j.plugins.processor.PluginEntry;
 import org.apache.logging.log4j.plugins.processor.PluginService;
 import org.apache.logging.log4j.plugins.test.validation.FakePlugin;
 import org.apache.logging.log4j.plugins.test.validation.plugins.Log4jPlugins;
@@ -29,8 +28,6 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.List;
-
 import static org.junit.Assert.*;
 
 @RunWith(JUnit4.class)
@@ -48,7 +45,7 @@ public class PluginProcessorTest {
     @Test
     public void testTestCategoryFound() throws Exception {
         assertNotNull("No plugin annotation on FakePlugin.", p);
-        final List<PluginType<?>> testCategory = 
pluginService.getCategory(p.category());
+        final var 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());
@@ -56,56 +53,47 @@ public class PluginProcessorTest {
 
     @Test
     public void testFakePluginFoundWithCorrectInformation() throws Exception {
-        final List<PluginType<?>> list = 
pluginService.getCategory(p.category());
-        assertNotNull(list);
-        final PluginEntry fake = getEntry(list, p.name());
-        assertNotNull(fake);
-        verifyFakePluginEntry(p.name(), fake);
+        final var testCategory = pluginService.getCategory(p.category());
+        assertNotNull(testCategory);
+        final PluginType<?> type = testCategory.get(p.name());
+        assertNotNull(type);
+        verifyFakePluginEntry(p.name(), type);
     }
 
     @Test
     public void testFakePluginAliasesContainSameInformation() throws Exception 
{
         final PluginAliases aliases = 
FakePlugin.class.getAnnotation(PluginAliases.class);
         for (final String alias : aliases.value()) {
-            final List<PluginType<?>> list = 
pluginService.getCategory(p.category());
-            assertNotNull(list);
-            final PluginEntry fake = getEntry(list, alias);
-            assertNotNull(fake);
-            verifyFakePluginEntry(alias, fake);
+            final var testCategory = pluginService.getCategory(p.category());
+            assertNotNull(testCategory);
+            final PluginType<?> type = testCategory.get(alias);
+            assertNotNull(type);
+            verifyFakePluginEntry(alias, type);
         }
     }
 
-    private void verifyFakePluginEntry(final String name, final PluginEntry 
fake) {
+    private void verifyFakePluginEntry(final String name, final PluginType<?> 
fake) {
         assertNotNull("The plugin '" + name.toLowerCase() + "' was not 
found.", fake);
-        assertEquals(FakePlugin.class.getName(), fake.getClassName());
+        assertEquals(FakePlugin.class.getName(), 
fake.getPluginEntry().getClassName());
         assertEquals(name.toLowerCase(), fake.getKey());
         assertEquals(Plugin.EMPTY, p.elementType());
         assertEquals(name, fake.getName());
-        assertEquals(p.printObject(), fake.isPrintable());
-        assertEquals(p.deferChildren(), fake.isDefer());
+        assertEquals(p.printObject(), fake.isObjectPrintable());
+        assertEquals(p.deferChildren(), fake.isDeferChildren());
     }
 
     @Test
     public void testNestedPlugin() throws Exception {
         final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class);
-        final List<PluginType<?>> list = 
pluginService.getCategory(p.category());
-        assertNotNull(list);
-        final PluginEntry nested = getEntry(list, p.name());
+        final var testCategory = pluginService.getCategory(p.category());
+        assertNotNull(testCategory);
+        final PluginType<?> nested = testCategory.get(p.name());
         assertNotNull(nested);
         assertEquals(p.name().toLowerCase(), nested.getKey());
-        assertEquals(FakePlugin.Nested.class.getName(), nested.getClassName());
+        assertEquals(FakePlugin.Nested.class.getName(), 
nested.getPluginEntry().getClassName());
         assertEquals(p.name(), nested.getName());
         assertEquals(Plugin.EMPTY, p.elementType());
-        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;
+        assertEquals(p.printObject(), nested.isObjectPrintable());
+        assertEquals(p.deferChildren(), nested.isDeferChildren());
     }
 }
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginOrder.java 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginOrder.java
new file mode 100644
index 0000000000..f8bb60afee
--- /dev/null
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginOrder.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Comparator;
+
+/**
+ * Specifies the order in which the annotated class should be considered in 
when processing plugins.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PluginOrder {
+    int value();
+
+    int FIRST = Integer.MIN_VALUE;
+
+    int LAST = Integer.MAX_VALUE;
+
+    Comparator<Class<?>> COMPARATOR = (lhs, rhs) -> {
+        if (lhs == rhs) {
+            return 0;
+        }
+        final PluginOrder lhsOrder = lhs.getAnnotation(PluginOrder.class);
+        final PluginOrder rhsOrder = rhs.getAnnotation(PluginOrder.class);
+        if (lhsOrder != null && rhsOrder != null) {
+            return Integer.compare(lhsOrder.value(), rhsOrder.value());
+        } else if (lhsOrder != null) {
+            return -1;
+        } else if (rhsOrder != null) {
+            return 1;
+        } else {
+            // natural ordering
+            return -1;
+        }
+    };
+}
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
index 781f32118e..271150150d 100644
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
@@ -20,7 +20,7 @@ package org.apache.logging.log4j.plugins.convert;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.status.StatusLogger;
 
@@ -38,7 +38,7 @@ public interface TypeConverter<T> {
      */
     String CATEGORY = "TypeConverter";
 
-    Key<PluginManager> PLUGIN_MANAGER_KEY = new @Named(CATEGORY) Key<>() {};
+    Key<PluginCategory> PLUGIN_CATEGORY_KEY = new @Named(CATEGORY) Key<>() {};
 
     /**
      * Converts a String to a given type.
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
index 4ed098f61f..2ae9342a63 100644
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
@@ -29,7 +29,8 @@ import org.apache.logging.log4j.plugins.convert.TypeConverter;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
 import org.apache.logging.log4j.plugins.util.AnnotationUtil;
-import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
+import org.apache.logging.log4j.plugins.util.PluginRegistry;
 import org.apache.logging.log4j.plugins.util.PluginType;
 import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.plugins.validation.Constraint;
@@ -81,7 +82,7 @@ class DefaultInjector implements Injector {
 
     DefaultInjector() {
         bindingMap = new BindingMap();
-        bindingMap.put(Key.forClass(Injector.class), () -> this);
+        bindingMap.put(KEY, () -> this);
         scopes.put(Singleton.class, new SingletonScope());
     }
 
@@ -249,12 +250,16 @@ class DefaultInjector implements Injector {
         }
         final Class<T> rawType = key.getRawType();
         final Scope scope = getScopeForType(rawType);
-        if (rawType == PluginManager.class && key.getQualifierType() == 
Named.class) {
+        if (rawType == PluginCategory.class && key.getQualifierType() == 
Named.class) {
             final Supplier<T> factory = () -> {
-                final var manager = new PluginManager(key.getName());
+                final String categoryName = key.getName();
                 final Binding<List<String>> pluginPackagesBinding = 
bindingMap.get(Keys.PLUGIN_PACKAGES_KEY, List.of());
-                manager.collectPlugins(pluginPackagesBinding != null ? 
pluginPackagesBinding.getSupplier().get() : List.of());
-                return TypeUtil.cast(manager);
+                final List<String> pluginPackages = pluginPackagesBinding != 
null ? pluginPackagesBinding.getSupplier().get() : List.of();
+                // TODO: can this be a bean, too? might be tricky in OSGi
+                final var registry = PluginRegistry.getInstance();
+                // Then, iterate over packages registered in PluginManager
+                final var category = registry.getCategory(categoryName, 
pluginPackages);
+                return TypeUtil.cast(category);
             };
             bindingMap.put(key, scope.get(key, factory));
             return bindingMap.get(key, aliases).getSupplier();
@@ -298,13 +303,13 @@ class DefaultInjector implements Injector {
     }
 
     private void initializeTypeConverters() {
-        final PluginManager manager = 
getInstance(TypeConverter.PLUGIN_MANAGER_KEY);
-        for (final PluginType<?> knownType : manager.getPlugins().values()) {
+        final PluginCategory category = 
getInstance(TypeConverter.PLUGIN_CATEGORY_KEY);
+        category.forEach(knownType -> {
             final Class<?> pluginClass = knownType.getPluginClass();
             final Type type = getTypeConverterSupportedType(pluginClass);
             final TypeConverter<?> converter = 
getInstance(pluginClass.asSubclass(TypeConverter.class));
             registerTypeConverter(type, converter);
-        }
+        });
         registerTypeConverter(Boolean.class, Boolean::valueOf);
         registerTypeAlias(Boolean.class, Boolean.TYPE);
         registerTypeConverter(Byte.class, Byte::valueOf);
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
index 24a0ee9478..a98d78042a 100644
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
@@ -31,6 +31,7 @@ import java.util.function.Supplier;
  * {@linkplain #init() initialized} with {@link InjectorCallback} services.
  */
 public interface Injector {
+    Key<Injector> KEY = new Key<>() {};
 
     /**
      * Initializes this Injector with all registered {@link InjectorCallback} 
services in
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/LookupSelector.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/LookupSelector.java
deleted file mode 100644
index 59df5a634d..0000000000
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/LookupSelector.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.plugins.di;
-
-import java.lang.invoke.MethodHandles;
-
-@FunctionalInterface
-public interface LookupSelector {
-    MethodHandles.Lookup in(final Class<?> clazz) throws 
IllegalAccessException;
-}
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 2364aa9ae9..ea7b76be3f 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,17 +17,23 @@
 
 package org.apache.logging.log4j.plugins.osgi;
 
-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.status.StatusLogger;
-import org.osgi.framework.*;
+import org.osgi.framework.AdaptPermission;
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.SynchronousBundleListener;
 import org.osgi.framework.wiring.BundleWiring;
 
 import java.security.Permission;
 import java.util.Collection;
-import java.util.Hashtable;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -54,11 +60,12 @@ public final class Activator implements BundleActivator, 
SynchronousBundleListen
     }
 
     private void loadPlugins(final BundleContext bundleContext) {
+        final PluginRegistry pluginRegistry = PluginRegistry.getInstance();
         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());
+                pluginRegistry.loadFromBundle(pluginService.getBundle(), 
bundleContext.getBundle().getBundleId());
             }
         } catch (final InvalidSyntaxException ex) {
             LOGGER.error("Error accessing Plugins", ex);
@@ -122,7 +129,7 @@ public final class Activator implements BundleActivator, 
SynchronousBundleListen
         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);
+            LOGGER.trace("Scanning bundle [{}, id={}] for plugins.", 
bundle.getSymbolicName(), bundleId);
             PluginRegistry.getInstance().loadFromBundle(bundleId,
                     bundle.adapt(BundleWiring.class).getClassLoader());
         }
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 00d8a7cf5d..69233c64d4 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,14 +16,10 @@
  */
 package org.apache.logging.log4j.plugins.processor;
 
+import org.apache.logging.log4j.plugins.util.PluginBundle;
+import org.apache.logging.log4j.plugins.util.PluginCategory;
 import org.apache.logging.log4j.plugins.util.PluginType;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Provides {@linkplain PluginEntry plugin metadata} for a module. 
Implementation classes are typically generated by
  * {@code PluginProcessor} in log4j-plugin-processor.
@@ -32,31 +28,29 @@ import java.util.Map;
  */
 public abstract class PluginService {
 
-    private final Map<String, List<PluginType<?>>> categories = new 
LinkedHashMap<>();
+    private final PluginBundle bundle = new PluginBundle();
 
     public PluginService() {
         PluginEntry[] entries = getEntries();
         ClassLoader classLoader = getClass().getClassLoader();
         for (PluginEntry entry : entries) {
-            String category = entry.getCategory().toLowerCase();
-            List<PluginType<?>> list = categories.computeIfAbsent(category, 
ignored -> new ArrayList<>());
-            PluginType<?> type = new PluginType<>(entry, classLoader);
-            list.add(type);
+            final PluginType<?> pluginType = new PluginType<>(entry, 
classLoader);
+            bundle.add(pluginType);
         }
     }
 
     public abstract PluginEntry[] getEntries();
 
-    public Map<String, List<PluginType<?>>> getCategories() {
-        return Collections.unmodifiableMap(categories);
+    public PluginBundle getBundle() {
+        return bundle;
     }
 
-    public List<PluginType<?>> getCategory(String category) {
-        return 
Collections.unmodifiableList(categories.get(category.toLowerCase()));
+    public PluginCategory getCategory(String category) {
+        return bundle.get(category);
     }
 
     public long size() {
-        return categories.size();
+        return bundle.size();
     }
 
 }
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginBundle.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginBundle.java
new file mode 100644
index 0000000000..a1a3271cae
--- /dev/null
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginBundle.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.util;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Bundles plugins by category from a plugin source.
+ */
+public class PluginBundle {
+    private final Map<String, PluginCategory> categories = new 
LinkedHashMap<>();
+
+    public int size() {
+        return categories.size();
+    }
+
+    public boolean isEmpty() {
+        return categories.isEmpty();
+    }
+
+    public void put(final String category, final List<PluginType<?>> 
pluginTypes) {
+        final var pluginCategory = new PluginCategory(category);
+        pluginCategory.putAll(pluginTypes);
+        categories.put(pluginCategory.getKey(), pluginCategory);
+    }
+
+    public void put(final PluginCategory category) {
+        categories.put(category.getKey(), category);
+    }
+
+    public int merge(final PluginCategory category) {
+        final PluginCategory existingCategory = getOrCreate(category.getKey());
+        final AtomicInteger addedCount = new AtomicInteger();
+        category.forEach((key, plugin) -> {
+            final var merged = existingCategory.merge(key, plugin);
+            if (merged == plugin) {
+                addedCount.incrementAndGet();
+            }
+        });
+        return addedCount.get();
+    }
+
+    public void add(final PluginType<?> pluginType) {
+        getOrCreate(pluginType.getCategory()).put(pluginType);
+    }
+
+    public void addAll(final String category, final Collection<PluginType<?>> 
pluginTypes) {
+        getOrCreate(category).putAll(pluginTypes);
+    }
+
+    public PluginCategory get(final String category) {
+        return categories.get(category.toLowerCase(Locale.ROOT));
+    }
+
+    public PluginCategory getOrCreate(final String category) {
+        return categories.computeIfAbsent(category.toLowerCase(Locale.ROOT), 
key -> new PluginCategory(key, category));
+    }
+
+    public void forEach(final Consumer<? super PluginCategory> consumer) {
+        categories.values().forEach(consumer);
+    }
+
+    public void forEach(final BiConsumer<? super String, ? super 
PluginCategory> biConsumer) {
+        categories.forEach(biConsumer);
+    }
+}
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginCategory.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginCategory.java
new file mode 100644
index 0000000000..63a7dae8e2
--- /dev/null
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginCategory.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.PluginOrder;
+import org.apache.logging.log4j.plugins.Singleton;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+
+/**
+ * Plugin categories are mappings of plugin keys to plugin classes where 
plugin keys are lower-cased
+ * versions of plugin names.
+ */
+@Singleton
+public class PluginCategory implements Iterable<PluginType<?>> {
+    private static final Logger LOGGER = StatusLogger.getLogger();
+
+    private final String key;
+    private final String name;
+    private final Map<String, PluginType<?>> plugins = new LinkedHashMap<>();
+
+    public PluginCategory(final String name) {
+        this(name.toLowerCase(Locale.ROOT), name);
+    }
+
+    PluginCategory(final String key, final String name) {
+        this.key = key;
+        this.name = name;
+    }
+
+    /**
+     * Returns the key corresponding to this plugin category. These keys are 
lowercase versions of the category name.
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * Returns the name of this plugin category.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the number of plugins in this category.
+     */
+    public int size() {
+        return plugins.size();
+    }
+
+    /**
+     * Indicates if this category has no plugins.
+     */
+    public boolean isEmpty() {
+        return plugins.isEmpty();
+    }
+
+    /**
+     * Returns an unmodifiable set of plugin keys in this category.
+     */
+    public Set<String> getPluginKeys() {
+        return Collections.unmodifiableSet(plugins.keySet());
+    }
+
+    /**
+     * Returns an unmodifiable collection of plugin types in this category.
+     */
+    public Collection<PluginType<?>> getPluginTypes() {
+        return Collections.unmodifiableCollection(plugins.values());
+    }
+
+    /**
+     * Returns an unmodifiable map of plugin keys to plugin types in this 
category.
+     */
+    public Map<String, PluginType<?>> asMap() {
+        return Collections.unmodifiableMap(plugins);
+    }
+
+    /**
+     * Gets the plugin type for the provided plugin name (case-insensitive) if 
available or {@code null}.
+     */
+    public PluginType<?> get(final String name) {
+        return plugins.get(name.toLowerCase(Locale.ROOT));
+    }
+
+    /**
+     * Puts all the provided plugin types into this category.
+     */
+    public void putAll(final Collection<PluginType<?>> pluginTypes) {
+        pluginTypes.forEach(this::put);
+    }
+
+    /**
+     * Puts the provided plugin type into this category.
+     */
+    public void put(final PluginType<?> pluginType) {
+        put(pluginType.getKey(), pluginType);
+    }
+
+    /**
+     * Puts the provided plugin type into this category using the given key.
+     */
+    public void put(final String key, final PluginType<?> pluginType) {
+        plugins.put(key, pluginType);
+        LOGGER.trace("Put PluginCategory[{}][{}] = {}", name, key, pluginType);
+    }
+
+    /**
+     * Merges the provided plugin type into this category using the given key 
and returns the merged result.
+     * Merging is done by preferring plugins according to {@link PluginOrder} 
where a conflict occurs with the
+     * same key.
+     */
+    public PluginType<?> merge(final String key, final PluginType<?> 
pluginType) {
+        final PluginType<?> result = plugins.merge(key, pluginType, (lhs, rhs) 
-> {
+            final int compare = 
PluginOrder.COMPARATOR.compare(lhs.getPluginClass(), rhs.getPluginClass());
+            LOGGER.debug("PluginCategory merge for key {} with comparison 
result {}", key, compare);
+            return compare <= 0 ? lhs : rhs;
+        });
+        LOGGER.trace("Merged PluginCategory[{}][{}] = {}", name, key, result);
+        return result;
+    }
+
+    public int mergeAll(final PluginCategory category) {
+        if (category != null) {
+            final AtomicInteger addedCount = new AtomicInteger();
+            category.forEach((pluginKey, pluginType) -> {
+                if (pluginType == merge(pluginKey, pluginType)) {
+                    addedCount.incrementAndGet();
+                }
+            });
+            return addedCount.get();
+        }
+        return 0;
+    }
+
+    @Override
+    public Iterator<PluginType<?>> iterator() {
+        return plugins.values().iterator();
+    }
+
+    /**
+     * Performs the given action on all the plugin types in this category.
+     */
+    public void forEach(final BiConsumer<? super String, ? super 
PluginType<?>> biConsumer) {
+        plugins.forEach(biConsumer);
+    }
+}
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
deleted file mode 100644
index 8360515116..0000000000
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.plugins.util;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.plugins.Singleton;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Strings;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Loads and manages all the plugins.
- */
-@Singleton
-public class PluginManager {
-
-    /**
-     * The location of the plugin cache data file. This file is written to by 
this processor, and read from by
-     * {@link org.apache.logging.log4j.plugins.util.PluginManager}.
-     */
-    public static final String PLUGIN_CACHE_FILE =
-            
"META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
-    private static final CopyOnWriteArrayList<String> PACKAGES = new 
CopyOnWriteArrayList<>();
-    private static final String LOG4J_PACKAGES = 
"org.apache.logging.log4j.core";
-
-    private static final Logger LOGGER = StatusLogger.getLogger();
-
-    private Map<String, PluginType<?>> plugins = new HashMap<>();
-    private final String category;
-
-    /**
-     * Constructs a PluginManager for the plugin category name given.
-     *
-     * @param category The plugin category name.
-     */
-    public PluginManager(final String category) {
-        this.category = category;
-    }
-
-    /**
-     * Adds a package name to be scanned for plugins. Must be invoked prior to 
plugins being collected.
-     *
-     * @param p The package name. Ignored if {@code null} or empty.
-     */
-    public static void addPackage(final String p) {
-        if (Strings.isBlank(p)) {
-            return;
-        }
-        PACKAGES.addIfAbsent(p);
-    }
-
-    /**
-     * Adds a list of package names to be scanned for plugins. Convenience 
method for {@link #addPackage(String)}.
-     *
-     * @param packages collection of package names to add. Empty and null 
package names are ignored.
-     */
-    public static void addPackages(final Collection<String> packages) {
-        for (final String pkg : packages) {
-            if (Strings.isNotBlank(pkg)) {
-                PACKAGES.addIfAbsent(pkg);
-            }
-        }
-    }
-
-    /**
-     * Returns the type of a specified plugin.
-     *
-     * @param name The name of the plugin.
-     * @return The plugin's type.
-     */
-    public PluginType<?> getPluginType(final String name) {
-        return plugins.get(name.toLowerCase());
-    }
-
-    /**
-     * Returns all the matching plugins.
-     *
-     * @return A Map containing the name of the plugin and its type.
-     */
-    public Map<String, PluginType<?>> getPlugins() {
-        return plugins;
-    }
-
-    /**
-     * Locates all the plugins.
-     */
-    public void collectPlugins() {
-        collectPlugins(null);
-    }
-
-    /**
-     * 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(final List<String> packages) {
-        final String categoryLowerCase = category.toLowerCase();
-        final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>();
-
-        // 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);
-        }
-        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));
-
-        // 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));
-        }
-
-        // Next iterate any packages passed to the static addPackage method.
-        for (final String pkg : PACKAGES) {
-            mergeByName(newPlugins, 
PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
-        }
-        // 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));
-            }
-        }
-
-        LOGGER.debug("PluginManager '{}' found {} plugins", category, 
newPlugins.size());
-
-        plugins = newPlugins;
-    }
-
-    private static void mergeByName(final Map<String, PluginType<?>> 
newPlugins, final List<PluginType<?>> plugins) {
-        if (plugins == null) {
-            return;
-        }
-        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());
-            }
-        }
-    }
-}
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 e68bfdb2a4..09762c4a0e 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
@@ -32,19 +32,14 @@ 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.LinkedList;
 import java.util.List;
-import java.util.Locale;
-import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -52,6 +47,11 @@ import java.util.function.Supplier;
  */
 public class PluginRegistry {
 
+    /**
+     * The location of the plugin cache data file for compatibility with Log4j 
2.x plugins.
+     */
+    private static final String PLUGIN_CACHE_FILE =
+            
"META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private static final Supplier<PluginRegistry> INSTANCE = new 
LazyValue<>(PluginRegistry::new);
@@ -59,20 +59,17 @@ public class PluginRegistry {
     /**
      * Contains plugins found in Log4j2Plugins.dat cache files in the main 
CLASSPATH.
      */
-    private final AtomicReference<Map<String, List<PluginType<?>>>> 
pluginsByCategoryRef =
-        new AtomicReference<>();
+    private final AtomicReference<PluginBundle> pluginsByCategoryRef = new 
AtomicReference<>();
 
     /**
      * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles.
      */
-    private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> 
pluginsByCategoryByBundleId =
-        new ConcurrentHashMap<>();
+    private final ConcurrentMap<Long, PluginBundle> 
pluginsByCategoryByBundleId = new ConcurrentHashMap<>();
 
     /**
      * Contains plugins found by searching for annotated classes at runtime.
      */
-    private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> 
pluginsByCategoryByPackage =
-        new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, PluginBundle> 
pluginsByCategoryByPackage = new ConcurrentHashMap<>();
 
     private PluginRegistry() {
     }
@@ -96,13 +93,8 @@ public class PluginRegistry {
         pluginsByCategoryByBundleId.clear();
     }
 
-    /**
-     * Retrieve plugins by their category and bundle id.
-     * @return The Map of plugin maps.
-     * @since 2.1
-     */
-    public Map<Long, Map<String, List<PluginType<?>>>> 
getPluginsByCategoryByBundleId() {
-        return pluginsByCategoryByBundleId;
+    public void forEachOsgiPluginBundle(final Consumer<? super PluginBundle> 
consumer) {
+        pluginsByCategoryByBundleId.values().forEach(consumer);
     }
 
     /**
@@ -110,13 +102,13 @@ public class PluginRegistry {
      * @return Map of the List of PluginTypes by category.
      * @since 2.1
      */
-    public Map<String, List<PluginType<?>>> loadFromMainClassLoader() {
-        final Map<String, List<PluginType<?>>> existing = 
pluginsByCategoryRef.get();
+    public PluginBundle loadFromMainClassLoader() {
+        final var existing = pluginsByCategoryRef.get();
         if (existing != null) {
             // already loaded
             return existing;
         }
-        final Map<String, List<PluginType<?>>> newPluginsByCategory = 
decodeCacheFiles(LoaderUtil.getClassLoader());
+        final PluginBundle newPluginsByCategory = 
decodeCacheFiles(LoaderUtil.getClassLoader());
         loadPlugins(newPluginsByCategory);
 
         // Note multiple threads could be calling this method concurrently. 
Both will do the work,
@@ -144,13 +136,13 @@ public class PluginRegistry {
      * @return the Map of Lists of plugins organized by category.
      * @since 2.1
      */
-    public Map<String, List<PluginType<?>>> loadFromBundle(final long 
bundleId, final ClassLoader loader) {
-        Map<String, List<PluginType<?>>> existing = 
pluginsByCategoryByBundleId.get(bundleId);
+    public PluginBundle loadFromBundle(final long bundleId, final ClassLoader 
loader) {
+        PluginBundle existing = pluginsByCategoryByBundleId.get(bundleId);
         if (existing != null) {
             // already loaded from this classloader
             return existing;
         }
-        final Map<String, List<PluginType<?>>> newPluginsByCategory = 
decodeCacheFiles(loader);
+        final PluginBundle newPluginsByCategory = decodeCacheFiles(loader);
         loadPlugins(loader, newPluginsByCategory);
 
         // Note multiple threads could be calling this method concurrently. 
Both will do the work,
@@ -169,15 +161,8 @@ public class PluginRegistry {
      * @param bundleId The bundle Id.
      * @since 3.0
      */
-    public void loadFromBundle(Map<String, List<PluginType<?>>> categories, 
Long bundleId) {
+    public void loadFromBundle(PluginBundle 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());
-        }
     }
 
     /**
@@ -185,7 +170,7 @@ public class PluginRegistry {
      * @param map The Map of the lists of plugins organized by category.
      * @since 3.0
      */
-    public void loadPlugins(Map<String, List<PluginType<?>>> map) {
+    public void loadPlugins(PluginBundle map) {
         Throwable throwable = null;
         ClassLoader errorClassLoader = null;
         boolean allFail = true;
@@ -208,18 +193,15 @@ public class PluginRegistry {
     /**
      * Load plugins from a specific ClassLoader.
      * @param classLoader The ClassLoader.
-     * @param map The Map of the list of plugin types organized by category.
+     * @param bundle The PluginBundle to merge discovered plugins to
      * @since 3.0
      */
-    public void loadPlugins(ClassLoader classLoader, Map<String, 
List<PluginType<?>>> map) {
+    public void loadPlugins(ClassLoader classLoader, PluginBundle bundle) {
         final long startTime = System.nanoTime();
         final ServiceLoader<PluginService> serviceLoader = 
ServiceLoader.load(PluginService.class, classLoader);
         final AtomicInteger pluginCount = new AtomicInteger();
         for (final PluginService pluginService : serviceLoader) {
-            pluginService.getCategories().forEach((category, plugins) -> {
-                map.computeIfAbsent(category.toLowerCase(Locale.ROOT), ignored 
-> new ArrayList<>()).addAll(plugins);
-                pluginCount.addAndGet(plugins.size());
-            });
+            pluginService.getBundle().forEach((category, plugins) -> 
pluginCount.addAndGet(bundle.merge(plugins)));
         }
         final int numPlugins = pluginCount.get();
         LOGGER.debug(() -> {
@@ -230,11 +212,11 @@ public class PluginRegistry {
         });
     }
 
-    private Map<String, List<PluginType<?>>> decodeCacheFiles(final 
ClassLoader classLoader) {
+    private PluginBundle decodeCacheFiles(final ClassLoader classLoader) {
         final long startTime = System.nanoTime();
         final PluginCache cache = new PluginCache();
         try {
-            final Enumeration<URL> resources = 
classLoader.getResources(PluginManager.PLUGIN_CACHE_FILE);
+            final Enumeration<URL> resources = 
classLoader.getResources(PLUGIN_CACHE_FILE);
             if (resources == null) {
                 LOGGER.info("Plugin preloads not available from class loader 
{}", classLoader);
             } else {
@@ -243,20 +225,15 @@ public class PluginRegistry {
         } catch (final IOException ioe) {
             LOGGER.warn("Unable to preload plugins", ioe);
         }
-        final Map<String, List<PluginType<?>>> newPluginsByCategory = new 
HashMap<>();
-        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<>(outer.getValue().size());
-            newPluginsByCategory.put(categoryLowerCase, types);
-            for (final Map.Entry<String, PluginEntry> inner : 
outer.getValue().entrySet()) {
-                final PluginEntry entry = inner.getValue();
-                final PluginType<?> type = new PluginType<>(entry, 
classLoader);
-                types.add(type);
-                ++pluginCount;
-            }
-        }
-        final int numPlugins = pluginCount;
+        final PluginBundle newPluginsByCategory = new PluginBundle();
+        final AtomicInteger pluginCount = new AtomicInteger();
+        cache.getAllCategories().forEach((key, outer) ->
+                outer.values().forEach(entry -> {
+                    final PluginType<?> type = new PluginType<>(entry, 
classLoader);
+                    newPluginsByCategory.add(type);
+                    pluginCount.incrementAndGet();
+                }));
+        final int numPlugins = pluginCount.get();
         LOGGER.debug(() -> {
             final long endTime = System.nanoTime();
             final DecimalFormat numFormat = new DecimalFormat("#0.000000");
@@ -272,12 +249,12 @@ public class PluginRegistry {
      * @return A Map of the lists of plugin types organized by category.
      * @since 2.1
      */
-    public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
+    public PluginBundle loadFromPackage(final String pkg) {
         if (Strings.isBlank(pkg)) {
             // happens when splitting an empty string
-            return Collections.emptyMap();
+            return new PluginBundle();
         }
-        Map<String, List<PluginType<?>>> existing = 
pluginsByCategoryByPackage.get(pkg);
+        PluginBundle existing = pluginsByCategoryByPackage.get(pkg);
         if (existing != null) {
             // already loaded this package
             return existing;
@@ -291,11 +268,9 @@ public class PluginRegistry {
         }
         resolver.findInPackage(new PluginTest(), pkg);
 
-        final Map<String, List<PluginType<?>>> newPluginsByCategory = new 
HashMap<>();
+        final PluginBundle newPluginsByCategory = new PluginBundle();
         for (final Class<?> clazz : resolver.getClasses()) {
             final Plugin plugin = clazz.getAnnotation(Plugin.class);
-            final String categoryLowerCase = plugin.category().toLowerCase();
-            List<PluginType<?>> list = 
newPluginsByCategory.computeIfAbsent(categoryLowerCase, k -> new ArrayList<>());
             final PluginEntry mainEntry = new PluginEntry();
             final String mainElementName = plugin.elementType().equals(
                 Plugin.EMPTY) ? plugin.name() : plugin.elementType();
@@ -306,7 +281,7 @@ public class PluginRegistry {
             mainEntry.setPrintable(plugin.printObject());
             mainEntry.setDefer(plugin.deferChildren());
             final PluginType<?> mainType = new PluginType<>(mainEntry, clazz, 
mainElementName);
-            list.add(mainType);
+            newPluginsByCategory.add(mainType);
             final PluginAliases pluginAliases = 
clazz.getAnnotation(PluginAliases.class);
             if (pluginAliases != null) {
                 for (final String alias : pluginAliases.value()) {
@@ -320,7 +295,7 @@ public class PluginRegistry {
                     aliasEntry.setPrintable(plugin.printObject());
                     aliasEntry.setDefer(plugin.deferChildren());
                     final PluginType<?> aliasType = new 
PluginType<>(aliasEntry, clazz, aliasElementName);
-                    list.add(aliasType);
+                    newPluginsByCategory.add(aliasType);
                 }
             }
         }
@@ -341,6 +316,37 @@ public class PluginRegistry {
         return newPluginsByCategory;
     }
 
+    /**
+     * Gets the registered plugins for the given category. If additional scan 
packages are provided, then plugins
+     * are scanned and loaded from there as well.
+     */
+    public PluginCategory getCategory(final String categoryName, List<String> 
additionalScanPackages) {
+        final var category = new PluginCategory(categoryName);
+        // First, iterate the PluginService services and legacy 
Log4j2Plugin.dat files found in the main CLASSPATH
+        PluginBundle builtInPlugins = 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 = loadFromPackage("org.apache.logging.log4j.core");
+        }
+        final AtomicInteger addedCount = new 
AtomicInteger(category.mergeAll(builtInPlugins.get(categoryName)));
+
+        // Next, iterate OSGi modules that provide plugins as OSGi services
+        forEachOsgiPluginBundle(bundle ->
+                
addedCount.addAndGet(category.mergeAll(bundle.get(categoryName))));
+
+        // Finally, iterate over additional packages from configuration
+        if (additionalScanPackages != null) {
+            for (final String pkg : additionalScanPackages) {
+                
addedCount.addAndGet(category.mergeAll(loadFromPackage(pkg).get(categoryName)));
+            }
+        }
+
+        LOGGER.debug("Discovered {} new plugins in category '{}'", 
addedCount.get(), categoryName);
+
+        return category;
+    }
+
     /**
      * 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.
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 f1ab067e48..f934936356 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
@@ -65,7 +65,6 @@ public class PluginType<T> {
         this.elementName = pluginEntry.getName();
     }
 
-
     public PluginEntry getPluginEntry() {
         return this.pluginEntry;
     }
@@ -104,6 +103,10 @@ public class PluginType<T> {
         return this.pluginEntry.getCategory();
     }
 
+    public String getName() {
+        return pluginEntry.getName();
+    }
+
     @Override
     public String toString() {
         return "PluginType [pluginClass=" + pluginClass.get() +
diff --git a/src/site/asciidoc/manual/plugins.adoc 
b/src/site/asciidoc/manual/plugins.adoc
index 88c64599e9..f198c86934 100644
--- a/src/site/asciidoc/manual/plugins.adoc
+++ b/src/site/asciidoc/manual/plugins.adoc
@@ -30,7 +30,7 @@ 
link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/Configuration.ht
 will invoke the
 
link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/util/PluginManager.html[`PluginManager`]
 to load the built-in Log4j plugins as well as any custom plugins. The
-`PluginManager` locates plugins by looking in five places:
+`Injector` locates plugins by looking in five places:
 
 1.  Plugin collection classes on the classpath that are loaded by 
java.util.ServiceLoader.
 These classes are generated automatically during the build (more details 
below).
@@ -40,30 +40,19 @@ new bundles after `log4j-plugins` has started. Bundles must 
register their plugi
 class as an OSGi service.
 3.  A comma-separated list of packages specified by the
 `log4j.plugin.packages` system property.
-4.  Packages passed to the static `PluginManager.addPackages` method
-(before Log4j configuration occurs).
+4.  Packages from the `@Named("PluginPackages") List<String>` binding in 
`Injector` (if provided before configuration occurs).
 5.  The link:./configuration.html#ConfigurationSyntax[packages] declared
 in your log4j2 configuration file.
 6. Serialized plugin listing files on the classpath. These files were 
generated by
 the plugin annotation processor in Log4j 2 2.x. These are processed to allow
 compatibility.
 
-If multiple Plugins specify the same (case-insensitive) `name`, then the
-load order above determines which one will be used. For example, to
-override the `File` plugin which is provided by the built-in
-`FileAppender` class, you would need to place your plugin in a JAR file
-in the CLASSPATH ahead of`log4j-core.jar`. This is not recommended;
-plugin name collisions will cause a warning to be emitted. Note that in
-an OSGi environment, the order that bundles are scanned for plugins
-generally follows the same order that bundles were installed into the
-framework. See
-http://www.osgi.org/javadoc/r5/core/org/osgi/framework/BundleContext.html#getBundles()[`getBundles()`]
-and
-http://www.osgi.org/javadoc/r5/core/org/osgi/framework/SynchronousBundleListener.html[`SynchronousBundleListener`].
-In short, name collisions are even more unpredictable in an OSGi environment.
-////
-TODO: in future, plugins will be able to be annotated with @Order which can 
override priorities
-////
+When multiple plugins use the same case-insensitive `name` within the same 
plugin category, then which one is selected is determined first by the presence 
of `@PluginOrder` annotations and then by the previously described plugin 
loading order.
+For example, to override the `File` plugin which is provided by the built-in 
`FileAppender` class, you would need to place your plugin in a JAR file in the 
CLASSPATH ahead of`log4j-core.jar`.
+This is not recommended; plugin name collisions will cause a warning to be 
emitted.
+Note that in an OSGi environment, the order that bundles are scanned for 
plugins generally follows the same order that bundles were installed into the 
framework.
+See 
https://www.osgi.org/javadoc/r5/core/org/osgi/framework/BundleContext.html#getBundles()[`getBundles()`]
 and 
https://www.osgi.org/javadoc/r5/core/org/osgi/framework/SynchronousBundleListener.html[`SynchronousBundleListener`].
+In short, name collisions are even more unpredictable in an OSGi environment 
without additional `@PluginOrder` usage.
 
 Plugin collection classes are generated by an annotation processor contained
 in the log4j-plugins artifact which will automatically scan your code for

Reply via email to