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 5a0eafd10956738d8ab4ce00325651a29ec58bce Author: Matt Sicker <[email protected]> AuthorDate: Sat May 14 23:20:16 2022 -0500 LOG4J2-3496 - Support injection via container types - Adds Category qualifier annotation type for injecting plugins from a category - Adds support for injecting Optional<P>, Collection<P>, Iterable<P>, Set<P>, Stream<P>, List<P>, and Map<String, P>, for a given category - Moves PluginRegistry into a singleton bean - Update OSGi activation to work with PluginRegistry Signed-off-by: Matt Sicker <[email protected]> --- .../apache/logging/log4j/util/ServiceRegistry.java | 4 +- .../apache/logging/log4j/core/osgi/Activator.java | 34 +++++++ .../plugins/test/validation/generic/AlphaBean.java | 31 ++++++ .../plugins/test/validation/generic/BaseBean.java | 25 +++++ .../plugins/test/validation/generic/BetaBean.java | 28 +++++ .../plugins/test/validation/generic/GammaBean.java | 26 +++++ .../logging/log4j/plugins/di/InjectorTest.java | 35 +++++++ .../org/apache/logging/log4j/plugins/Category.java | 42 ++++++++ .../logging/log4j/plugins/di/DefaultInjector.java | 113 +++++++++++++++++---- .../log4j/plugins/name/CategoryNameProvider.java | 30 ++++++ .../logging/log4j/plugins/osgi/Activator.java | 77 ++++++++------ .../logging/log4j/plugins/util/PluginRegistry.java | 18 +--- src/site/asciidoc/manual/dependencyinjection.adoc | 1 + src/site/asciidoc/manual/plugins.adoc | 4 +- 14 files changed, 395 insertions(+), 73 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java index 2268ffaddd..83a40fdb73 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java @@ -105,7 +105,9 @@ public class ServiceRegistry { * @param <S> type of service */ public <S> void registerBundleServices(final Class<S> serviceType, final long bundleId, final List<S> services) { - bundleServices.computeIfAbsent(bundleId, ignored -> new ConcurrentHashMap<>()).put(serviceType, services); + bundleServices.computeIfAbsent(bundleId, ignored -> new ConcurrentHashMap<>()) + .computeIfAbsent(serviceType, ignored -> new ArrayList<>()) + .addAll(cast(services)); } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java index 332275d488..4549e63ca6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java @@ -20,16 +20,22 @@ package org.apache.logging.log4j.core.osgi; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.ContextDataProvider; +import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.plugins.di.InjectorCallback; +import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.plugins.processor.PluginService; +import org.apache.logging.log4j.plugins.util.PluginRegistry; import org.apache.logging.log4j.spi.Provider; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ServiceRegistry; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWiring; +import java.util.Hashtable; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** @@ -37,13 +43,33 @@ import java.util.concurrent.atomic.AtomicReference; */ public final class Activator implements BundleActivator { private final AtomicReference<BundleContext> contextRef = new AtomicReference<>(); + private ServiceRegistration<PluginRegistry> pluginRegistryServiceRegistration; + private PluginRegistry pluginRegistry; + private ServiceRegistration<InjectorCallback> injectorCallbackServiceRegistration; + private InjectorCallback injectorCallback; @Override public void start(final BundleContext context) throws Exception { + pluginRegistryServiceRegistration = context.registerService(PluginRegistry.class, new PluginRegistry(), new Hashtable<>()); + pluginRegistry = context.getService(pluginRegistryServiceRegistration.getReference()); + injectorCallbackServiceRegistration = context.registerService(InjectorCallback.class, new InjectorCallback() { + @Override + public void configure(final Injector injector) { + injector.registerBinding(Key.forClass(PluginRegistry.class), + () -> context.getService(pluginRegistryServiceRegistration.getReference())); + } + + @Override + public int getOrder() { + return -50; + } + }, new Hashtable<>()); + injectorCallback = context.getService(injectorCallbackServiceRegistration.getReference()); final ServiceRegistry registry = ServiceRegistry.getInstance(); final Bundle bundle = context.getBundle(); final long bundleId = bundle.getBundleId(); final ClassLoader classLoader = bundle.adapt(BundleWiring.class).getClassLoader(); + registry.registerBundleServices(InjectorCallback.class, bundleId, List.of(injectorCallback)); registry.loadServicesFromBundle(PluginService.class, bundleId, classLoader); registry.loadServicesFromBundle(Provider.class, bundleId, classLoader); registry.loadServicesFromBundle(ContextDataProvider.class, bundleId, classLoader); @@ -58,6 +84,14 @@ public final class Activator implements BundleActivator { @Override public void stop(final BundleContext context) throws Exception { ServiceRegistry.getInstance().unregisterBundleServices(context.getBundle().getBundleId()); + if (injectorCallback != null) { + injectorCallback = null; + injectorCallbackServiceRegistration.unregister(); + } + if (pluginRegistry != null) { + pluginRegistry = null; + pluginRegistryServiceRegistration.unregister(); + } this.contextRef.compareAndSet(context, null); LogManager.shutdown(false, true); } diff --git a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/AlphaBean.java b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/AlphaBean.java new file mode 100644 index 0000000000..40ce717911 --- /dev/null +++ b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/AlphaBean.java @@ -0,0 +1,31 @@ +/* + * 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.test.validation.generic; + +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.Singleton; + +@Plugin(category = "Bean", name = "Alpha") +@Singleton +public class AlphaBean implements BaseBean { + @Factory + static AlphaBean newAlphaBean() { + return new AlphaBean(); + } +} diff --git a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/BaseBean.java b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/BaseBean.java new file mode 100644 index 0000000000..e481a085d9 --- /dev/null +++ b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/BaseBean.java @@ -0,0 +1,25 @@ +/* + * 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.test.validation.generic; + +import org.apache.logging.log4j.plugins.Category; + +@Category("Bean") +public +interface BaseBean { +} diff --git a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/BetaBean.java b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/BetaBean.java new file mode 100644 index 0000000000..f872c47348 --- /dev/null +++ b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/BetaBean.java @@ -0,0 +1,28 @@ +/* + * 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.test.validation.generic; + +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginOrder; +import org.apache.logging.log4j.plugins.Singleton; + +@Plugin(category = "Bean", name = "Beta") +@PluginOrder(PluginOrder.FIRST) +@Singleton +public class BetaBean implements BaseBean { +} diff --git a/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/GammaBean.java b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/GammaBean.java new file mode 100644 index 0000000000..73519484d5 --- /dev/null +++ b/log4j-plugins-test/src/main/java/org/apache/logging/log4j/plugins/test/validation/generic/GammaBean.java @@ -0,0 +1,26 @@ +/* + * 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.test.validation.generic; + +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.Singleton; + +@Plugin(category = "Bean", name = "Gamma") +@Singleton +public class GammaBean implements BaseBean { +} 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 5fb1ba3f5d..7ce4484220 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 @@ -17,6 +17,7 @@ package org.apache.logging.log4j.plugins.di; +import org.apache.logging.log4j.plugins.Category; import org.apache.logging.log4j.plugins.Factory; import org.apache.logging.log4j.plugins.Inject; import org.apache.logging.log4j.plugins.Named; @@ -31,6 +32,9 @@ import org.apache.logging.log4j.plugins.ScopeType; import org.apache.logging.log4j.plugins.Singleton; import org.apache.logging.log4j.plugins.processor.PluginEntry; import org.apache.logging.log4j.plugins.test.validation.ValidatingPluginWithGenericBuilder; +import org.apache.logging.log4j.plugins.test.validation.generic.BaseBean; +import org.apache.logging.log4j.plugins.test.validation.generic.BetaBean; +import org.apache.logging.log4j.plugins.test.validation.generic.GammaBean; import org.apache.logging.log4j.plugins.util.PluginType; import org.apache.logging.log4j.plugins.util.TypeUtil; import org.apache.logging.log4j.plugins.validation.constraints.Required; @@ -41,14 +45,17 @@ import org.junit.jupiter.api.parallel.Resources; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -789,4 +796,32 @@ class InjectorTest { assertThat(first.c).isNotNull().isNotSameAs(second.c); assertThat(second.c).isNotNull(); } + + /** + * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-3496">LOG4J2-3496</a> + */ + static class ContainerPluginBeanInjection { + @Category("Bean") Optional<BaseBean> optional; + @Category("Bean") Collection<BaseBean> collection; + @Category("Bean") Iterable<BaseBean> iterable; + @Category("Bean") Set<BaseBean> set; + @Category("Bean") Stream<BaseBean> stream; + @Category("Bean") List<BaseBean> list; + @Category("Bean") Map<String, BaseBean> map; + } + + @Test + void categoryQualifierInjection() { + final ContainerPluginBeanInjection instance = DI.createInjector() + .registerBinding(Keys.PLUGIN_PACKAGES_KEY, () -> List.of(BaseBean.class.getPackageName())) + .getInstance(ContainerPluginBeanInjection.class); + assertThat(instance.list).hasSize(3).first().isInstanceOf(BetaBean.class); + assertThat(instance.collection).containsExactlyElementsOf(instance.list); + assertThat(instance.iterable).containsExactlyElementsOf(instance.list); + assertThat(instance.set).containsExactlyElementsOf(instance.list); + assertThat(instance.stream).containsExactlyElementsOf(instance.list); + assertThat(instance.map).hasSize(3); + assertThat(instance.map.get("gamma")).isInstanceOf(GammaBean.class); + assertThat(instance.optional).get().isInstanceOf(BetaBean.class); + } } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Category.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Category.java new file mode 100644 index 0000000000..7f7e47a5e8 --- /dev/null +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Category.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.plugins; + +import org.apache.logging.log4j.plugins.name.CategoryNameProvider; +import org.apache.logging.log4j.plugins.name.NameProvider; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Qualifier used for matching against injectable plugins in a given category. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.TYPE_USE }) +@Documented +@QualifierType +@NameProvider(CategoryNameProvider.class) +public @interface Category { + /** + * The category to use plugins from. + */ + String value(); +} 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 fe2c0097d8..590ba47bd9 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 @@ -18,11 +18,13 @@ package org.apache.logging.log4j.plugins.di; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.plugins.Category; 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.PluginOrder; import org.apache.logging.log4j.plugins.QualifierType; import org.apache.logging.log4j.plugins.ScopeType; import org.apache.logging.log4j.plugins.Singleton; @@ -250,21 +252,57 @@ class DefaultInjector implements Injector { if (existing != null) { return existing.getSupplier(); } + final Class<T> rawType = key.getRawType(); final Scope scope = getScopeForType(rawType); + + // @Named PluginCategory injection if (rawType == PluginCategory.class && key.getQualifierType() == Named.class) { final Key<PluginCategory> pluginCategoryKey = TypeUtil.cast(key); final Supplier<PluginCategory> pluginCategoryFactory = createPluginCategoryFactory(pluginCategoryKey); - bindingMap.put(pluginCategoryKey, pluginCategoryFactory); + bindingMap.putIfAbsent(pluginCategoryKey, pluginCategoryFactory); + return bindingMap.get(key, aliases).getSupplier(); + } + + // @Category Collection<T>/Map<String, T>/Stream<T> injection + if (key.getQualifierType() == Category.class) { + if (Stream.class.isAssignableFrom(rawType)) { + final Key<Stream<T>> streamKey = TypeUtil.cast(key); + final Supplier<Stream<T>> streamFactory = + () -> streamPluginInstancesFromCategory(key.getParameterizedTypeArgument(0)); + bindingMap.putIfAbsent(streamKey, streamFactory); + } else if (Set.class.isAssignableFrom(rawType)) { + final Key<Set<T>> setKey = TypeUtil.cast(key); + final Supplier<Set<T>> setFactory = () -> getPluginSet(key.getParameterizedTypeArgument(0)); + bindingMap.putIfAbsent(setKey, setFactory); + } else if (Map.class.isAssignableFrom(rawType)) { + final Key<Map<String, T>> mapKey = TypeUtil.cast(key); + final Supplier<Map<String, T>> mapFactory = () -> getPluginMap(key.getParameterizedTypeArgument(1)); + bindingMap.putIfAbsent(mapKey, mapFactory); + } else if (Iterable.class.isAssignableFrom(rawType)) { + final Key<Iterable<T>> iterableKey = TypeUtil.cast(key); + final Supplier<Iterable<T>> iterableFactory = () -> getPluginList(key.getParameterizedTypeArgument(0)); + bindingMap.putIfAbsent(iterableKey, iterableFactory); + } else if (Optional.class.isAssignableFrom(rawType)) { + final Key<Optional<T>> optionalKey = TypeUtil.cast(key); + final Supplier<Optional<T>> optionalFactory = () -> getOptionalPlugin(key.getParameterizedTypeArgument(0)); + bindingMap.putIfAbsent(optionalKey, optionalFactory); + } else { + throw new InjectException("Cannot inject plugins into " + key); + } return bindingMap.get(key, aliases).getSupplier(); } + + // Optional<T> injection if (rawType == Optional.class) { final Key<Optional<T>> optionalKey = TypeUtil.cast(key); - final Supplier<Optional<T>> optionalFactory = - createOptionalFactory(key.getParameterizedTypeArgument(0), aliases, node, chain); + final Supplier<Optional<T>> optionalFactory = () -> + getOptionalInstance(key.getParameterizedTypeArgument(0), aliases, node, chain); bindingMap.put(optionalKey, optionalFactory); return bindingMap.get(key, aliases).getSupplier(); } + + // generic T injection final Supplier<T> instanceSupplier = () -> { final StringBuilder debugLog = new StringBuilder(); final T instance = TypeUtil.cast(getInjectableInstance(key, node, chain, debugLog)); @@ -275,24 +313,57 @@ class DefaultInjector implements Injector { } 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); - }); + return LazyValue.from(() -> getInstance(PluginRegistry.class).getCategory(key.getName(), getPluginPackages())); + } + + private List<String> getPluginPackages() { + final Binding<List<String>> pluginPackagesBinding = bindingMap.get(Keys.PLUGIN_PACKAGES_KEY, List.of()); + return pluginPackagesBinding != null ? pluginPackagesBinding.getSupplier().get() : List.of(); + } + + private <T> Stream<PluginType<? extends T>> streamPluginsFromCategory(final Key<T> itemKey) { + if (itemKey == null) { + return Stream.empty(); + } + final PluginCategory category = getInstance(PluginRegistry.class).getCategory(itemKey.getName(), getPluginPackages()); + final Type type = itemKey.getType(); + final Class<T> rawType = itemKey.getRawType(); + return category.stream() + .filter(pluginType -> rawType.isInterface() && TypeUtil.isAssignable(type, pluginType.getPluginClass()) || + TypeUtil.isAssignable(type, pluginType.getPluginClass())) + .sorted(Comparator.comparing(PluginType::getPluginClass, PluginOrder.COMPARATOR)) + .map(TypeUtil::cast); } - private <T> Supplier<Optional<T>> createOptionalFactory( + private <T> Stream<T> streamPluginInstancesFromCategory(final Key<T> key) { + return streamPluginsFromCategory(key).map(pluginType -> getInstance(pluginType.getPluginClass())); + } + + private <T> Set<T> getPluginSet(final Key<T> key) { + return streamPluginInstancesFromCategory(key).collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private <T> Map<String, T> getPluginMap(final Key<T> key) { + return streamPluginsFromCategory(key).collect( + Collectors.toMap(PluginType::getKey, pluginType -> getInstance(pluginType.getPluginClass()), (lhs, rhs) -> lhs, + LinkedHashMap::new)); + } + + private <T> List<T> getPluginList(final Key<T> key) { + return streamPluginInstancesFromCategory(key).collect(Collectors.toList()); + } + + public <T> Optional<T> getOptionalPlugin(final Key<T> key) { + return streamPluginInstancesFromCategory(key).findFirst(); + } + + private <T> Optional<T> getOptionalInstance( 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(); - } - }; + try { + return Optional.ofNullable(getFactory(key, aliases, node, chain).get()); + } catch (final PluginException e) { + return Optional.empty(); + } } private Object getInjectableInstance( @@ -361,13 +432,11 @@ class DefaultInjector implements Injector { if (conflictingConverter != null) { final boolean overridable; if (converter instanceof Comparable) { - @SuppressWarnings("unchecked") - final Comparable<TypeConverter<?>> comparableConverter = + @SuppressWarnings("unchecked") final Comparable<TypeConverter<?>> comparableConverter = (Comparable<TypeConverter<?>>) converter; overridable = comparableConverter.compareTo(conflictingConverter) < 0; } else if (conflictingConverter instanceof Comparable) { - @SuppressWarnings("unchecked") - final Comparable<TypeConverter<?>> comparableConflictingConverter = + @SuppressWarnings("unchecked") final Comparable<TypeConverter<?>> comparableConflictingConverter = (Comparable<TypeConverter<?>>) conflictingConverter; overridable = comparableConflictingConverter.compareTo(converter) > 0; } else { diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/CategoryNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/CategoryNameProvider.java new file mode 100644 index 0000000000..9772498072 --- /dev/null +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/CategoryNameProvider.java @@ -0,0 +1,30 @@ +/* + * 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.name; + +import org.apache.logging.log4j.plugins.Category; +import org.apache.logging.log4j.util.Strings; + +import java.util.Optional; + +public class CategoryNameProvider implements AnnotatedElementNameProvider<Category> { + @Override + public Optional<String> getSpecifiedName(final Category annotation) { + return Strings.trimToOptional(annotation.value()); + } +} 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 6ff109df5d..f17cd67d7c 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 @@ -44,34 +44,22 @@ public final class Activator implements BundleActivator, SynchronousBundleListen private static final Logger LOGGER = StatusLogger.getLogger(); private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); + public static final String CORE_MODULE_NAME = "org.apache.logging.log4j.core"; private final AtomicReference<BundleContext> contextRef = new AtomicReference<>(); + private int state = Bundle.UNINSTALLED; + + private ServiceReference<PluginRegistry> pluginRegistryServiceReference; + private PluginRegistry pluginRegistry; + @Override public void start(final BundleContext bundleContext) throws Exception { - loadPlugins(bundleContext); + state = Bundle.STARTING; bundleContext.addBundleListener(this); - final Bundle[] bundles = bundleContext.getBundles(); - for (final Bundle bundle : bundles) { - loadPlugins(bundle); - } - scanInstalledBundlesForPlugins(bundleContext); this.contextRef.compareAndSet(null, bundleContext); } - private void loadPlugins(final BundleContext bundleContext) { - final PluginRegistry pluginRegistry = PluginRegistry.getInstance(); - try { - final Collection<ServiceReference<PluginService>> serviceReferences = bundleContext.getServiceReferences(PluginService.class, null); - for (final ServiceReference<PluginService> serviceReference : serviceReferences) { - final PluginService pluginService = bundleContext.getService(serviceReference); - pluginRegistry.loadFromBundle(bundleContext.getBundle().getBundleId(), pluginService.getCategories()); - } - } catch (final InvalidSyntaxException ex) { - LOGGER.error("Error accessing Plugins", ex); - } - } - private void loadPlugins(final Bundle bundle) { if (bundle.getState() == Bundle.UNINSTALLED) { return; @@ -83,10 +71,17 @@ public final class Activator implements BundleActivator, SynchronousBundleListen if (bundleContext == null) { LOGGER.debug("Bundle {} has no context (state={}), skipping loading plugins", bundle.getSymbolicName(), toStateString(bundle.getState())); } else { - loadPlugins(bundleContext); + final Collection<ServiceReference<PluginService>> serviceReferences = + bundleContext.getServiceReferences(PluginService.class, null); + for (final ServiceReference<PluginService> serviceReference : serviceReferences) { + final PluginService pluginService = bundleContext.getService(serviceReference); + pluginRegistry.loadFromBundle(bundleContext.getBundle().getBundleId(), pluginService.getCategories()); + } } } catch (final SecurityException e) { LOGGER.debug("Cannot access bundle [{}] contents. Ignoring.", bundle.getSymbolicName(), e); + } catch (final InvalidSyntaxException ex) { + LOGGER.error("Error accessing Plugins", ex); } catch (final Exception e) { LOGGER.warn("Problem checking bundle {} for Log4j 2 provider.", bundle.getSymbolicName(), e); } @@ -117,28 +112,28 @@ public final class Activator implements BundleActivator, SynchronousBundleListen } } - private static void scanInstalledBundlesForPlugins(final BundleContext context) { + private void scanInstalledBundlesForPlugins(final BundleContext context) { final Bundle[] bundles = context.getBundles(); for (final Bundle bundle : bundles) { - // TODO: bundle state can change during this scanBundleForPlugins(bundle); } } - private static void scanBundleForPlugins(final Bundle bundle) { + private void scanBundleForPlugins(final Bundle bundle) { final long bundleId = bundle.getBundleId(); // LOG4J2-920: don't scan system bundle for plugins if (bundle.getState() == Bundle.ACTIVE && bundleId != 0) { LOGGER.trace("Scanning bundle [{}, id={}] for plugins.", bundle.getSymbolicName(), bundleId); final ClassLoader classLoader = bundle.adapt(BundleWiring.class).getClassLoader(); - PluginRegistry.getInstance().loadFromBundle(bundleId, classLoader); + pluginRegistry.loadFromBundle(bundleId, classLoader); } } - private static void stopBundlePlugins(final Bundle bundle) { + private void stopBundlePlugins(final Bundle bundle) { LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName()); - // TODO: plugin lifecycle code - PluginRegistry.getInstance().clearBundlePlugins(bundle.getBundleId()); + if (pluginRegistry != null) { + pluginRegistry.clearBundlePlugins(bundle.getBundleId()); + } } @Override @@ -153,15 +148,33 @@ public final class Activator implements BundleActivator, SynchronousBundleListen @Override public void bundleChanged(final BundleEvent event) { + final Bundle bundle = event.getBundle(); switch (event.getType()) { - // FIXME: STARTING instead of STARTED? - case BundleEvent.STARTED: - loadPlugins(event.getBundle()); - scanBundleForPlugins(event.getBundle()); + case BundleEvent.STARTING: + if (CORE_MODULE_NAME.equals(bundle.getSymbolicName()) && state != Bundle.ACTIVE) { + break; + } break; + case BundleEvent.STARTED: + if (CORE_MODULE_NAME.equals(bundle.getSymbolicName()) && state != Bundle.ACTIVE) { + final BundleContext bundleContext = contextRef.get(); + pluginRegistryServiceReference = + bundleContext.getServiceReference(PluginRegistry.class); + pluginRegistry = bundleContext.getService(pluginRegistryServiceReference); + scanInstalledBundlesForPlugins(bundleContext); + state = Bundle.ACTIVE; + } else if (state == Bundle.ACTIVE) { + loadPlugins(bundle); + scanBundleForPlugins(bundle); + } + case BundleEvent.STOPPING: - stopBundlePlugins(event.getBundle()); + if (CORE_MODULE_NAME.equals(bundle.getSymbolicName()) && pluginRegistry != null) { + pluginRegistry = null; + contextRef.get().ungetService(pluginRegistryServiceReference); + } + stopBundlePlugins(bundle); break; default: 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 d665fac555..867e7d2edf 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 @@ -20,6 +20,7 @@ package org.apache.logging.log4j.plugins.util; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.Singleton; import org.apache.logging.log4j.plugins.processor.PluginCache; import org.apache.logging.log4j.plugins.processor.PluginEntry; import org.apache.logging.log4j.plugins.processor.PluginService; @@ -41,13 +42,13 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -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. */ +@Singleton public class PluginRegistry { /** @@ -57,8 +58,6 @@ public class PluginRegistry { "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat"; private static final Logger LOGGER = StatusLogger.getLogger(); - private static final Supplier<PluginRegistry> INSTANCE = new LazyValue<>(PluginRegistry::new); - /** * Contains plugins found from {@link PluginService} services and legacy Log4j2Plugins.dat cache files in the main CLASSPATH. */ @@ -99,19 +98,6 @@ public class PluginRegistry { */ private final Map<String, Categories> pluginCategoriesByPackage = new ConcurrentHashMap<>(); - private PluginRegistry() { - } - - /** - * Returns the global PluginRegistry instance. - * - * @return the global PluginRegistry instance. - * @since 2.1 - */ - public static PluginRegistry getInstance() { - return INSTANCE.get(); - } - /** * Resets the registry to an empty state. */ diff --git a/src/site/asciidoc/manual/dependencyinjection.adoc b/src/site/asciidoc/manual/dependencyinjection.adoc index 272ba4b461..2f292c53c1 100644 --- a/src/site/asciidoc/manual/dependencyinjection.adoc +++ b/src/site/asciidoc/manual/dependencyinjection.adoc @@ -141,6 +141,7 @@ Some of these bindings were previously configured through various system propert * `org.apache.logging.log4j.core.ContextDataInjector` * `org.apache.logging.log4j.core.config.ConfigurationFactory` +* `org.apache.logging.log4j.core.config.composite.MergeStrategy` * `org.apache.logging.log4j.core.impl.LogEventFactory` * `org.apache.logging.log4j.core.lookup.InterpolatorFactory` * `org.apache.logging.log4j.core.lookup.StrSubstitutor` diff --git a/src/site/asciidoc/manual/plugins.adoc b/src/site/asciidoc/manual/plugins.adoc index f198c86934..af3271bd0e 100644 --- a/src/site/asciidoc/manual/plugins.adoc +++ b/src/site/asciidoc/manual/plugins.adoc @@ -24,11 +24,11 @@ extend the PatternLayout class and add them via code. One goal of Log4j 2 is to make extending it extremely easy through the use of plugins. In Log4j 2 a plugin is declared by adding a -link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/Plugin.html[`@Plugin`] +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/Plugin.html[`@Plugin`] annotation to the class declaration. During initialization the link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/Configuration.html[`Configuration`] will invoke the -link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/util/PluginManager.html[`PluginManager`] +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/util/PluginRegistry.html[`PluginRegistry`] to load the built-in Log4j plugins as well as any custom plugins. The `Injector` locates plugins by looking in five places:
