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

commit c80a381d09d8475b2c5ea7f30f3f279606bf6615
Author: Matt Sicker <[email protected]>
AuthorDate: Sat May 14 19:41:27 2022 -0500

    LOG4J2-3496 - Add Optional injection support, plugin interface filtering
    
    - Adds support for using Optional<T> as an injection point where any errors 
involved in looking up an instance are collapsed into an empty value.
    - Adds additional plugin metadata caching for implemented interfaces to 
help with filtering plugins by type.
    - Moves PluginBundle into PluginRegistry.Categories
    
    Signed-off-by: Matt Sicker <[email protected]>
---
 .../json/resolver/TemplateResolverFactories.java   |  30 ++-
 .../resolver/TemplateResolverInterceptors.java     |  31 ++-
 .../log4j/plugin/processor/PluginProcessor.java    |  81 +++++--
 .../logging/log4j/plugins/di/InjectorTest.java     |  36 +++
 .../logging/log4j/plugins/di/DefaultInjector.java  |  44 +++-
 .../org/apache/logging/log4j/plugins/di/Key.java   |   7 +
 .../logging/log4j/plugins/osgi/Activator.java      |   6 +-
 .../log4j/plugins/processor/PluginEntry.java       |  23 +-
 .../log4j/plugins/processor/PluginService.java     |  25 +-
 .../logging/log4j/plugins/util/PluginBundle.java   |  88 -------
 .../logging/log4j/plugins/util/PluginCategory.java |  54 ++---
 .../logging/log4j/plugins/util/PluginRegistry.java | 260 ++++++++++-----------
 .../logging/log4j/plugins/util/PluginType.java     |  18 +-
 13 files changed, 367 insertions(+), 336 deletions(-)

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 57c0187143..8bd503b358 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
@@ -20,7 +20,6 @@ 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;
 
@@ -80,22 +79,19 @@ public final class TemplateResolverFactories {
             final Class<V> valueClass,
             final Class<C> contextClass) {
         final Map<String, F> factoryByName = new LinkedHashMap<>();
-        for (final PluginType<?> pluginType : factoryPlugins) {
-            final Class<?> pluginClass = pluginType.getPluginClass();
-            final boolean pluginClassMatched =
-                    
TemplateResolverFactory.class.isAssignableFrom(pluginClass);
-            if (pluginClassMatched) {
-                @SuppressWarnings("rawtypes")
-                final Class<? extends TemplateResolverFactory> factoryClass =
-                        pluginClass.asSubclass(TemplateResolverFactory.class);
-                final TemplateResolverFactory<?, ?> rawFactory =
-                        configuration.getComponent(Key.forClass(factoryClass));
-                final F factory = castFactory(valueClass, contextClass, 
rawFactory);
-                if (factory != null) {
-                    addFactory(factoryByName, factory);
-                }
-            }
-        }
+        factoryPlugins.forEachMatching(
+                pluginType -> 
pluginType.getImplementedInterfaces().contains(TemplateResolverFactory.class),
+                pluginType -> {
+                    @SuppressWarnings("rawtypes")
+                    final Class<? extends TemplateResolverFactory> 
factoryClass =
+                            
pluginType.getPluginClass().asSubclass(TemplateResolverFactory.class);
+                    final TemplateResolverFactory<?, ?> rawFactory =
+                            
configuration.getComponent(Key.forClass(factoryClass));
+                    final F factory = castFactory(valueClass, contextClass, 
rawFactory);
+                    if (factory != null) {
+                        addFactory(factoryByName, factory);
+                    }
+                });
         return factoryByName;
     }
 
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 d793c8f139..99dc926647 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
@@ -20,7 +20,6 @@ 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;
 
