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/07/23 23:40:37 UTC

[logging-log4j2] branch master updated (5fff1a51d7 -> f120775e53)

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

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


    from 5fff1a51d7 Remove ConditionalOnMissingBinding
     new f92a612422 Add more docs for plugins/DI
     new f120775e53 Move reflection utils from DefaultInjector to ReflectionAccessor

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 log4j-plugins/src/main/java/module-info.java       |   8 +-
 .../logging/log4j/plugins/di/DefaultInjector.java  | 101 +++------------------
 .../apache/logging/log4j/plugins/di/Injector.java  |  40 ++++++--
 .../org/apache/logging/log4j/plugins/di/Key.java   |  15 ++-
 .../log4j/plugins/di/ReflectionAccessor.java       |  61 +++++++++++++
 5 files changed, 127 insertions(+), 98 deletions(-)


[logging-log4j2] 01/02: Add more docs for plugins/DI

Posted by ma...@apache.org.
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 f92a61242211ab496e9542bbbb9d1681ef0a9d9d
Author: Matt Sicker <ma...@apache.org>
AuthorDate: Sat Jul 23 18:00:27 2022 -0500

    Add more docs for plugins/DI
    
    Signed-off-by: Matt Sicker <ma...@apache.org>
---
 log4j-plugins/src/main/java/module-info.java       |  8 +++--
 .../logging/log4j/plugins/di/DefaultInjector.java  |  5 ++-
 .../apache/logging/log4j/plugins/di/Injector.java  | 40 ++++++++++++++++++----
 .../org/apache/logging/log4j/plugins/di/Key.java   | 15 +++++++-
 4 files changed, 56 insertions(+), 12 deletions(-)

