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; }