@@ -77,23 +76,21 @@ public class TemplateResolverInterceptors {
             final Class<V> valueClass,
             final Class<C> contextClass) {
         final List<I> interceptors = new LinkedList<>();
-        for (final PluginType<?> pluginType : interceptorPlugins) {
-            final Class<?> pluginClass = pluginType.getPluginClass();
-            final boolean pluginClassMatched =
-                    
TemplateResolverInterceptor.class.isAssignableFrom(pluginClass);
-            if (pluginClassMatched) {
-                @SuppressWarnings("rawtypes")
-                final Class<? extends TemplateResolverInterceptor> 
interceptorClass =
-                        
pluginClass.asSubclass(TemplateResolverInterceptor.class);
-                final TemplateResolverInterceptor<?, ?> rawInterceptor =
-                        
configuration.getComponent(Key.forClass(interceptorClass));
-                final I interceptor =
-                        castInterceptor(valueClass, contextClass, 
rawInterceptor);
-                if (interceptor != null) {
-                    interceptors.add(interceptor);
+        interceptorPlugins.forEachMatching(
+                pluginType -> 
pluginType.getImplementedInterfaces().contains(TemplateResolverInterceptor.class),
+                pluginType -> {
+                    @SuppressWarnings("rawtypes")
+                    final Class<? extends TemplateResolverInterceptor> 
interceptorClass =
+                            
pluginType.getPluginClass().asSubclass(TemplateResolverInterceptor.class);
+                    final TemplateResolverInterceptor<?, ?> rawInterceptor =
+                            
configuration.getComponent(Key.forClass(interceptorClass));
+                    final I interceptor =
+                            castInterceptor(valueClass, contextClass, 
rawInterceptor);
+                    if (interceptor != null) {
+                        interceptors.add(interceptor);
+                    }
                 }
-            }
-        }
+        );
         return interceptors;
     }
 
diff --git 
a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
 
b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
index cf6a7a6ecd..9dca19fa3f 100644
--- 
a/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
+++ 
b/log4j-plugin-processor/src/main/java/org/apache/logging/log4j/plugin/processor/PluginProcessor.java
@@ -30,10 +30,16 @@ import javax.annotation.processing.SupportedAnnotationTypes;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.Modifier;
 import javax.lang.model.element.Name;
 import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementKindVisitor9;
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.SimpleElementVisitor8;
+import javax.lang.model.util.SimpleTypeVisitor9;
+import javax.lang.model.util.Types;
 import javax.tools.Diagnostic.Kind;
 import javax.tools.FileObject;
 import javax.tools.JavaFileObject;
@@ -45,6 +51,7 @@ import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -85,7 +92,7 @@ public class PluginProcessor extends AbstractProcessor {
                 return false;
             }
             messager.printMessage(Kind.NOTE, "Retrieved " + elements.size() + 
" Plugin elements");
-            List<PluginEntry> list = new ArrayList<>();
+            List<PluginEntryMirror> list = new ArrayList<>();
             packageName = collectPlugins(packageName, elements, list);
             writeClassFile(packageName, list);
             writeServiceFile(packageName);
@@ -100,26 +107,23 @@ public class PluginProcessor extends AbstractProcessor {
         processingEnv.getMessager().printMessage(Kind.ERROR, message);
     }
 
-    private String collectPlugins(String packageName, final Iterable<? extends 
Element> elements, List<PluginEntry> list) {
+    private String collectPlugins(String packageName, final Iterable<? extends 
Element> elements, List<PluginEntryMirror> list) {
         boolean calculatePackage = packageName == null;
         final Elements elementUtils = processingEnv.getElementUtils();
-        final ElementVisitor<PluginEntry, Plugin> pluginVisitor = new 
PluginElementVisitor(elementUtils);
-        final ElementVisitor<Collection<PluginEntry>, Plugin> 
pluginAliasesVisitor = new PluginAliasesElementVisitor(
+        final ElementVisitor<PluginEntryMirror, Plugin> pluginVisitor = new 
PluginElementVisitor(elementUtils);
+        final ElementVisitor<Collection<PluginEntryMirror>, Plugin> 
pluginAliasesVisitor = new PluginAliasesElementVisitor(
                 elementUtils);
         for (final Element element : elements) {
             final Plugin plugin = element.getAnnotation(Plugin.class);
             if (plugin == null) {
                 continue;
             }
-            final PluginEntry entry = element.accept(pluginVisitor, plugin);
+            final PluginEntryMirror entry = element.accept(pluginVisitor, 
plugin);
             list.add(entry);
             if (calculatePackage) {
                 packageName = calculatePackage(elementUtils, element, 
packageName);
             }
-            final Collection<PluginEntry> entries = 
element.accept(pluginAliasesVisitor, plugin);
-            for (final PluginEntry pluginEntry : entries) {
-                list.add(pluginEntry);
-            }
+            list.addAll(element.accept(pluginAliasesVisitor, plugin));
         }
         return packageName;
     }
@@ -151,7 +155,7 @@ public class PluginProcessor extends AbstractProcessor {
         }
     }
 
-    private void writeClassFile(String pkg, List<PluginEntry> list) {
+    private void writeClassFile(String pkg, List<PluginEntryMirror> list) {
         String fqcn = createFqcn(pkg);
         try (final PrintWriter writer = createSourceFile(fqcn)) {
             writer.println("package " + pkg + ".plugins;");
@@ -165,14 +169,19 @@ public class PluginProcessor extends AbstractProcessor {
             StringBuilder sb = new StringBuilder();
             int max = list.size() - 1;
             for (int i = 0; i < list.size(); ++i) {
-                PluginEntry entry = list.get(i);
+                PluginEntryMirror mirror = list.get(i);
+                final PluginEntry entry = mirror.entry;
                 sb.append("        ").append("new PluginEntry(\"");
                 sb.append(entry.getKey()).append("\", \"");
                 sb.append(entry.getClassName()).append("\", \"");
                 sb.append(entry.getName()).append("\", ");
                 sb.append(entry.isPrintable()).append(", ");
                 sb.append(entry.isDefer()).append(", \"");
-                sb.append(entry.getCategory()).append("\")");
+                sb.append(entry.getCategory()).append("\"");
+                for (final Name implementedInterface : 
getImplementedInterfaces(mirror.element.asType())) {
+                    sb.append(", 
").append(implementedInterface).append(".class");
+                }
+                sb.append(')');
                 if (i < max) {
                     sb.append(",");
                 }
@@ -199,10 +208,20 @@ public class PluginProcessor extends AbstractProcessor {
         return packageName + ".plugins.Log4jPlugins";
     }
 
+    private static class PluginEntryMirror {
+        private final TypeElement element;
+        private final PluginEntry entry;
+
+        private PluginEntryMirror(final TypeElement element, final PluginEntry 
entry) {
+            this.element = element;
+            this.entry = entry;
+        }
+    }
+
     /**
      * ElementVisitor to scan the Plugin annotation.
      */
-    private static class PluginElementVisitor extends 
SimpleElementVisitor8<PluginEntry, Plugin> {
+    private static class PluginElementVisitor extends 
SimpleElementVisitor8<PluginEntryMirror, Plugin> {
 
         private final Elements elements;
 
@@ -211,7 +230,7 @@ public class PluginProcessor extends AbstractProcessor {
         }
 
         @Override
-        public PluginEntry visitType(final TypeElement e, final Plugin plugin) 
{
+        public PluginEntryMirror visitType(final TypeElement e, final Plugin 
plugin) {
             Objects.requireNonNull(plugin, "Plugin annotation is null.");
             final PluginEntry entry = new PluginEntry();
             entry.setKey(plugin.name().toLowerCase(Locale.US));
@@ -220,10 +239,34 @@ public class PluginProcessor extends AbstractProcessor {
             entry.setPrintable(plugin.printObject());
             entry.setDefer(plugin.deferChildren());
             entry.setCategory(plugin.category());
-            return entry;
+            return new PluginEntryMirror(e, entry);
         }
     }
 
+    private Set<Name> getImplementedInterfaces(final TypeMirror base) {
+        final Set<Name> implementedInterfaces = new LinkedHashSet<>();
+        final Types types = processingEnv.getTypeUtils();
+        base.accept(new SimpleTypeVisitor9<Void, Void>() {
+            @Override
+            public Void visitDeclared(final DeclaredType t, final Void unused) 
{
+                for (final TypeMirror directSupertype : 
types.directSupertypes(t)) {
+                    directSupertype.accept(this, null);
+                }
+                t.asElement().accept(new ElementKindVisitor9<Void, Void>() {
+                    @Override
+                    public Void visitTypeAsInterface(final TypeElement e, 
final Void unused) {
+                        if (e.getModifiers().contains(Modifier.PUBLIC)) {
+                            implementedInterfaces.add(e.getQualifiedName());
+                        }
+                        return null;
+                    }
+                }, null);
+                return null;
+            }
+        }, null);
+        return implementedInterfaces;
+    }
+
     private String commonPrefix(String str1, String str2) {
         int minLength = Math.min(str1.length(), str2.length());
         for (int i = 0; i < minLength; i++) {
@@ -241,7 +284,7 @@ public class PluginProcessor extends AbstractProcessor {
     /**
      * ElementVisitor to scan the PluginAliases annotation.
      */
-    private static class PluginAliasesElementVisitor extends 
SimpleElementVisitor8<Collection<PluginEntry>, Plugin> {
+    private static class PluginAliasesElementVisitor extends 
SimpleElementVisitor8<Collection<PluginEntryMirror>, Plugin> {
 
         private final Elements elements;
 
@@ -251,12 +294,12 @@ public class PluginProcessor extends AbstractProcessor {
         }
 
         @Override
-        public Collection<PluginEntry> visitType(final TypeElement e, final 
Plugin plugin) {
+        public Collection<PluginEntryMirror> visitType(final TypeElement e, 
final Plugin plugin) {
             final PluginAliases aliases = e.getAnnotation(PluginAliases.class);
             if (aliases == null) {
                 return DEFAULT_VALUE;
             }
-            final Collection<PluginEntry> entries = new 
ArrayList<>(aliases.value().length);
+            final Collection<PluginEntryMirror> entries = new 
ArrayList<>(aliases.value().length);
             for (final String alias : aliases.value()) {
                 final PluginEntry entry = new PluginEntry();
                 entry.setKey(alias.toLowerCase(Locale.US));
@@ -265,7 +308,7 @@ public class PluginProcessor extends AbstractProcessor {
                 entry.setPrintable(plugin.printObject());
                 entry.setDefer(plugin.deferChildren());
                 entry.setCategory(plugin.category());
-                entries.add(entry);
+                entries.add(new PluginEntryMirror(e, entry));
             }
             return entries;
         }
diff --git 
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
index 8f93480a0b..5fb1ba3f5d 100644
--- 
a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
+++ 
b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/InjectorTest.java
@@ -44,6 +44,7 @@ import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
@@ -277,6 +278,12 @@ class InjectorTest {
                 .hasMessage("No @Inject constructors or no-arg constructor 
found for " + key);
     }
 
+    @Test
+    void optionalUnknownInstance() {
+        final Key<Optional<UnknownInstance>> key = new Key<>() {};
+        assertThat(DI.createInjector().getInstance(key)).isEmpty();
+    }
+
     @Singleton
     static class DeferredSingleton {
         private final int id;
@@ -753,4 +760,33 @@ class InjectorTest {
         final MultipleElements instance = 
DI.createInjector(NoOpStringSubstitution.class).configure(root);
         assertThat(instance.objects).hasSize(2);
     }
+
+    static class OptionalInjection {
+        @Inject
+        Optional<BeanA> a;
+
+        final BeanB b;
+        BeanC c;
+
+        @Inject
+        OptionalInjection(final Optional<BeanB> b) {
+            this.b = b.orElse(null);
+        }
+
+        @Inject
+        void setC(final Optional<BeanC> c) {
+            this.c = c.orElse(null);
+        }
+    }
+
+    @Test
+    void optionalInjection() {
+        final Injector injector = DI.createInjector();
+        final OptionalInjection first = 
injector.getInstance(OptionalInjection.class);
+        final OptionalInjection second = 
injector.getInstance(OptionalInjection.class);
+        assertThat(first.a).isPresent().isEqualTo(second.a);
+        assertThat(first.b).isNotNull().isEqualTo(second.b);
+        assertThat(first.c).isNotNull().isNotSameAs(second.c);
+        assertThat(second.c).isNotNull();
+    }
 }
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 2ae9342a63..fe2c0097d8 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
@@ -22,6 +22,7 @@ import org.apache.logging.log4j.plugins.FactoryType;
 import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.PluginException;
 import org.apache.logging.log4j.plugins.QualifierType;
 import org.apache.logging.log4j.plugins.ScopeType;
 import org.apache.logging.log4j.plugins.Singleton;
@@ -64,6 +65,7 @@ import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.UnknownFormatConversionException;
@@ -251,17 +253,16 @@ class DefaultInjector implements Injector {
         final Class<T> rawType = key.getRawType();
         final Scope scope = getScopeForType(rawType);
         if (rawType == PluginCategory.class && key.getQualifierType() == 
Named.class) {
-            final Supplier<T> factory = () -> {
-                final String categoryName = key.getName();
-                final Binding<List<String>> pluginPackagesBinding = 
bindingMap.get(Keys.PLUGIN_PACKAGES_KEY, List.of());
-                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));
+            final Key<PluginCategory> pluginCategoryKey = TypeUtil.cast(key);
+            final Supplier<PluginCategory> pluginCategoryFactory = 
createPluginCategoryFactory(pluginCategoryKey);
+            bindingMap.put(pluginCategoryKey, pluginCategoryFactory);
+            return bindingMap.get(key, aliases).getSupplier();
+        }
+        if (rawType == Optional.class) {
+            final Key<Optional<T>> optionalKey = TypeUtil.cast(key);
+            final Supplier<Optional<T>> optionalFactory =
+                    createOptionalFactory(key.getParameterizedTypeArgument(0), 
aliases, node, chain);
+            bindingMap.put(optionalKey, optionalFactory);
             return bindingMap.get(key, aliases).getSupplier();
         }
         final Supplier<T> instanceSupplier = () -> {
@@ -273,6 +274,27 @@ class DefaultInjector implements Injector {
         return bindingMap.bindIfAbsent(key, scope.get(key, instanceSupplier));
     }
 
+    private Supplier<PluginCategory> createPluginCategoryFactory(final 
Key<PluginCategory> key) {
+        return LazyValue.from(() -> {
+            final String categoryName = key.getName();
+            final Binding<List<String>> pluginPackagesBinding = 
bindingMap.get(Keys.PLUGIN_PACKAGES_KEY, List.of());
+            final List<String> pluginPackages = pluginPackagesBinding != null 
? pluginPackagesBinding.getSupplier().get() : List.of();
+            // TODO: can this be a bean, too? might be tricky in OSGi
+            return PluginRegistry.getInstance().getCategory(categoryName, 
pluginPackages);
+        });
+    }
+
+    private <T> Supplier<Optional<T>> createOptionalFactory(
+            final Key<T> key, final Collection<String> aliases, final Node 
node, final Set<Key<?>> chain) {
+        return () -> {
+            try {
+                return Optional.ofNullable(getFactory(key, aliases, node, 
chain).get());
+            } catch (final PluginException e) {
+                return Optional.empty();
+            }
+        };
+    }
+
     private Object getInjectableInstance(
             final Key<?> key, final Node node, final Set<Key<?>> chain, final 
StringBuilder debugLog) {
         final Class<?> rawType = key.getRawType();
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java
index 83686604c5..62e33e6afa 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java
@@ -118,6 +118,13 @@ public class Key<T> {
         return null;
     }
 
+    public final <P> Key<P> getParameterizedTypeArgument(final int arg) {
+        if (type instanceof ParameterizedType) {
+            return forQualifiedNamedType(qualifierType, name, 
((ParameterizedType) type).getActualTypeArguments()[arg]);
+        }
+        return null;
+    }
+
     @Override
     public final boolean equals(final Object o) {
         if (o == this) {
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 ea7b76be3f..6ff109df5d 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
@@ -65,7 +65,7 @@ public final class Activator implements BundleActivator, 
SynchronousBundleListen
             final Collection<ServiceReference<PluginService>> 
serviceReferences = bundleContext.getServiceReferences(PluginService.class, 
null);
             for (final ServiceReference<PluginService> serviceReference : 
serviceReferences) {
                 final PluginService pluginService = 
bundleContext.getService(serviceReference);
-                pluginRegistry.loadFromBundle(pluginService.getBundle(), 
bundleContext.getBundle().getBundleId());
+                
pluginRegistry.loadFromBundle(bundleContext.getBundle().getBundleId(), 
pluginService.getCategories());
             }
         } catch (final InvalidSyntaxException ex) {
             LOGGER.error("Error accessing Plugins", ex);
@@ -130,8 +130,8 @@ public final class Activator implements BundleActivator, 
SynchronousBundleListen
         // LOG4J2-920: don't scan system bundle for plugins
         if (bundle.getState() == Bundle.ACTIVE && bundleId != 0) {
             LOGGER.trace("Scanning bundle [{}, id={}] for plugins.", 
bundle.getSymbolicName(), bundleId);
-            PluginRegistry.getInstance().loadFromBundle(bundleId,
-                    bundle.adapt(BundleWiring.class).getClassLoader());
+            final ClassLoader classLoader = 
bundle.adapt(BundleWiring.class).getClassLoader();
+            PluginRegistry.getInstance().loadFromBundle(bundleId, classLoader);
         }
     }
 
diff --git 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.java
 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.java
index 92b19ce9f5..c35f9aa192 100644
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.java
+++ 
b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.java
@@ -26,7 +26,8 @@ public class PluginEntry {
     private String name;
     private boolean printable;
     private boolean defer;
-    private transient String category;
+    private String category;
+    private Class<?>[] interfaces;
 
     public PluginEntry() {
     }
@@ -40,6 +41,18 @@ public class PluginEntry {
         this.category = category;
     }
 
+    public PluginEntry(
+            final String key, final String className, final String name, final 
boolean printable, final boolean defer,
+            final String category, final Class<?>... interfaces) {
+        this.key = key;
+        this.className = className;
+        this.name = name;
+        this.printable = printable;
+        this.defer = defer;
+        this.category = category;
+        this.interfaces = interfaces;
+    }
+
     public String getKey() {
         return key;
     }
@@ -88,6 +101,14 @@ public class PluginEntry {
         this.category = category;
     }
 
+    public Class<?>[] getInterfaces() {
+        return interfaces;
+    }
+
+    public void setInterfaces(final Class<?>... interfaces) {
+        this.interfaces = interfaces;
+    }
+
     @Override
     public String toString() {
         return "PluginEntry [key=" + key + ", className=" + className + ", 
name=" + name + ", printable=" + printable
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 69233c64d4..ae2f0e3ef3 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,10 +16,13 @@
  */
 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.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
 /**
  * Provides {@linkplain PluginEntry plugin metadata} for a module. 
Implementation classes are typically generated by
  * {@code PluginProcessor} in log4j-plugin-processor.
@@ -28,29 +31,33 @@ import org.apache.logging.log4j.plugins.util.PluginType;
  */
 public abstract class PluginService {
 
-    private final PluginBundle bundle = new PluginBundle();
+    private final Map<String, PluginCategory> categories = new 
LinkedHashMap<>();
 
     public PluginService() {
         PluginEntry[] entries = getEntries();
         ClassLoader classLoader = getClass().getClassLoader();
         for (PluginEntry entry : entries) {
-            final PluginType<?> pluginType = new PluginType<>(entry, 
classLoader);
-            bundle.add(pluginType);
+            final String category = entry.getCategory();
+            categories.computeIfAbsent(category.toLowerCase(Locale.ROOT), key 
-> new PluginCategory(key, category))
+                    .merge(entry.getKey(), new PluginType<>(entry, 
classLoader));
         }
     }
 
     public abstract PluginEntry[] getEntries();
 
-    public PluginBundle getBundle() {
-        return bundle;
+    public Map<String, PluginCategory> getCategories() {
+        return categories;
     }
 
     public PluginCategory getCategory(String category) {
-        return bundle.get(category);
+        return categories.get(category.toLowerCase(Locale.ROOT));
     }
 
-    public long size() {
-        return bundle.size();
+    public int size() {
+        return categories.values()
+                .stream()
+                .mapToInt(PluginCategory::size)
+                .sum();
     }
 
 }
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
deleted file mode 100644
index a1a3271cae..0000000000
--- 
a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginBundle.java
+++ /dev/null
@@ -1,88 +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 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
index 63a7dae8e2..0d39d4681e 100644
--- 
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
@@ -22,22 +22,23 @@ 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.AbstractCollection;
 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;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * 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<?>> {
+public class PluginCategory extends AbstractCollection<PluginType<?>> {
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private final String key;
@@ -48,7 +49,7 @@ public class PluginCategory implements 
Iterable<PluginType<?>> {
         this(name.toLowerCase(Locale.ROOT), name);
     }
 
-    PluginCategory(final String key, final String name) {
+    public PluginCategory(final String key, final String name) {
         this.key = key;
         this.name = name;
     }
@@ -88,20 +89,6 @@ public class PluginCategory implements 
Iterable<PluginType<?>> {
         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}.
      */
@@ -109,13 +96,6 @@ public class PluginCategory implements 
Iterable<PluginType<?>> {
         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.
      */
@@ -131,6 +111,11 @@ public class PluginCategory implements 
Iterable<PluginType<?>> {
         LOGGER.trace("Put PluginCategory[{}][{}] = {}", name, key, pluginType);
     }
 
+    @Override
+    public boolean add(final PluginType<?> pluginType) {
+        return pluginType == merge(pluginType.getKey(), 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
@@ -146,17 +131,10 @@ public class PluginCategory implements 
Iterable<PluginType<?>> {
         return result;
     }
 
-    public int mergeAll(final PluginCategory category) {
+    public void 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();
+            category.forEach(this::merge);
         }
-        return 0;
     }
 
     @Override
@@ -170,4 +148,12 @@ public class PluginCategory implements 
Iterable<PluginType<?>> {
     public void forEach(final BiConsumer<? super String, ? super 
PluginType<?>> biConsumer) {
         plugins.forEach(biConsumer);
     }
+
+    public void forEachMatching(
+            final Predicate<? super PluginType<?>> predicate, final Consumer<? 
super PluginType<?>> consumer) {
+        plugins.values()
+                .stream()
+                .filter(predicate)
+                .forEach(consumer);
+    }
 }
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 09762c4a0e..d665fac555 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
@@ -33,15 +33,18 @@ import java.net.URI;
 import java.net.URL;
 import java.text.DecimalFormat;
 import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 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;
 
+import static org.apache.logging.log4j.util.Unbox.box;
+
 /**
  * Registry singleton for PluginType maps partitioned by source type and then 
by category names.
  */
@@ -57,19 +60,44 @@ public class PluginRegistry {
     private static final Supplier<PluginRegistry> INSTANCE = new 
LazyValue<>(PluginRegistry::new);
 
     /**
-     * Contains plugins found in Log4j2Plugins.dat cache files in the main 
CLASSPATH.
+     * Contains plugins found from {@link PluginService} services and legacy 
Log4j2Plugins.dat cache files in the main CLASSPATH.
      */
-    private final AtomicReference<PluginBundle> pluginsByCategoryRef = new 
AtomicReference<>();
+    private final LazyValue<Categories> mainPluginCategories = 
LazyValue.from(() -> {
+        final Categories bundle = 
decodeCacheFiles(LoaderUtil.getClassLoader());
+        Throwable throwable = null;
+        ClassLoader errorClassLoader = null;
+        boolean allFail = true;
+        for (ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
+            try {
+                loadPlugins(classLoader, bundle);
+                allFail = false;
+            } catch (Throwable ex) {
+                if (throwable == null) {
+                    throwable = ex;
+                    errorClassLoader = classLoader;
+                }
+            }
+        }
+        if (allFail && throwable != null) {
+            LOGGER.debug("Unable to retrieve provider from ClassLoader {}", 
errorClassLoader, throwable);
+        }
+        if (bundle.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.
+            loadFromPackage(bundle, "org.apache.logging.log4j.core");
+        }
+        return bundle;
+    });
 
     /**
-     * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles.
+     * Contains plugins found in PluginService services and legacy 
Log4j2Plugins.dat cache files in OSGi Bundles.
      */
-    private final ConcurrentMap<Long, PluginBundle> 
pluginsByCategoryByBundleId = new ConcurrentHashMap<>();
+    private final Map<Long, Categories> pluginCategoriesByBundleId = new 
ConcurrentHashMap<>();
 
     /**
      * Contains plugins found by searching for annotated classes at runtime.
      */
-    private final ConcurrentMap<String, PluginBundle> 
pluginsByCategoryByPackage = new ConcurrentHashMap<>();
+    private final Map<String, Categories> pluginCategoriesByPackage = new 
ConcurrentHashMap<>();
 
     private PluginRegistry() {
     }
@@ -88,36 +116,9 @@ public class PluginRegistry {
      * Resets the registry to an empty state.
      */
     public void clear() {
-        pluginsByCategoryRef.set(null);
-        pluginsByCategoryByPackage.clear();
-        pluginsByCategoryByBundleId.clear();
-    }
-
-    public void forEachOsgiPluginBundle(final Consumer<? super PluginBundle> 
consumer) {
-        pluginsByCategoryByBundleId.values().forEach(consumer);
-    }
-
-    /**
-     * Retrieve plugins from the main classloader.
-     * @return Map of the List of PluginTypes by category.
-     * @since 2.1
-     */
-    public PluginBundle loadFromMainClassLoader() {
-        final var existing = pluginsByCategoryRef.get();
-        if (existing != null) {
-            // already loaded
-            return existing;
-        }
-        final PluginBundle newPluginsByCategory = 
decodeCacheFiles(LoaderUtil.getClassLoader());
-        loadPlugins(newPluginsByCategory);
-
-        // Note multiple threads could be calling this method concurrently. 
Both will do the work,
-        // but only one will be allowed to store the result in the 
AtomicReference.
-        // Return the map produced by whichever thread won the race, so all 
callers will get the same result.
-        if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) {
-            return newPluginsByCategory;
-        }
-        return pluginsByCategoryRef.get();
+        mainPluginCategories.set(null);
+        pluginCategoriesByPackage.clear();
+        pluginCategoriesByBundleId.clear();
     }
 
     /**
@@ -126,82 +127,45 @@ public class PluginRegistry {
      * @since 2.1
      */
     public void clearBundlePlugins(final long bundleId) {
-        pluginsByCategoryByBundleId.remove(bundleId);
+        pluginCategoriesByBundleId.remove(bundleId);
     }
 
     /**
      * Load plugins from a bundle.
      * @param bundleId The bundle id.
      * @param loader The ClassLoader.
-     * @return the Map of Lists of plugins organized by category.
-     * @since 2.1
+     * @since 3.0.0
      */
-    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 PluginBundle newPluginsByCategory = decodeCacheFiles(loader);
-        loadPlugins(loader, newPluginsByCategory);
-
-        // Note multiple threads could be calling this method concurrently. 
Both will do the work,
-        // but only one will be allowed to store the result in the outer map.
-        // Return the inner map produced by whichever thread won the race, so 
all callers will get the same result.
-        existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, 
newPluginsByCategory);
-        if (existing != null) {
-            return existing;
-        }
-        return newPluginsByCategory;
+    public void loadFromBundle(final long bundleId, final ClassLoader loader) {
+        pluginCategoriesByBundleId.computeIfAbsent(bundleId, ignored -> {
+            final Categories bundle = decodeCacheFiles(loader);
+            loadPlugins(loader, bundle);
+            return bundle;
+        });
     }
 
     /**
      * Loads all the plugins in a Bundle.
-     * @param categories All the categories in the bundle.
-     * @param bundleId The bundle Id.
-     * @since 3.0
+     * @param bundleId The bundle id.
+     * @param pluginsByCategory the plugins organized by category
+     * @since 3.0.0
      */
-    public void loadFromBundle(PluginBundle categories, Long bundleId) {
-        pluginsByCategoryByBundleId.put(bundleId, categories);
-    }
-
-    /**
-     * Load plugins across all ClassLoaders.
-     * @param map The Map of the lists of plugins organized by category.
-     * @since 3.0
-     */
-    public void loadPlugins(PluginBundle map) {
-        Throwable throwable = null;
-        ClassLoader errorClassLoader = null;
-        boolean allFail = true;
-        for (ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
-            try {
-                loadPlugins(classLoader, map);
-                allFail = false;
-            } catch (Throwable ex) {
-                if (throwable == null) {
-                    throwable = ex;
-                    errorClassLoader = classLoader;
-                }
-            }
-        }
-        if (allFail && throwable != null) {
-            LOGGER.debug("Unable to retrieve provider from ClassLoader {}", 
errorClassLoader, throwable);
-        }
+    public void loadFromBundle(final long bundleId, final Map<String, 
PluginCategory> pluginsByCategory) {
+        pluginCategoriesByBundleId.put(bundleId, new 
Categories(pluginsByCategory));
     }
 
     /**
      * Load plugins from a specific ClassLoader.
      * @param classLoader The ClassLoader.
-     * @param bundle The PluginBundle to merge discovered plugins to
+     * @param categories The Categories to merge discovered plugins to
      * @since 3.0
      */
-    public void loadPlugins(ClassLoader classLoader, PluginBundle bundle) {
+    private void loadPlugins(ClassLoader classLoader, Categories categories) {
         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.getBundle().forEach((category, plugins) -> 
pluginCount.addAndGet(bundle.merge(plugins)));
+            pluginService.getCategories().values().forEach(category -> 
pluginCount.addAndGet(categories.merge(category)));
         }
         final int numPlugins = pluginCount.get();
         LOGGER.debug(() -> {
@@ -212,7 +176,7 @@ public class PluginRegistry {
         });
     }
 
-    private PluginBundle decodeCacheFiles(final ClassLoader classLoader) {
+    private Categories decodeCacheFiles(final ClassLoader classLoader) {
         final long startTime = System.nanoTime();
         final PluginCache cache = new PluginCache();
         try {
@@ -225,12 +189,12 @@ public class PluginRegistry {
         } catch (final IOException ioe) {
             LOGGER.warn("Unable to preload plugins", ioe);
         }
-        final PluginBundle newPluginsByCategory = new PluginBundle();
+        final Categories categories = new Categories();
         final AtomicInteger pluginCount = new AtomicInteger();
         cache.getAllCategories().forEach((key, outer) ->
                 outer.values().forEach(entry -> {
                     final PluginType<?> type = new PluginType<>(entry, 
classLoader);
-                    newPluginsByCategory.add(type);
+                    categories.add(type);
                     pluginCount.incrementAndGet();
                 }));
         final int numPlugins = pluginCount.get();
@@ -240,26 +204,14 @@ public class PluginRegistry {
             return "Took " + numFormat.format((endTime - startTime) * 1e-9) +
                     " seconds to load " + numPlugins + " plugins from " + 
classLoader;
         });
-        return newPluginsByCategory;
+        return categories;
     }
 
-    /**
-     * Load plugin types from a package.
-     * @param pkg The package name.
-     * @return A Map of the lists of plugin types organized by category.
-     * @since 2.1
-     */
-    public PluginBundle loadFromPackage(final String pkg) {
+    private void loadFromPackage(final Categories bundle, final String pkg) {
         if (Strings.isBlank(pkg)) {
             // happens when splitting an empty string
-            return new PluginBundle();
+            return;
         }
-        PluginBundle existing = pluginsByCategoryByPackage.get(pkg);
-        if (existing != null) {
-            // already loaded this package
-            return existing;
-        }
-
         final long startTime = System.nanoTime();
         final ResolverUtil resolver = new ResolverUtil();
         final ClassLoader classLoader = LoaderUtil.getClassLoader(getClass(), 
LoaderUtil.class);
@@ -268,34 +220,31 @@ public class PluginRegistry {
         }
         resolver.findInPackage(new PluginTest(), pkg);
 
-        final PluginBundle newPluginsByCategory = new PluginBundle();
         for (final Class<?> clazz : resolver.getClasses()) {
             final Plugin plugin = clazz.getAnnotation(Plugin.class);
             final PluginEntry mainEntry = new PluginEntry();
             final String mainElementName = plugin.elementType().equals(
-                Plugin.EMPTY) ? plugin.name() : plugin.elementType();
+                    Plugin.EMPTY) ? plugin.name() : plugin.elementType();
             mainEntry.setKey(plugin.name().toLowerCase());
             mainEntry.setName(plugin.name());
             mainEntry.setCategory(plugin.category());
             mainEntry.setClassName(clazz.getName());
             mainEntry.setPrintable(plugin.printObject());
             mainEntry.setDefer(plugin.deferChildren());
-            final PluginType<?> mainType = new PluginType<>(mainEntry, clazz, 
mainElementName);
-            newPluginsByCategory.add(mainType);
+            bundle.add(new PluginType<>(mainEntry, clazz, mainElementName));
             final PluginAliases pluginAliases = 
clazz.getAnnotation(PluginAliases.class);
             if (pluginAliases != null) {
                 for (final String alias : pluginAliases.value()) {
                     final PluginEntry aliasEntry = new PluginEntry();
                     final String aliasElementName = 
plugin.elementType().equals(
-                        Plugin.EMPTY) ? alias.trim() : plugin.elementType();
+                            Plugin.EMPTY) ? alias.trim() : 
plugin.elementType();
                     aliasEntry.setKey(alias.trim().toLowerCase());
                     aliasEntry.setName(plugin.name());
                     aliasEntry.setCategory(plugin.category());
                     aliasEntry.setClassName(clazz.getName());
                     aliasEntry.setPrintable(plugin.printObject());
                     aliasEntry.setDefer(plugin.deferChildren());
-                    final PluginType<?> aliasType = new 
PluginType<>(aliasEntry, clazz, aliasElementName);
-                    newPluginsByCategory.add(aliasType);
+                    bundle.add(new PluginType<>(aliasEntry, clazz, 
aliasElementName));
                 }
             }
         }
@@ -305,44 +254,36 @@ public class PluginRegistry {
             return "Took " + numFormat.format((endTime - startTime) * 1e-9) +
                     " seconds to load " + resolver.getClasses().size() + " 
plugins from package " + pkg;
         });
-
-        // Note multiple threads could be calling this method concurrently. 
Both will do the work,
-        // but only one will be allowed to store the result in the outer map.
-        // Return the inner map produced by whichever thread won the race, so 
all callers will get the same result.
-        existing = pluginsByCategoryByPackage.putIfAbsent(pkg, 
newPluginsByCategory);
-        if (existing != null) {
-            return existing;
-        }
-        return newPluginsByCategory;
     }
 
     /**
      * 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) {
+    public PluginCategory getCategory(final String categoryName, final 
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 Categories builtInPlugins = mainPluginCategories.get();
+        if (builtInPlugins != null) {
+            category.mergeAll(builtInPlugins.get(categoryName));
         }
-        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))));
+        pluginCategoriesByBundleId.values().forEach(bundle -> 
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)));
+                
category.mergeAll(pluginCategoriesByPackage.computeIfAbsent(pkg, ignored -> {
+                    final var bundle = new Categories();
+                    loadFromPackage(bundle, pkg);
+                    return bundle;
+                }).get(categoryName));
             }
         }
 
-        LOGGER.debug("Discovered {} new plugins in category '{}'", 
addedCount.get(), categoryName);
+        LOGGER.debug("Discovered {} plugins in category '{}'", 
box(category.size()), categoryName);
 
         return category;
     }
@@ -379,4 +320,51 @@ public class PluginRegistry {
             return false;
         }
     }
+
+    /**
+     * Bundles plugins by category from a plugin source.
+     */
+    private static class Categories implements Iterable<PluginCategory> {
+        private final Map<String, PluginCategory> categories;
+
+        private Categories() {
+            categories = new LinkedHashMap<>();
+        }
+
+        private Categories(final Map<String, PluginCategory> categories) {
+            this.categories = categories;
+        }
+
+        public boolean isEmpty() {
+            return categories.isEmpty();
+        }
+
+        public int merge(final PluginCategory category) {
+            final PluginCategory existingCategory = 
getOrCreate(category.getKey());
+            int added = 0;
+            for (final PluginType<?> pluginType : category) {
+                if (existingCategory.add(pluginType)) {
+                    added++;
+                }
+            }
+            return added;
+        }
+
+        public void add(final PluginType<?> pluginType) {
+            getOrCreate(pluginType.getCategory()).put(pluginType);
+        }
+
+        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));
+        }
+
+        @Override
+        public Iterator<PluginCategory> iterator() {
+            return categories.values().iterator();
+        }
+    }
 }
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 f934936356..dca0daaa94 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
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.plugins.util;
 import org.apache.logging.log4j.plugins.processor.PluginEntry;
 import org.apache.logging.log4j.util.LazyValue;
 
+import java.util.Set;
 import java.util.function.Supplier;
 
 /**
@@ -32,6 +33,7 @@ public class PluginType<T> {
     private final PluginEntry pluginEntry;
     private final Supplier<Class<T>> pluginClass;
     private final String elementName;
+    private final Supplier<Set<Class<?>>> implementedInterfaces;
 
     /**
      * Constructor.
@@ -45,6 +47,8 @@ public class PluginType<T> {
         this.pluginEntry = pluginEntry;
         this.pluginClass = () -> pluginClass;
         this.elementName = elementName;
+        final var interfaces = Set.of(pluginClass.getInterfaces());
+        this.implementedInterfaces = () -> interfaces;
     }
 
     /**
@@ -54,7 +58,7 @@ public class PluginType<T> {
      */
     public PluginType(final PluginEntry pluginEntry, final ClassLoader 
classLoader) {
         this.pluginEntry = pluginEntry;
-        this.pluginClass = new LazyValue<>(() -> {
+        final LazyValue<Class<T>> classProvider = LazyValue.from(() -> {
             try {
                 return 
TypeUtil.cast(classLoader.loadClass(pluginEntry.getClassName()));
             } catch (final ClassNotFoundException e) {
@@ -62,6 +66,14 @@ public class PluginType<T> {
                         " located for element " + pluginEntry.getName(), e);
             }
         });
+        this.pluginClass = classProvider;
+        final Class<?>[] interfaces = pluginEntry.getInterfaces();
+        if (interfaces != null) {
+            final var implementedInterfaces = Set.of(interfaces);
+            this.implementedInterfaces = () -> implementedInterfaces;
+        } else {
+            this.implementedInterfaces = classProvider.map(clazz -> 
Set.of(clazz.getInterfaces()));
+        }
         this.elementName = pluginEntry.getName();
     }
 
@@ -73,6 +85,10 @@ public class PluginType<T> {
         return pluginClass.get();
     }
 
+    public Set<Class<?>> getImplementedInterfaces() {
+        return implementedInterfaces.get();
+    }
+
     public String getElementName() {
         return this.elementName;
     }

Reply via email to