diff --git a/log4j-plugins/src/main/java/module-info.java b/log4j-plugins/src/main/java/module-info.java
index b884b96944..96e35e9476 100644
--- a/log4j-plugins/src/main/java/module-info.java
+++ b/log4j-plugins/src/main/java/module-info.java
@@ -16,12 +16,16 @@
  */
 
 /**
- * Log4j plugin annotations and dependency injection system.
+ * Log4j plugin annotations and dependency injection system. Plugins encompass a variety of customizable
+ * Log4j interfaces and classes that are addressable by {@linkplain org.apache.logging.log4j.plugins.Named name} or type
+ * including {@linkplain org.apache.logging.log4j.plugins.Configurable configurable plugins} which are created from a
+ * parsed tree of {@linkplain org.apache.logging.log4j.plugins.Node configuration nodes} along with other
+ * {@linkplain org.apache.logging.log4j.plugins.Namespace namespaces} for different dependency injection purposes.
  *
  * @see org.apache.logging.log4j.plugins.Inject
  * @see org.apache.logging.log4j.plugins.Plugin
  * @see org.apache.logging.log4j.plugins.PluginFactory
- * @see org.apache.logging.log4j.plugins.Namespace
+ * @see org.apache.logging.log4j.plugins.di.Injector
  */
 module org.apache.logging.log4j.plugins {
     exports org.apache.logging.log4j.plugins;
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 aee5cf183c..cfb5d19964 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
@@ -67,7 +67,6 @@ 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;
 import java.util.concurrent.ConcurrentHashMap;
@@ -713,12 +712,12 @@ class DefaultInjector implements Injector {
 
     private Scope getScopeForMethod(final Method method) {
         final Annotation methodScope = AnnotationUtil.getMetaAnnotation(method, ScopeType.class);
-        return methodScope != null ? scopes.get(methodScope.annotationType()) : getScopeForType(method.getReturnType());
+        return methodScope != null ? getScope(methodScope.annotationType()) : getScopeForType(method.getReturnType());
     }
 
     private Scope getScopeForType(final Class<?> type) {
         final Annotation scope = AnnotationUtil.getMetaAnnotation(type, ScopeType.class);
-        return scope != null ? scopes.get(scope.annotationType()) : DefaultScope.INSTANCE;
+        return scope != null ? getScope(scope.annotationType()) : DefaultScope.INSTANCE;
     }
 
     private static boolean isCompatibleValidator(
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
index a35440b87d..c9b01a5aa2 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
@@ -17,6 +17,7 @@
 
 package org.apache.logging.log4j.plugins.di;
 
+import org.apache.logging.log4j.plugins.Configurable;
 import org.apache.logging.log4j.plugins.FactoryType;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.convert.TypeConverter;
@@ -26,16 +27,35 @@ import java.lang.reflect.Type;
 import java.util.function.Supplier;
 
 /**
- * Central interface for dependency injection operations. An Injector maintains a registry of bindings between {@link Key}s to
- * {@link Supplier}s along with a registry of {@link Scope}s for different scope annotation types. Injectors may be
- * {@linkplain #init() initialized} with {@link InjectorCallback} services.
+ * Manages dependency injection of a set of bindings between {@link Key}s and {@link Supplier}s lifecycle-bound to
+ * {@link Scope}s. Keys describe the type, name, namespace, qualifier type, and order of a binding. Suppliers are known
+ * as <i>factories</i>, and factories may have injectable dependencies on other bindings upon creation. Scopes control
+ * the lifecycle of instances returned by a binding's factory.
+ *
+ * <p>When first creating an Injector, invocation of {@link #init()} will invoke all registered {@link InjectorCallback}
+ * services in {@linkplain InjectorCallback#getOrder() numeric order}. These callbacks may register bindings
+ * {@linkplain #registerBinding(Key, Supplier) programmatically} or via {@linkplain #registerBundle(Object) bundle classes},
+ * and set a {@linkplain #setReflectionAccessor(ReflectionAccessor) default reflection accessor} for customizing the
+ * context in which private reflection operations are performed. Additional scopes
+ * {@linkplain #registerScope(Class, Scope) may also be registered}.</p>
+ *
+ * <p>Factories for keys can be looked up {@linkplain #getFactory(Key) by key} or {@linkplain #getFactory(Class) by class}.
+ * Any object created outside this system can have {@linkplain #injectMembers(Object) its members injected}.</p>
+ *
+ * <p>A parsed {@linkplain Node configuration node} can be {@linkplain #configure(Node) configured} using its referenced
+ * plugin class to return the plugin instance. Configuring a node configures its children nodes and consumes its
+ * attributes before returning the plugin instance.</p>
  */
 public interface Injector {
+    /**
+     * The key corresponding to the current Injector.
+     */
     Key<Injector> KEY = new Key<>() {};
 
     /**
      * Initializes this Injector with all registered {@link InjectorCallback} services in
-     * {@linkplain InjectorCallback#getOrder() integral order}.
+     * {@linkplain InjectorCallback#getOrder() integral order}. This method should only be invoked after construction
+     * of a fresh Injector.
      */
     void init();
 
@@ -107,7 +127,13 @@ public interface Injector {
     void injectMembers(final Object instance);
 
     /**
-     * Creates a plugin instance using the provided configuration node.
+     * Creates a plugin instance using the provided configuration node. Unless the plugin
+     * {@linkplain Configurable#deferChildren() defers children nodes}, child nodes are configured first before
+     * configuring this node. Each node's plugin should consume all supported
+     * {@linkplain org.apache.logging.log4j.plugins.PluginAttribute attributes} through dependency injection along with
+     * any other instances that can be provided through
+     * {@linkplain org.apache.logging.log4j.plugins.visit.NodeVisitor node visitor strategies} or general bindings from
+     * {@link #getFactory(Key)}. The end result of {@link Node#getObject()} is returned for convenience.
      *
      * @param node configuration node containing a plugin type, attributes, and child nodes to consume for dependency injection
      * @param <T>  type of instance the given node configures
@@ -133,7 +159,9 @@ public interface Injector {
 
     /**
      * Registers a bundle into this Injector. A bundle is an instance of a class with methods annotated with
-     * {@link FactoryType}-annotated annotations which provide dependency-injected bindings.
+     * {@link FactoryType}-annotated annotations which provide dependency-injected bindings. Bindings registered
+     * via bundles are merged with the existing bindings in this Injector based on the {@linkplain Key#getOrder() order}
+     * of the methods to determine ambiguities.
      *
      * @param bundle bundle to install with factory methods for factories
      */
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 b85658af16..4cd1156465 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
@@ -86,25 +86,38 @@ public class Key<T> {
         hashCode = Objects.hash(type, qualifierType, name.toLowerCase(Locale.ROOT), namespace.toLowerCase(Locale.ROOT));
     }
 
+    /**
+     * Returns the generic type of this key.
+     */
     public final Type getType() {
         return type;
     }
 
+    /**
+     * Returns the raw type of this key corresponding to its generic type.
+     */
     public final Class<T> getRawType() {
         return rawType;
     }
 
+    /**
+     * Returns the name of this key. Names are case-insensitive. If this key has no defined name, then this returns
+     * an empty string.
+     */
     public final String getName() {
         return name;
     }
 
+    /**
+     * Returns the namespace of this key. If this key has no defined namespace, then this returns an empty string.
+     */
     public final String getNamespace() {
         return namespace;
     }
 
     /**
      * Returns the ordinal value of this key. Keys that are otherwise equal can be compared by this
-     * ordinal using the natural integer comparator.
+     * ordinal using the natural integer comparator where ties should default to keeping an existing binding intact.
      */
     public final int getOrder() {
         return order;


[logging-log4j2] 02/02: Move reflection utils from DefaultInjector to ReflectionAccessor

Posted by ma...@apache.org.
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 f120775e53f01f627eafa96ed42df4f49338058a
Author: Matt Sicker <ma...@apache.org>
AuthorDate: Sat Jul 23 18:40:21 2022 -0500

    Move reflection utils from DefaultInjector to ReflectionAccessor
    
    Signed-off-by: Matt Sicker <ma...@apache.org>
---
 .../logging/log4j/plugins/di/DefaultInjector.java  | 96 +++-------------------
 .../log4j/plugins/di/ReflectionAccessor.java       | 61 ++++++++++++++
 2 files changed, 71 insertions(+), 86 deletions(-)

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 cfb5d19964..578e9ae41e 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
@@ -46,13 +46,10 @@ import org.apache.logging.log4j.util.StringBuilders;
 
 import java.lang.annotation.Annotation;
 import java.lang.invoke.MethodHandles;
-import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Executable;
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Parameter;
@@ -226,13 +223,6 @@ class DefaultInjector implements Injector {
         this.accessor = accessor;
     }
 
-    private <M extends AccessibleObject & Member> void makeAccessible(final M member, final Object instance) {
-        final boolean isStatic = Modifier.isStatic(member.getModifiers());
-        if (!member.canAccess(isStatic ? null : instance)) {
-            accessor.makeAccessible(member);
-        }
-    }
-
     private <T> Supplier<T> getFactory(
             final InjectionPoint<T> point, final Node node, final Set<Key<?>> chain, final StringBuilder debugLog) {
         final AnnotatedElement element = point.getElement();
@@ -394,10 +384,9 @@ class DefaultInjector implements Injector {
         final Class<?> rawType = key.getRawType();
         validate(rawType, key.getName(), rawType);
         final Constructor<?> constructor = getInjectableConstructor(key, chain);
-        makeAccessible(constructor, null);
         final List<InjectionPoint<?>> points = InjectionPoint.fromExecutable(constructor);
         final var args = getArguments(key, node, points, chain, debugLog);
-        return newInstance(constructor, args);
+        return accessor.newInstance(constructor, args);
     }
 
     private void validate(final AnnotatedElement element, final String name, final Object value) {
@@ -504,16 +493,15 @@ class DefaultInjector implements Injector {
     }
 
     private <T> void injectField(final Field field, final Node node, final Object instance, final StringBuilder debugLog) {
-        makeAccessible(field, instance);
         final InjectionPoint<T> point = InjectionPoint.forField(field);
         final Supplier<T> factory = getFactory(point, node, Set.of(), debugLog);
         final Key<T> key = point.getKey();
         final Object value = key.getRawType() == Supplier.class ? factory : factory.get();
         if (value != null) {
-            setField(field, instance, value);
+            accessor.setFieldValue(field, instance, value);
         }
         if (AnnotationUtil.isMetaAnnotationPresent(field, Constraint.class)) {
-            final Object fieldValue = getField(field, instance);
+            final Object fieldValue = accessor.getFieldValue(field, instance);
             validate(field, key.getName(), fieldValue);
         }
     }
@@ -525,18 +513,18 @@ class DefaultInjector implements Injector {
         for (Class<?> clazz = rawType; clazz != Object.class; clazz = clazz.getSuperclass()) {
             for (final Method method : clazz.getDeclaredMethods()) {
                 if (isInjectable(method)) {
-                    makeAccessible(method, instance);
+                    accessor.makeAccessible(method, instance);
                     if (method.getParameterCount() == 0) {
                         injectMethodsWithNoArgs.add(method);
                     } else {
                         final List<InjectionPoint<?>> injectionPoints = InjectionPoint.fromExecutable(method);
                         final var args = getArguments(key, node, injectionPoints, chain, debugLog);
-                        invokeMethod(method, instance, args);
+                        accessor.invokeMethod(method, instance, args);
                     }
                 }
             }
         }
-        injectMethodsWithNoArgs.forEach(method -> invokeMethod(method, instance));
+        injectMethodsWithNoArgs.forEach(method -> accessor.invokeMethod(method, instance));
     }
 
     private void inject(final Node node) {
@@ -608,9 +596,9 @@ class DefaultInjector implements Injector {
         }
         final var args = getArguments(key, node, points, Set.of(), debugLog);
         if (factory instanceof Method) {
-            return invokeMethod((Method) factory, null, args);
+            return accessor.invokeMethod((Method) factory, null, args);
         } else {
-            return newInstance((Constructor<?>) factory, args);
+            return accessor.newInstance((Constructor<?>) factory, args);
         }
     }
 
@@ -634,7 +622,7 @@ class DefaultInjector implements Injector {
     }
 
     private <T> List<Binding<T>> createMethodBindings(final Object instance, final Method method) {
-        makeAccessible(method, instance);
+        accessor.makeAccessible(method, instance);
         final Key<T> primaryKey = Key.forMethod(method);
         LOGGER.debug("Checking {} on {} for conditions", primaryKey, method);
         final Conditional conditional = AnnotationUtil.getLogicalAnnotation(method, Conditional.class);
@@ -657,7 +645,7 @@ class DefaultInjector implements Injector {
                         return value;
                     })
                     .toArray();
-            return rethrow(() -> TypeUtil.cast(invokeMethod(method, instance, args)));
+            return TypeUtil.cast(accessor.invokeMethod(method, instance, args));
         };
         final Supplier<T> factory = getScopeForMethod(method).get(primaryKey, unscoped);
         final Collection<String> aliases = Keys.getAliases(method);
@@ -834,70 +822,6 @@ class DefaultInjector implements Injector {
                                 parameter -> AnnotationUtil.isMetaAnnotationPresent(parameter, QualifierType.class));
     }
 
-    private static Object getField(final Field field, final Object instance) {
-        try {
-            return field.get(instance);
-        } catch (final IllegalAccessException e) {
-            throw errorFrom(e);
-        }
-    }
-
-    private static void setField(final Field field, final Object instance, final Object value) {
-        try {
-            field.set(instance, value);
-        } catch (final IllegalAccessException e) {
-            throw errorFrom(e);
-        }
-    }
-
-    private static Object invokeMethod(final Method method, final Object instance, final Object... args) {
-        try {
-            return method.invoke(instance, args);
-        } catch (final InvocationTargetException e) {
-            throw new InjectException(e.getMessage(), e.getCause());
-        } catch (final IllegalAccessException e) {
-            throw errorFrom(e);
-        }
-    }
-
-    private static <T> T newInstance(final Constructor<T> constructor, final Object[] args) {
-        try {
-            return constructor.newInstance(args);
-        } catch (final InvocationTargetException e) {
-            throw new InjectException(e.getMessage(), e.getCause());
-        } catch (final IllegalAccessException e) {
-            throw errorFrom(e);
-        } catch (final InstantiationException e) {
-            throw new InjectException(e.getMessage(), e);
-        }
-    }
-
-    private static IllegalAccessError errorFrom(final IllegalAccessException e) {
-        final IllegalAccessError error = new IllegalAccessError(e.getMessage());
-        error.initCause(e);
-        return error;
-    }
-
-    private static <T> T rethrow(final CheckedSupplier<T> supplier) {
-        try {
-            return supplier.get();
-        } catch (final Throwable e) {
-            rethrow(e);
-            throw new IllegalStateException("unreachable", e);
-        }
-    }
-
-    // type inference and erasure ensures that checked exceptions can be thrown here without being checked anymore
-    @SuppressWarnings("unchecked")
-    private static <T extends Throwable> void rethrow(final Throwable t) throws T {
-        throw (T) t;
-    }
-
-    @FunctionalInterface
-    private interface CheckedSupplier<T> {
-        T get() throws Throwable;
-    }
-
     private static class SingletonScope implements Scope {
         private final Map<Key<?>, Supplier<?>> singletonProviders = new ConcurrentHashMap<>();
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ReflectionAccessor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ReflectionAccessor.java
index 22508ce806..3755ccea58 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ReflectionAccessor.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ReflectionAccessor.java
@@ -18,8 +18,69 @@
 package org.apache.logging.log4j.plugins.di;
 
 import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 
 @FunctionalInterface
 public interface ReflectionAccessor {
     void makeAccessible(final AccessibleObject object);
+
+    default <M extends AccessibleObject & Member> void makeAccessible(final M member, final Object instance) {
+        final boolean isStatic = Modifier.isStatic(member.getModifiers());
+        if (!member.canAccess(isStatic ? null : instance)) {
+            makeAccessible(member);
+        }
+    }
+
+    default Object getFieldValue(final Field field, final Object instance) {
+        makeAccessible(field, instance);
+        try {
+            return field.get(instance);
+        } catch (final IllegalAccessException e) {
+            throw errorFrom(e);
+        }
+    }
+
+    default void setFieldValue(final Field field, final Object instance, final Object value) {
+        makeAccessible(field, instance);
+        try {
+            field.set(instance, value);
+        } catch (final IllegalAccessException e) {
+            throw errorFrom(e);
+        }
+    }
+
+    default Object invokeMethod(final Method method, final Object instance, final Object... args) {
+        makeAccessible(method, instance);
+        try {
+            return method.invoke(instance, args);
+        } catch (final InvocationTargetException e) {
+            throw new InjectException(e.getMessage(), e.getCause());
+        } catch (final IllegalAccessException e) {
+            throw errorFrom(e);
+        }
+    }
+
+    default <T> T newInstance(final Constructor<T> constructor, final Object[] args) {
+        makeAccessible(constructor, null);
+        try {
+            return constructor.newInstance(args);
+        } catch (final InvocationTargetException e) {
+            throw new InjectException(e.getMessage(), e.getCause());
+        } catch (final IllegalAccessException e) {
+            throw errorFrom(e);
+        } catch (final InstantiationException e) {
+            throw new InjectException(e.getMessage(), e);
+        }
+    }
+
+    private static IllegalAccessError errorFrom(final IllegalAccessException e) {
+        final IllegalAccessError error = new IllegalAccessError(e.getMessage());
+        error.initCause(e);
+        return error;
+    }
 }