You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2022/05/15 04:20:47 UTC
[logging-log4j2] 02/02: LOG4J2-3496 - Support injection via container types
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 <ma...@apache.org>
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 <ma...@apache.org>
---
.../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: