You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@logging.apache.org by Matt Sicker <ma...@apache.org> on 2019/10/06 18:32:39 UTC
Re: [logging-log4j2] 04/04: Extract binder, injector, and name
provider APIs
Might be easier to review this via GitHub:
https://github.com/apache/logging-log4j2/commit/a1c3c7829e2fe3edea415a25121e31fb0afc0f1a
On Sun, 6 Oct 2019 at 13:29, <ma...@apache.org> wrote:
>
> 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 a1c3c7829e2fe3edea415a25121e31fb0afc0f1a
> Author: Matt Sicker <bo...@gmail.com>
> AuthorDate: Sun Oct 6 13:25:49 2019 -0500
>
> Extract binder, injector, and name provider APIs
>
> This refactors some of the strategies used for configuring and
> instantiating plugins. The previously named
> ConfigurationInjectionBuilder classes have been renamed to be
> ConfigurationInjector as they are no longer builder classes.
> This API has been simplified to find an appropriate injector
> given an AnnotatedElement which can automatically set both
> the AnnotatedElement and the Annotation. Conversion types have
> been weakened to use java.lang.reflect.Type rather than Class.
> This introduces a new plugin SPI named OptionBinder for
> abstracting the binding strategies previously used in the
> massive PluginBuilder class as desired by LOG4J2-860. This SPI
> is extended to include support for method-based configuration
> injection into plugin builder classes as specified in
> LOG4J2-2700. This introduces another plugin SPI for specifying
> the name of an annotated element rather than relying on Java
> reflection to calculate the name. This SPI also fixes
> LOG4J2-2693.
>
> Signed-off-by: Matt Sicker <bo...@gmail.com>
> ---
> .../org/apache/logging/log4j/util/Strings.java | 11 ++
> .../log4j/core/config/AbstractConfiguration.java | 3 +-
> .../log4j/core/config/plugins/PluginAttribute.java | 11 +-
> .../config/plugins/PluginBuilderAttribute.java | 10 +-
> .../core/config/plugins/PluginConfiguration.java | 15 +-
> .../log4j/core/config/plugins/PluginElement.java | 10 +-
> .../log4j/core/config/plugins/PluginNode.java | 10 +-
> .../log4j/core/config/plugins/PluginValue.java | 11 +-
> .../inject/PluginConfigurationInjector.java | 23 +++
> .../core/config/plugins/util/PluginBuilder.java | 167 +++++++-------------
> .../plugins/visitors/AbstractPluginVisitor.java | 45 ------
> .../plugins/visitors/PluginAttributeVisitor.java | 96 ++++++-----
> .../visitors/PluginBuilderAttributeVisitor.java | 31 ++--
> .../visitors/PluginConfigurationVisitor.java | 44 ------
> .../plugins/visitors/PluginElementVisitor.java | 99 ++++++------
> .../config/plugins/visitors/PluginNodeVisitor.java | 19 +--
> .../plugins/visitors/PluginValueVisitor.java | 18 +--
> .../core/config/plugins/visitors/package-info.java | 6 +-
> .../logging/log4j/plugins/PluginAliases.java | 8 +-
> .../logging/log4j/plugins/PluginAttribute.java | 39 +++--
> .../log4j/plugins/PluginBuilderAttribute.java | 13 +-
> .../log4j/plugins/PluginBuilderFactory.java | 4 +-
> .../logging/log4j/plugins/PluginElement.java | 26 ++-
> .../logging/log4j/plugins/PluginFactory.java | 9 +-
> .../apache/logging/log4j/plugins/PluginNode.java | 12 +-
> .../apache/logging/log4j/plugins/PluginValue.java | 27 +++-
> .../log4j/plugins/bind/AbstractOptionBinder.java | 62 ++++++++
> .../log4j/plugins/bind/FactoryMethodBinder.java | 58 +++++++
> .../log4j/plugins/bind/FieldOptionBinder.java | 36 +++++
> .../log4j/plugins/bind/MethodOptionBinder.java | 26 +++
> .../logging/log4j/plugins/bind/OptionBinder.java | 7 +
> .../log4j/plugins/bind/OptionBindingException.java | 24 +++
> .../AbstractConfigurationInjectionBuilder.java | 175 ---------------------
> .../inject/AbstractConfigurationInjector.java | 118 ++++++++++++++
> .../inject/ConfigurationInjectionBuilder.java | 105 -------------
> .../plugins/inject/ConfigurationInjector.java | 48 ++++++
> .../log4j/plugins/inject/InjectionStrategy.java | 42 -----
> .../log4j/plugins/inject/InjectorStrategy.java | 16 ++
> .../plugins/inject/PluginAttributeBuilder.java | 81 ----------
> .../plugins/inject/PluginAttributeInjector.java | 72 +++++++++
> .../inject/PluginBuilderAttributeBuilder.java | 49 ------
> .../inject/PluginBuilderAttributeInjector.java | 19 +++
> .../log4j/plugins/inject/PluginElementBuilder.java | 110 -------------
> .../plugins/inject/PluginElementInjector.java | 88 +++++++++++
> .../log4j/plugins/inject/PluginNodeBuilder.java | 39 -----
> .../log4j/plugins/inject/PluginNodeInjector.java | 17 ++
> .../log4j/plugins/inject/PluginValueBuilder.java | 52 ------
> .../log4j/plugins/inject/PluginValueInjector.java | 27 ++++
> .../logging/log4j/plugins/inject/package-info.java | 4 +-
> .../plugins/name/AnnotatedElementNameProvider.java | 69 ++++++++
> .../logging/log4j/plugins/name/NameProvider.java | 15 ++
> .../plugins/name/PluginAttributeNameProvider.java | 13 ++
> .../name/PluginBuilderAttributeNameProvider.java | 13 ++
> .../plugins/name/PluginElementNameProvider.java | 13 ++
> .../plugins/name/PluginValueNameProvider.java | 13 ++
> .../apache/logging/log4j/plugins/package-info.java | 3 +
> .../plugins/validation/constraints/Required.java | 2 +-
> .../plugins/validation/constraints/ValidHost.java | 2 +-
> .../plugins/validation/constraints/ValidPort.java | 2 +-
> src/changes/changes.xml | 9 ++
> 60 files changed, 1142 insertions(+), 1054 deletions(-)
>
> diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
> index e285502..f236b61 100644
> --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
> +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
> @@ -19,6 +19,7 @@ package org.apache.logging.log4j.util;
> import java.util.Iterator;
> import java.util.Locale;
> import java.util.Objects;
> +import java.util.Optional;
>
> /**
> * <em>Consider this class private.</em>
> @@ -214,6 +215,16 @@ public final class Strings {
> }
>
> /**
> + * Removes control characters from both ends of this String returning {@code Optional.empty()} if the String is
> + * empty ("") after the trim or if it is {@code null}.
> + *
> + * @see #trimToNull(String)
> + */
> + public static Optional<String> trimToOptional(final String str) {
> + return Optional.ofNullable(str).map(String::trim).filter(s -> !s.isEmpty());
> + }
> +
> + /**
> * <p>Joins the elements of the provided {@code Iterable} into
> * a single String containing the provided elements.</p>
> *
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
> index 371ceb9..47794fa 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
> @@ -63,7 +63,6 @@ import org.apache.logging.log4j.core.script.ScriptRef;
> import org.apache.logging.log4j.core.util.Constants;
> import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
> import org.apache.logging.log4j.core.util.Loader;
> -import org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder;
> import org.apache.logging.log4j.util.NameUtil;
> import org.apache.logging.log4j.core.util.Source;
> import org.apache.logging.log4j.core.time.NanoClock;
> @@ -971,7 +970,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
> * @param event the LogEvent that spurred the creation of this plugin
> * @return the created plugin object or {@code null} if there was an error setting it up.
> * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
> - * @see ConfigurationInjectionBuilder
> + * @see org.apache.logging.log4j.plugins.inject.ConfigurationInjector
> * @see org.apache.logging.log4j.plugins.convert.TypeConverter
> */
> private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
> index a5595cf..e75de09 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
> @@ -16,11 +16,15 @@
> */
> package org.apache.logging.log4j.core.config.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> import org.apache.logging.log4j.util.Strings;
>
> -import java.lang.annotation.*;
> +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;
>
> /**
> * Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be
> @@ -33,7 +37,7 @@ import java.lang.annotation.*;
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginAttributeVisitor.class)
> +@InjectorStrategy(PluginAttributeVisitor.class)
> public @interface PluginAttribute {
>
> /**
> @@ -86,7 +90,6 @@ public @interface PluginAttribute {
> */
> String defaultString() default Strings.EMPTY;
>
> - // TODO: could we allow a blank value and infer the attribute name through reflection?
> /**
> * Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
> */
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
> index 584d5f8..17ad890 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
> @@ -17,11 +17,15 @@
>
> package org.apache.logging.log4j.core.config.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> import org.apache.logging.log4j.util.Strings;
>
> -import java.lang.annotation.*;
> +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;
>
> /**
> * Marks a field as a Plugin Attribute.
> @@ -30,7 +34,7 @@ import java.lang.annotation.*;
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginBuilderAttributeVisitor.class)
> +@InjectorStrategy(PluginBuilderAttributeVisitor.class)
> public @interface PluginBuilderAttribute {
>
> /**
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
> index e941388..dee6f67 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
> @@ -16,23 +16,24 @@
> */
> package org.apache.logging.log4j.core.config.plugins;
>
> +import org.apache.logging.log4j.core.config.plugins.inject.PluginConfigurationInjector;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +
> 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;
>
> -import org.apache.logging.log4j.core.config.plugins.visitors.PluginConfigurationVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -
> /**
> - * Identifies a parameter or field as a Configuration.
> - * @see org.apache.logging.log4j.core.config.Configuration
> + * Identifies the current {@link org.apache.logging.log4j.core.config.Configuration}. This can be injected as a
> + * parameter to a static {@linkplain org.apache.logging.log4j.plugins.PluginFactory factory method}, or as a field
> + * or single-parameter method in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginConfigurationVisitor.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginConfigurationInjector.class)
> public @interface PluginConfiguration {
> // empty
> }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
> index 81c5a47..5c4f41e 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
> @@ -17,9 +17,13 @@
> package org.apache.logging.log4j.core.config.plugins;
>
> import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>
> -import java.lang.annotation.*;
> +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;
>
> /**
> * Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files.
> @@ -28,7 +32,7 @@ import java.lang.annotation.*;
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginElementVisitor.class)
> +@InjectorStrategy(PluginElementVisitor.class)
> public @interface PluginElement {
>
> /**
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
> index 3411a12..14a136c 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
> @@ -17,9 +17,13 @@
> package org.apache.logging.log4j.core.config.plugins;
>
> import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>
> -import java.lang.annotation.*;
> +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;
>
> /**
> * Identifies a Plugin configuration Node.
> @@ -28,7 +32,7 @@ import java.lang.annotation.*;
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginNodeVisitor.class)
> +@InjectorStrategy(PluginNodeVisitor.class)
> public @interface PluginNode {
> // empty
> }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
> index 0a93acb..9389aa9 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
> @@ -17,10 +17,13 @@
> package org.apache.logging.log4j.core.config.plugins;
>
> import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor;
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
>
> -
> -import java.lang.annotation.*;
> +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;
>
> /**
> * Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be
> @@ -32,7 +35,7 @@ import java.lang.annotation.*;
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginValueVisitor.class)
> +@InjectorStrategy(PluginValueVisitor.class)
> public @interface PluginValue {
>
> String value();
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java
> new file mode 100644
> index 0000000..6148f42
> --- /dev/null
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java
> @@ -0,0 +1,23 @@
> +package org.apache.logging.log4j.core.config.plugins.inject;
> +
> +import org.apache.logging.log4j.core.config.Configuration;
> +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
> +import org.apache.logging.log4j.core.util.TypeUtil;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
> +
> +public class PluginConfigurationInjector extends AbstractConfigurationInjector<PluginConfiguration, Configuration> {
> + @Override
> + public Object inject(final Object target) {
> + if (TypeUtil.isAssignable(conversionType, configuration.getClass())) {
> + debugLog.append("Configuration");
> + if (configuration.getName() != null) {
> + debugLog.append('(').append(configuration.getName()).append(')');
> + }
> + return optionBinder.bindObject(target, configuration);
> + } else {
> + LOGGER.warn("Element with type {} annotated with @PluginConfiguration is not compatible with type {}.",
> + conversionType, configuration.getClass());
> + return target;
> + }
> + }
> +}
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
> index b59c538..7624de6 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
> @@ -26,12 +26,13 @@ import org.apache.logging.log4j.plugins.Node;
> import org.apache.logging.log4j.plugins.PluginAliases;
> import org.apache.logging.log4j.plugins.PluginBuilderFactory;
> import org.apache.logging.log4j.plugins.PluginFactory;
> -import org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder;
> +import org.apache.logging.log4j.plugins.bind.FactoryMethodBinder;
> +import org.apache.logging.log4j.plugins.bind.FieldOptionBinder;
> +import org.apache.logging.log4j.plugins.bind.MethodOptionBinder;
> +import org.apache.logging.log4j.plugins.inject.ConfigurationInjector;
> import org.apache.logging.log4j.plugins.util.Builder;
> import org.apache.logging.log4j.plugins.util.PluginType;
> import org.apache.logging.log4j.plugins.util.TypeUtil;
> -import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
> -import org.apache.logging.log4j.plugins.validation.ConstraintValidators;
> import org.apache.logging.log4j.status.StatusLogger;
> import org.apache.logging.log4j.util.ReflectionUtil;
> import org.apache.logging.log4j.util.StringBuilders;
> @@ -42,11 +43,9 @@ import java.lang.reflect.Field;
> import java.lang.reflect.InvocationTargetException;
> import java.lang.reflect.Method;
> import java.lang.reflect.Modifier;
> -import java.util.Collection;
> import java.util.List;
> import java.util.Map;
> import java.util.Objects;
> -import java.util.Optional;
> import java.util.concurrent.ConcurrentHashMap;
> import java.util.concurrent.ConcurrentMap;
> import java.util.function.Function;
> @@ -126,26 +125,21 @@ public class PluginBuilder implements Builder<Object> {
> pluginType.getPluginClass().getName());
> final Builder<?> builder = createBuilder(this.clazz);
> if (builder != null) {
> - injectFields(builder);
> - return builder.build();
> + return injectBuilder(builder);
> }
> } catch (final ConfigurationException e) { // LOG4J2-1908
> LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e);
> return null; // no point in trying the factory method
> } catch (final Exception e) {
> - LOGGER.error("Could not create plugin of type {} for element {}: {}",
> - this.clazz, node.getName(),
> - (e instanceof InvocationTargetException ? e.getCause() : e).toString(), e);
> + LOGGER.error("Could not create plugin of type {} for element {}: {}", clazz, node.getName(),
> + e.toString(), e);
> }
> // or fall back to factory method if no builder class is available
> try {
> - final Method factory = findFactoryMethod(this.clazz);
> - final Object[] params = generateParameters(factory);
> - return factory.invoke(null, params);
> - } catch (final Exception e) {
> - LOGGER.error("Unable to invoke factory method in {} for element {}: {}",
> - this.clazz, this.node.getName(),
> - (e instanceof InvocationTargetException ? e.getCause() : e).toString(), e);
> + return injectFactoryMethod(findFactoryMethod(this.clazz));
> + } catch (final Throwable e) {
> + LOGGER.error("Could not create plugin of type {} for element {}: {}", clazz, node.getName(),
> + e.toString(), e);
> return null;
> }
> }
> @@ -158,7 +152,7 @@ public class PluginBuilder implements Builder<Object> {
> private static Builder<?> createBuilder(final Class<?> clazz)
> throws InvocationTargetException, IllegalAccessException {
> for (final Method method : clazz.getDeclaredMethods()) {
> - if (method.isAnnotationPresent(PluginBuilderFactory.class) &&
> + if ((method.isAnnotationPresent(PluginFactory.class) || method.isAnnotationPresent(PluginBuilderFactory.class)) &&
> Modifier.isStatic(method.getModifiers()) &&
> TypeUtil.isAssignable(Builder.class, method.getReturnType())) {
> ReflectionUtil.makeAccessible(method);
> @@ -173,62 +167,47 @@ public class PluginBuilder implements Builder<Object> {
> return null;
> }
>
> - private void injectFields(final Builder<?> builder) throws IllegalAccessException {
> + private Object injectBuilder(final Builder<?> builder) {
> final Object target = builder instanceof BuilderWrapper ? ((BuilderWrapper) builder).getBuilder() : builder;
> final List<Field> fields = TypeUtil.getAllDeclaredFields(target.getClass());
> - AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true);
> + AccessibleObject.setAccessible(fields.toArray(new Field[0]), true);
> final StringBuilder log = new StringBuilder();
> - boolean invalid = false;
> - StringBuilder reason = new StringBuilder();
> + // TODO: collect OptionBindingExceptions into a composite error message (ConfigurationException?)
> for (final Field field : fields) {
> - log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
> - final Annotation[] annotations = field.getDeclaredAnnotations();
> - final String[] aliases = extractPluginAliases(annotations);
> - for (Annotation a : annotations) {
> - if (a instanceof PluginAliases ||
> - a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
> - continue; // already processed
> - }
> - final Object value = ConfigurationInjectionBuilder.findBuilderForInjectionStrategy(a.annotationType())
> - .flatMap(b -> Optional.ofNullable(b
> - .withAliases(aliases)
> - .withAnnotation(a)
> - .withConfiguration(configuration)
> - .withConfigurationNode(node)
> - .withConversionType(field.getType())
> - .withDebugLog(log)
> - .withMember(field)
> - .withStringSubstitutionStrategy(substitutor)
> - .build()))
> - .orElse(null);
> - if (value != null) {
> - field.set(target, value);
> - }
> - }
> - final Collection<ConstraintValidator<?>> validators =
> - ConstraintValidators.findValidators(annotations);
> - final Object value = field.get(target);
> - for (final ConstraintValidator<?> validator : validators) {
> - if (!validator.isValid(field.getName(), value)) {
> - invalid = true;
> - if (reason.length() > 0) {
> - reason.append(", ");
> - } else {
> - reason.append("Arguments given for element ").append(node.getName())
> - .append(" are invalid: ");
> - }
> - reason.append("field '").append(field.getName()).append("' has invalid value '")
> - .append(value).append("'");
> + ConfigurationInjector.forAnnotatedElement(field).ifPresent(injector -> {
> + log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
> + injector.withAliases(extractPluginAliases(field.getAnnotations()))
> + .withConversionType(field.getGenericType())
> + .withOptionBinder(new FieldOptionBinder(field))
> + .withDebugLog(log)
> + .withStringSubstitutionStrategy(substitutor)
> + .withConfiguration(configuration)
> + .withNode(node)
> + .inject(target);
> + });
> + }
> + // TODO: tests
> + for (final Method method : target.getClass().getMethods()) {
> + ConfigurationInjector.forAnnotatedElement(method).ifPresent(injector -> {
> + if (method.getParameterCount() != 1) {
> + throw new IllegalArgumentException("Cannot inject to a plugin builder method with parameter count other than 1");
> }
> - }
> + log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
> + injector.withAliases(extractPluginAliases(method.getAnnotations()))
> + .withConversionType(method.getGenericParameterTypes()[0])
> + .withOptionBinder(new MethodOptionBinder(method))
> + .withDebugLog(log)
> + .withStringSubstitutionStrategy(substitutor)
> + .withConfiguration(configuration)
> + .withNode(node)
> + .inject(target);
> + });
> }
> log.append(log.length() == 0 ? builder.getClass().getSimpleName() + "()" : ")");
> LOGGER.debug(log.toString());
> - if (invalid) {
> - throw new ConfigurationException(reason.toString());
> - }
> checkForRemainingAttributes();
> verifyNodeChildrenUsed();
> + return builder.build();
> }
>
> /**
> @@ -255,60 +234,30 @@ public class PluginBuilder implements Builder<Object> {
> throw new IllegalStateException("No factory method found for class " + clazz.getName());
> }
>
> - private Object[] generateParameters(final Method factory) {
> + private Object injectFactoryMethod(final Method factory) throws Throwable {
> final StringBuilder log = new StringBuilder();
> - final Class<?>[] types = factory.getParameterTypes();
> - final Annotation[][] annotations = factory.getParameterAnnotations();
> - final Object[] args = new Object[annotations.length];
> - boolean invalid = false;
> - for (int i = 0; i < annotations.length; i++) {
> + final FactoryMethodBinder binder = new FactoryMethodBinder(factory);
> + binder.forEachParameter((parameter, optionBinder) -> {
> log.append(log.length() == 0 ? factory.getName() + "(" : ", ");
> - final String[] aliases = extractPluginAliases(annotations[i]);
> - final Class<?> conversionType = types[i];
> - for (final Annotation a : annotations[i]) {
> - if (a instanceof PluginAliases ||
> - a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
> - continue; // already processed
> - }
> - final Object value = ConfigurationInjectionBuilder.findBuilderForInjectionStrategy(a.annotationType())
> - .flatMap(b -> Optional.ofNullable(b
> - .withAliases(aliases)
> - .withAnnotation(a)
> - .withConfiguration(configuration)
> - .withConfigurationNode(node)
> - .withConversionType(conversionType)
> - .withDebugLog(log)
> - .withMember(factory)
> - .withStringSubstitutionStrategy(substitutor)
> - .build()))
> - .orElse(null);
> - // don't overwrite existing values if the builder gives us no value to inject
> - if (value != null) {
> - args[i] = value;
> - }
> - }
> - final Collection<ConstraintValidator<?>> validators =
> - ConstraintValidators.findValidators(annotations[i]);
> - final Object value = args[i];
> - final String argName = "arg[" + i + "](" + simpleName(value) + ")";
> - for (final ConstraintValidator<?> validator : validators) {
> - if (!validator.isValid(argName, value)) {
> - invalid = true;
> - }
> - }
> - }
> + ConfigurationInjector.forAnnotatedElement(parameter).ifPresent(injector -> injector
> + .withAliases(extractPluginAliases(parameter.getAnnotations()))
> + .withConversionType(parameter.getParameterizedType())
> + .withOptionBinder(optionBinder)
> + .withDebugLog(log)
> + .withStringSubstitutionStrategy(substitutor)
> + .withConfiguration(configuration)
> + .withNode(node)
> + .inject(binder));
> + });
> log.append(log.length() == 0 ? factory.getName() + "()" : ")");
> checkForRemainingAttributes();
> verifyNodeChildrenUsed();
> LOGGER.debug(log.toString());
> - if (invalid) {
> - throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
> - }
> - return args;
> + return binder.invoke();
> }
>
> private static String[] extractPluginAliases(final Annotation... parmTypes) {
> - String[] aliases = null;
> + String[] aliases = {};
> for (final Annotation a : parmTypes) {
> if (a instanceof PluginAliases) {
> aliases = ((PluginAliases) a).value();
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java
> deleted file mode 100644
> index b812459..0000000
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java
> +++ /dev/null
> @@ -1,45 +0,0 @@
> -package org.apache.logging.log4j.core.config.plugins.visitors;
> -
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjectionBuilder;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.reflect.Member;
> -import java.util.function.Function;
> -
> -abstract class AbstractPluginVisitor<Ann extends Annotation, Cfg> extends AbstractConfigurationInjectionBuilder<Ann, Cfg> {
> -
> - AbstractPluginVisitor(final Class<Ann> clazz) {
> - super(clazz);
> - }
> -
> - public AbstractPluginVisitor<Ann, Cfg> setAnnotation(final Annotation annotation) {
> - if (clazz.isInstance(annotation)) {
> - withAnnotation(clazz.cast(annotation));
> - }
> - return this;
> - }
> -
> - public AbstractPluginVisitor<Ann, Cfg> setAliases(final String... aliases) {
> - withAliases(aliases);
> - return this;
> - }
> -
> - public AbstractPluginVisitor<Ann, Cfg> setConversionType(final Class<?> conversionType) {
> - withConversionType(conversionType);
> - return this;
> - }
> -
> - public AbstractPluginVisitor<Ann, Cfg> setMember(final Member member) {
> - withMember(member);
> - return this;
> - }
> -
> - public Object visit(final Cfg configuration, final Node node, final Function<String, String> substitutor, final StringBuilder log) {
> - return this.withConfiguration(configuration)
> - .withConfigurationNode(node)
> - .withStringSubstitutionStrategy(substitutor)
> - .withDebugLog(log)
> - .build();
> - }
> -}
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
> index f8e30c2..dcc5220 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
> @@ -16,62 +16,78 @@
> */
> package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
> import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
> import org.apache.logging.log4j.util.NameUtil;
> import org.apache.logging.log4j.util.StringBuilders;
> +import org.apache.logging.log4j.util.Strings;
>
> +import java.lang.reflect.Type;
> +import java.util.Collections;
> import java.util.Map;
> +import java.util.Optional;
> +import java.util.concurrent.ConcurrentHashMap;
> import java.util.function.Function;
>
> /**
> * @deprecated Provided to support legacy plugins.
> */
> -public class PluginAttributeVisitor extends AbstractPluginVisitor<PluginAttribute, Object> {
> - public PluginAttributeVisitor() {
> - super(PluginAttribute.class);
> +// copy of PluginAttributeInjector
> +public class PluginAttributeVisitor extends AbstractConfigurationInjector<PluginAttribute, Configuration> {
> +
> + private static final Map<Type, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
> +
> + static {
> + final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
> + extractors.put(int.class, PluginAttribute::defaultInt);
> + extractors.put(Integer.class, PluginAttribute::defaultInt);
> + extractors.put(long.class, PluginAttribute::defaultLong);
> + extractors.put(Long.class, PluginAttribute::defaultLong);
> + extractors.put(boolean.class, PluginAttribute::defaultBoolean);
> + extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
> + extractors.put(float.class, PluginAttribute::defaultFloat);
> + extractors.put(Float.class, PluginAttribute::defaultFloat);
> + extractors.put(double.class, PluginAttribute::defaultDouble);
> + extractors.put(Double.class, PluginAttribute::defaultDouble);
> + extractors.put(byte.class, PluginAttribute::defaultByte);
> + extractors.put(Byte.class, PluginAttribute::defaultByte);
> + extractors.put(char.class, PluginAttribute::defaultChar);
> + extractors.put(Character.class, PluginAttribute::defaultChar);
> + extractors.put(short.class, PluginAttribute::defaultShort);
> + extractors.put(Short.class, PluginAttribute::defaultShort);
> + extractors.put(Class.class, PluginAttribute::defaultClass);
> + DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
> }
>
> @Override
> - public Object build() {
> - final String name = this.annotation.value();
> - final Map<String, String> attributes = node.getAttributes();
> - final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> - final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> - final Object defaultValue = findDefaultValue(stringSubstitutionStrategy);
> - final Object value = convert(replacedValue, defaultValue);
> - final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> - StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> - return value;
> + public Object inject(final Object target) {
> + final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
> + if (value.isPresent()) {
> + final String s = value.get();
> + return optionBinder.bindString(target, s);
> + } else {
> + return injectDefaultValue(target);
> + }
> }
>
> - private Object findDefaultValue(Function<String, String> substitutor) {
> - if (this.conversionType == int.class || this.conversionType == Integer.class) {
> - return this.annotation.defaultInt();
> - }
> - if (this.conversionType == long.class || this.conversionType == Long.class) {
> - return this.annotation.defaultLong();
> - }
> - if (this.conversionType == boolean.class || this.conversionType == Boolean.class) {
> - return this.annotation.defaultBoolean();
> - }
> - if (this.conversionType == float.class || this.conversionType == Float.class) {
> - return this.annotation.defaultFloat();
> + private Object injectDefaultValue(final Object target) {
> + final Function<PluginAttribute, Object> extractor = DEFAULT_VALUE_EXTRACTORS.get(conversionType);
> + if (extractor != null) {
> + final Object value = extractor.apply(annotation);
> + debugLog(value);
> + return optionBinder.bindObject(target, value);
> }
> - if (this.conversionType == double.class || this.conversionType == Double.class) {
> - return this.annotation.defaultDouble();
> + final String value = stringSubstitutionStrategy.apply(annotation.defaultString());
> + if (Strings.isNotBlank(value)) {
> + debugLog(value);
> + return optionBinder.bindString(target, value);
> }
> - if (this.conversionType == byte.class || this.conversionType == Byte.class) {
> - return this.annotation.defaultByte();
> - }
> - if (this.conversionType == char.class || this.conversionType == Character.class) {
> - return this.annotation.defaultChar();
> - }
> - if (this.conversionType == short.class || this.conversionType == Short.class) {
> - return this.annotation.defaultShort();
> - }
> - if (this.conversionType == Class.class) {
> - return this.annotation.defaultClass();
> - }
> - return substitutor.apply(this.annotation.defaultString());
> + return target;
> + }
> +
> + private void debugLog(final Object value) {
> + final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> + StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> }
> }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
> index 6c7f6b0..d834b5b 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
> @@ -17,31 +17,26 @@
>
> package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
> import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
> import org.apache.logging.log4j.util.NameUtil;
> import org.apache.logging.log4j.util.StringBuilders;
>
> -import java.util.Map;
> -
> /**
> * @deprecated Provided for support for PluginBuilderAttribute.
> */
> -public class PluginBuilderAttributeVisitor extends AbstractPluginVisitor<PluginBuilderAttribute, Object> {
> -
> - public PluginBuilderAttributeVisitor() {
> - super(PluginBuilderAttribute.class);
> - }
> -
> +// copy of PluginBuilderAttributeInjector
> +public class PluginBuilderAttributeVisitor extends AbstractConfigurationInjector<PluginBuilderAttribute, Configuration> {
> @Override
> - public Object build() {
> - final String overridden = this.annotation.value();
> - final String name = overridden.isEmpty() ? this.member.getName() : overridden;
> - final Map<String, String> attributes = node.getAttributes();
> - final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> - final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> - final Object value = convert(replacedValue, null);
> - final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> - StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> - return value;
> + public Object inject(final Object target) {
> + return findAndRemoveNodeAttribute()
> + .map(stringSubstitutionStrategy)
> + .map(value -> {
> + String debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> + StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> + return optionBinder.bindString(target, value);
> + })
> + .orElseGet(() -> optionBinder.bindObject(target, null));
> }
> }
> \ No newline at end of file
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java
> deleted file mode 100644
> index 20976c7..0000000
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java
> +++ /dev/null
> @@ -1,44 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.core.config.plugins.visitors;
> -
> -import org.apache.logging.log4j.core.config.Configuration;
> -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
> -
> -/**
> - * PluginVisitor implementation for {@link PluginConfiguration}.
> - */
> -public class PluginConfigurationVisitor extends AbstractPluginVisitor<PluginConfiguration, Configuration> {
> - public PluginConfigurationVisitor() {
> - super(PluginConfiguration.class);
> - }
> -
> - @Override
> - public Object build() {
> - if (this.conversionType.isInstance(configuration)) {
> - debugLog.append("Configuration");
> - if (configuration.getName() != null) {
> - debugLog.append('(').append(configuration.getName()).append(')');
> - }
> - return configuration;
> - }
> - LOGGER.warn("Variable annotated with @PluginConfiguration is not compatible with type {}.",
> - configuration.getClass());
> - return null;
> - }
> -}
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
> index de80831..e216293 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
> @@ -17,37 +17,38 @@
>
> package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
> import org.apache.logging.log4j.core.config.plugins.PluginElement;
> import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
> import org.apache.logging.log4j.plugins.util.PluginType;
> +import org.apache.logging.log4j.plugins.util.TypeUtil;
>
> import java.lang.reflect.Array;
> +import java.lang.reflect.Type;
> import java.util.ArrayList;
> import java.util.Arrays;
> import java.util.Collection;
> import java.util.List;
> +import java.util.Optional;
>
> /**
> * @deprecated Provided to support legacy plugins.
> */
> -public class PluginElementVisitor extends AbstractPluginVisitor<PluginElement, Object> {
> - public PluginElementVisitor() {
> - super(PluginElement.class);
> - }
> -
> +// copy of PluginElementInjector
> +public class PluginElementVisitor extends AbstractConfigurationInjector<PluginElement, Configuration> {
> @Override
> - public Object build() {
> - final String name = this.annotation.value();
> - if (this.conversionType.isArray()) {
> - setConversionType(this.conversionType.getComponentType());
> + public Object inject(final Object target) {
> + final Optional<Class<?>> componentType = getComponentType(conversionType);
> + if (componentType.isPresent()) {
> + final Class<?> compType = componentType.get();
> final List<Object> values = new ArrayList<>();
> final Collection<Node> used = new ArrayList<>();
> debugLog.append("={");
> boolean first = true;
> for (final Node child : node.getChildren()) {
> - final PluginType<?> childType = child.getType();
> - if (name.equalsIgnoreCase(childType.getElementName()) ||
> - this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> + final PluginType<?> type = child.getType();
> + if (name.equalsIgnoreCase(type.getElementName()) || compType.isAssignableFrom(type.getPluginClass())) {
> if (!first) {
> debugLog.append(", ");
> }
> @@ -55,56 +56,56 @@ public class PluginElementVisitor extends AbstractPluginVisitor<PluginElement, O
> used.add(child);
> final Object childObject = child.getObject();
> if (childObject == null) {
> - LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName());
> - continue;
> - }
> - if (childObject.getClass().isArray()) {
> - debugLog.append(Arrays.toString((Object[]) childObject)).append('}');
> + LOGGER.warn("Skipping null object returned for element {} in node {}", child.getName(), node.getName());
> + } else if (childObject.getClass().isArray()) {
> + Object[] children = (Object[]) childObject;
> + debugLog.append(Arrays.toString(children)).append('}');
> node.getChildren().removeAll(used);
> - return childObject;
> + return optionBinder.bindObject(target, children);
> + } else {
> + debugLog.append(child.toString());
> + values.add(childObject);
> }
> - debugLog.append(child.toString());
> - values.add(childObject);
> }
> }
> debugLog.append('}');
> - // note that we need to return an empty array instead of null if the types are correct
> - if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) {
> - LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.",
> - name, values.get(0).getClass(), this.conversionType);
> + if (!values.isEmpty() && !TypeUtil.isAssignable(compType, values.get(0).getClass())) {
> + LOGGER.error("Cannot assign element {} a list of {} as it is incompatible with {}", name, values.get(0).getClass(), compType);
> return null;
> }
> node.getChildren().removeAll(used);
> - // we need to use reflection here because values.toArray() will cause type errors at runtime
> - final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size());
> - for (int i = 0; i < array.length; i++) {
> - array[i] = values.get(i);
> + // using List::toArray here would cause type mismatch later on
> + final Object[] vals = (Object[]) Array.newInstance(compType, values.size());
> + for (int i = 0; i < vals.length; i++) {
> + vals[i] = values.get(i);
> + }
> + return optionBinder.bindObject(target, vals);
> + } else {
> + final Optional<Node> matchingChild = node.getChildren().stream().filter(this::isRequestedNode).findAny();
> + if (matchingChild.isPresent()) {
> + final Node child = matchingChild.get();
> + debugLog.append(child.getName()).append('(').append(child.toString()).append(')');
> + node.getChildren().remove(child);
> + return optionBinder.bindObject(target, child.getObject());
> + } else {
> + debugLog.append(name).append("=null");
> + return optionBinder.bindObject(target, null);
> }
> - return array;
> - }
> - final Node namedNode = findNamedNode(name, node.getChildren());
> - if (namedNode == null) {
> - debugLog.append(name).append("=null");
> - return null;
> }
> - debugLog.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')');
> - node.getChildren().remove(namedNode);
> - return namedNode.getObject();
> }
>
> - private Node findNamedNode(final String name, final Iterable<Node> children) {
> - for (final Node child : children) {
> - final PluginType<?> childType = child.getType();
> - if (childType == null) {
> - //System.out.println();
> - }
> - if (name.equalsIgnoreCase(childType.getElementName()) ||
> - this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> - // FIXME: check child.getObject() for null?
> - // doing so would be more consistent with the array version
> - return child;
> + private boolean isRequestedNode(final Node child) {
> + final PluginType<?> type = child.getType();
> + return name.equalsIgnoreCase(type.getElementName()) || TypeUtil.isAssignable(conversionType, type.getPluginClass());
> + }
> +
> + private static Optional<Class<?>> getComponentType(final Type type) {
> + if (type instanceof Class<?>) {
> + final Class<?> clazz = (Class<?>) type;
> + if (clazz.isArray()) {
> + return Optional.of(clazz.getComponentType());
> }
> }
> - return null;
> + return Optional.empty();
> }
> }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
> index 57c0d89..bf9a364 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
> @@ -17,23 +17,18 @@
>
> package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
> import org.apache.logging.log4j.core.config.plugins.PluginNode;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
>
> /**
> * @deprecated Provided to support legacy plugins.
> */
> -public class PluginNodeVisitor extends AbstractPluginVisitor<PluginNode, Object> {
> - public PluginNodeVisitor() {
> - super(PluginNode.class);
> - }
> -
> +// copy of PluginNodeInjector
> +public class PluginNodeVisitor extends AbstractConfigurationInjector<PluginNode, Configuration> {
> @Override
> - public Object build() {
> - if (this.conversionType.isInstance(node)) {
> - debugLog.append("Node=").append(node.getName());
> - return node;
> - }
> - LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass());
> - return null;
> + public Object inject(final Object target) {
> + debugLog.append("Node=").append(node.getName());
> + return optionBinder.bindObject(target, node);
> }
> }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
> index 9600514..95f86f4 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
> @@ -17,23 +17,21 @@
>
> package org.apache.logging.log4j.core.config.plugins.visitors;
>
> +import org.apache.logging.log4j.core.config.Configuration;
> import org.apache.logging.log4j.core.config.plugins.PluginValue;
> +import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
> import org.apache.logging.log4j.util.StringBuilders;
> import org.apache.logging.log4j.util.Strings;
>
> /**
> * @deprecated Provided to support legacy plugins.
> */
> -public class PluginValueVisitor extends AbstractPluginVisitor<PluginValue, Object> {
> - public PluginValueVisitor() {
> - super(PluginValue.class);
> - }
> -
> +// copy of PluginValueInjector
> +public class PluginValueVisitor extends AbstractConfigurationInjector<PluginValue, Configuration> {
> @Override
> - public Object build() {
> - final String name = this.annotation.value();
> + public Object inject(final Object target) {
> final String elementValue = node.getValue();
> - final String attributeValue = node.getAttributes().get("value");
> + final String attributeValue = node.getAttributes().get(name);
> String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
> if (Strings.isNotEmpty(elementValue)) {
> if (Strings.isNotEmpty(attributeValue)) {
> @@ -43,10 +41,10 @@ public class PluginValueVisitor extends AbstractPluginVisitor<PluginValue, Objec
> }
> rawValue = elementValue;
> } else {
> - rawValue = removeAttributeValue(node.getAttributes(), "value");
> + rawValue = findAndRemoveNodeAttribute().orElse(null);
> }
> final String value = stringSubstitutionStrategy.apply(rawValue);
> StringBuilders.appendKeyDqValue(debugLog, name, value);
> - return value;
> + return optionBinder.bindString(target, value);
> }
> }
> diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
> index 276ec0c..9b747e3 100644
> --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
> +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
> @@ -16,9 +16,7 @@
> */
>
> /**
> - * Visitor classes for extracting values from a Configuration or Node corresponding to a plugin annotation.
> - * Visitor implementations must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder},
> - * and the corresponding annotation must be annotated with
> - * {@link org.apache.logging.log4j.plugins.inject.InjectionStrategy}.
> + * Legacy injector strategies for legacy annotations. Plugins should use the updated annotations provided in
> + * {@code org.apache.logging.log4j.plugins}.
> */
> package org.apache.logging.log4j.core.config.plugins.visitors;
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
> index 4dce103..bb3962a 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
> @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
>
> /**
> - * Identifies a list of aliases for a Plugin, PluginAttribute, or PluginBuilderAttribute.
> + * Identifies a list of aliases for an annotated plugin element. This is supported by plugin classes and other element
> + * types supported by the annotations in this package.
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD})
> +@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
> public @interface PluginAliases {
>
> + /**
> + * Aliases the annotated element can also be referred to. These aliases are case-insensitive.
> + */
> String[] value();
> }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
> index efc769a..30cccad 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
> @@ -16,8 +16,10 @@
> */
> package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginAttributeBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginAttributeInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginAttributeNameProvider;
> import org.apache.logging.log4j.util.Strings;
>
> import java.lang.annotation.Documented;
> @@ -27,16 +29,24 @@ import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
>
> /**
> - * Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be
> - * used based on the type this annotation is attached to. Thus, for primitive types, the default<i>Type</i>
> - * attribute will be used for some <i>Type</i>. However, for more complex types (including enums), the default
> - * string value is used instead and should correspond to the string that would correctly convert to the appropriate
> - * enum value using {@link Enum#valueOf(Class, String) Enum.valueOf}.
> + * Identifies a Plugin Attribute along with optional metadata. Plugin attributes can be injected as parameters to
> + * a static {@linkplain PluginFactory factory method}, or as fields and single-parameter methods in a plugin
> + * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
> + *
> + * <p>Default values may be specified via one of the <code>default<var>Type</var></code> attributes depending on the
> + * annotated type. Unlisted types that are supported by a corresponding
> + * {@link org.apache.logging.log4j.plugins.convert.TypeConverter} may use the {@link #defaultString()} attribute.
> + * When annotating a field, a default value can be specified by the field's initial value instead of using one of the
> + * annotation attributes.</p>
> + *
> + * <p>Plugin attributes with sensitive data such as passwords should specify {@link #sensitive()} to avoid having
> + * their values logged in debug logs.</p>
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginAttributeBuilder.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginAttributeInjector.class)
> +@NameProvider(PluginAttributeNameProvider.class)
> public @interface PluginAttribute {
>
> /**
> @@ -89,11 +99,18 @@ public @interface PluginAttribute {
> */
> String defaultString() default Strings.EMPTY;
>
> - // TODO: could we allow a blank value and infer the attribute name through reflection?
> /**
> * Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
> + * If blank, defaults to using reflection on the annotated element as such:
> + *
> + * <ul>
> + * <li>Field: uses the field name.</li>
> + * <li>Method: when named <code>set<var>XYZ</var></code> or <code>with<var>XYZ</var></code>, uses the rest
> + * (<var>XYZ</var>) of the method name. Otherwise, uses the name of the first parameter.</li>
> + * <li>Parameter: uses the parameter name.</li>
> + * </ul>
> */
> - String value();
> + String value() default Strings.EMPTY;
>
> /**
> * Indicates that this attribute is a sensitive one that shouldn't be logged directly. Such attributes will instead
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
> index 3f3d597..06cb8f0 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
> @@ -17,8 +17,10 @@
>
> package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginBuilderAttributeBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginBuilderAttributeInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginBuilderAttributeNameProvider;
> import org.apache.logging.log4j.util.Strings;
>
> import java.lang.annotation.Documented;
> @@ -29,12 +31,15 @@ import java.lang.annotation.Target;
>
> /**
> * Marks a field as a Plugin Attribute.
> + *
> + * @deprecated use {@link PluginAttribute}
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
> -@InjectionStrategy(PluginBuilderAttributeBuilder.class)
> -// TODO: this annotation can be combined with @PluginAttribute
> +@InjectorStrategy(PluginBuilderAttributeInjector.class)
> +@NameProvider(PluginBuilderAttributeNameProvider.class)
> +@Deprecated
> public @interface PluginBuilderAttribute {
>
> /**
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java
> index 4d15caa..a698fe1 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderFactory.java
> @@ -25,11 +25,13 @@ import java.lang.annotation.Target;
>
> /**
> * Marks a method as a factory for custom Plugin builders.
> + *
> + * @deprecated use {@link PluginFactory}
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target(ElementType.METHOD)
> -// TODO: this can be combined with @PluginFactory as differentiating them by method signature is obvious
> +@Deprecated
> public @interface PluginBuilderFactory {
> // empty
> }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
> index deb4641..edd5412 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
> @@ -16,8 +16,11 @@
> */
> package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginElementBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginElementInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginElementNameProvider;
> +import org.apache.logging.log4j.util.Strings;
>
> import java.lang.annotation.Documented;
> import java.lang.annotation.ElementType;
> @@ -26,17 +29,28 @@ import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
>
> /**
> + * Identifies a Plugin Element which allows for plugins to be configured and injected into another plugin.
> + * Plugin elements can be injected as parameters to a static {@linkplain PluginFactory factory method}, or as fields and
> + * single-parameter methods in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
> * Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files.
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginElementBuilder.class)
> -// TODO: this can have a default value to use reflection
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginElementInjector.class)
> +@NameProvider(PluginElementNameProvider.class)
> public @interface PluginElement {
>
> /**
> * Identifies the case-insensitive element name (or attribute name) this corresponds with in a configuration file.
> + * If blank, defaults to using reflection on the annotated element as such:
> + *
> + * <ul>
> + * <li>Field: uses the field name.</li>
> + * <li>Method: when named <code>set<var>XYZ</var></code> or <code>with<var>XYZ</var></code>, uses the rest
> + * (<var>XYZ</var>) of the method name. Otherwise, uses the name of the first parameter.</li>
> + * <li>Parameter: uses the parameter name.</li>
> + * </ul>
> */
> - String value();
> + String value() default Strings.EMPTY;
> }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
> index b071510..f1d89c6 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
> @@ -23,8 +23,13 @@ import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
>
> /**
> - * Identifies a Method as the factory to create the plugin. This annotation should only be used on a {@code static}
> - * method, and its parameters should be annotated with the appropriate Plugin annotations.
> + * Identifies a static method as a factory to create a plugin or a
> + * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class} for constructing a plugin.
> + * Factory methods should annotate their parameters with {@link PluginAttribute}, {@link PluginElement},
> + * {@link PluginValue}, or other plugin annotations annotated with
> + * {@link org.apache.logging.log4j.plugins.inject.InjectorStrategy}.
> + * If a factory method returns a builder class, this method should have no arguments; instead, the builder class should
> + * annotate its fields or single-parameter methods to inject plugin configuration data.
> * <p>
> * There can only be one factory method per class.
> * </p>
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
> index 2f5cef3..dcc5b0c 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
> @@ -16,8 +16,8 @@
> */
> package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginNodeBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginNodeInjector;
>
> import java.lang.annotation.Documented;
> import java.lang.annotation.ElementType;
> @@ -26,12 +26,14 @@ import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
>
> /**
> - * Identifies a Plugin configuration Node.
> + * Identifies the configuration {@link Node} currently being configured. This can be injected as a parameter to a static
> + * {@linkplain PluginFactory factory method}, or as a field or single-parameter method in a plugin
> + * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginNodeBuilder.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginNodeInjector.class)
> public @interface PluginNode {
> // empty
> }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
> index 84c4385..a08eaf0 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
> @@ -16,8 +16,10 @@
> */
> package org.apache.logging.log4j.plugins;
>
> -import org.apache.logging.log4j.plugins.inject.InjectionStrategy;
> -import org.apache.logging.log4j.plugins.inject.PluginValueBuilder;
> +import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
> +import org.apache.logging.log4j.plugins.inject.PluginValueInjector;
> +import org.apache.logging.log4j.plugins.name.NameProvider;
> +import org.apache.logging.log4j.plugins.name.PluginValueNameProvider;
>
> import java.lang.annotation.Documented;
> import java.lang.annotation.ElementType;
> @@ -26,16 +28,25 @@ import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
>
> /**
> - * Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be
> - * used as a placeholder value somewhere.
> + * Identifies a Plugin Value and its corresponding attribute alias for configuration formats that don't distinguish
> + * between values and attributes. A value is typically used differently from an attribute in that it is either the
> + * main configuration value required or it is the only value needed to create a plugin. A plugin value can be injected
> + * as a parameter to a static {@linkplain PluginFactory factory method}, or as a field or single-parameter method in a
> + * plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
> *
> - * @see org.apache.logging.log4j.core.config.PropertiesPlugin
> + * <p>For example, a Property plugin corresponds to a property entry in a configuration file. The property name is
> + * specified as an attribute, and the property value is specified as a value.</p>
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.PARAMETER, ElementType.FIELD})
> -@InjectionStrategy(PluginValueBuilder.class)
> +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
> +@InjectorStrategy(PluginValueInjector.class)
> +@NameProvider(PluginValueNameProvider.class)
> public @interface PluginValue {
>
> - String value();
> + /**
> + * Specifies the case-insensitive attribute name to use in configuration formats that don't distinguish between
> + * attributes and values. By default, this uses the attribute name {@code value}.
> + */
> + String value() default "value";
> }
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractOptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractOptionBinder.java
> new file mode 100644
> index 0000000..e3603ce
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractOptionBinder.java
> @@ -0,0 +1,62 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import org.apache.logging.log4j.Logger;
> +import org.apache.logging.log4j.plugins.convert.TypeConverter;
> +import org.apache.logging.log4j.plugins.convert.TypeConverterRegistry;
> +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
> +import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
> +import org.apache.logging.log4j.plugins.validation.ConstraintValidators;
> +import org.apache.logging.log4j.status.StatusLogger;
> +
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Type;
> +import java.util.Collection;
> +import java.util.Objects;
> +import java.util.function.Function;
> +
> +public abstract class AbstractOptionBinder<E extends AnnotatedElement> implements OptionBinder {
> + protected static final Logger LOGGER = StatusLogger.getLogger();
> +
> + final E element;
> + final String name;
> + private final Type injectionType;
> + private final Collection<ConstraintValidator<?>> validators;
> +
> + AbstractOptionBinder(final E element, final Function<E, Type> injectionTypeExtractor) {
> + this.element = Objects.requireNonNull(element);
> + this.name = AnnotatedElementNameProvider.getName(element);
> + Objects.requireNonNull(injectionTypeExtractor);
> + this.injectionType = Objects.requireNonNull(injectionTypeExtractor.apply(element));
> + this.validators = ConstraintValidators.findValidators(element.getAnnotations());
> + }
> +
> + @Override
> + public Object bindString(final Object target, final String value) {
> + Object convertedValue = null;
> + if (value != null) {
> + final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(injectionType);
> + try {
> + convertedValue = converter.convert(value);
> + } catch (final Exception e) {
> + LOGGER.error("Cannot convert string '{}' to type {} in option named {}. {}", value, injectionType, name, e);
> + }
> + }
> + return bindObject(target, convertedValue);
> + }
> +
> + void validate(final Object value) {
> + boolean valid = true;
> + for (ConstraintValidator<?> validator : validators) {
> + valid &= validator.isValid(name, value);
> + }
> + // FIXME: this doesn't seem to work properly with primitive types
> +// if (valid && value != null && !TypeUtil.isAssignable(injectionType, value.getClass())) {
> +// LOGGER.error("Cannot bind value of type {} to option {} with type {}", value.getClass(), name, injectionType);
> +// valid = false;
> +// }
> + if (!valid) {
> + throw new OptionBindingException(name, value);
> + }
> + }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java
> new file mode 100644
> index 0000000..80bee70
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java
> @@ -0,0 +1,58 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import java.lang.reflect.InvocationTargetException;
> +import java.lang.reflect.Method;
> +import java.lang.reflect.Parameter;
> +import java.util.Map;
> +import java.util.Objects;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.function.BiConsumer;
> +
> +// TODO: can support constructor factory following same pattern
> +public class FactoryMethodBinder {
> +
> + private final Method factoryMethod;
> + private final Map<Parameter, OptionBinder> binders = new ConcurrentHashMap<>();
> + private final Map<Parameter, Object> boundParameters = new ConcurrentHashMap<>();
> +
> + public FactoryMethodBinder(final Method factoryMethod) {
> + this.factoryMethod = Objects.requireNonNull(factoryMethod);
> + for (final Parameter parameter : factoryMethod.getParameters()) {
> + binders.put(parameter, new ParameterOptionBinder(parameter));
> + }
> + }
> +
> + public void forEachParameter(final BiConsumer<Parameter, OptionBinder> consumer) {
> + binders.forEach(consumer);
> + }
> +
> + public Object invoke() throws Throwable {
> + final Parameter[] parameters = factoryMethod.getParameters();
> + final Object[] args = new Object[parameters.length];
> + for (int i = 0; i < parameters.length; i++) {
> + args[i] = boundParameters.get(parameters[i]);
> + }
> + try {
> + return factoryMethod.invoke(null, args);
> + } catch (final IllegalAccessException e) {
> + throw new OptionBindingException("Cannot access factory method " + factoryMethod, e);
> + } catch (final InvocationTargetException e) {
> + throw e.getCause();
> + }
> + }
> +
> + private class ParameterOptionBinder extends AbstractOptionBinder<Parameter> {
> + private ParameterOptionBinder(final Parameter parameter) {
> + super(parameter, Parameter::getParameterizedType);
> + }
> +
> + @Override
> + public Object bindObject(final Object target, final Object value) {
> + validate(value);
> + if (value != null) {
> + boundParameters.put(element, value);
> + }
> + return target;
> + }
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldOptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldOptionBinder.java
> new file mode 100644
> index 0000000..bbd9a6b
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldOptionBinder.java
> @@ -0,0 +1,36 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import java.lang.reflect.Field;
> +import java.util.Objects;
> +
> +public class FieldOptionBinder extends AbstractOptionBinder<Field> {
> +
> + public FieldOptionBinder(final Field field) {
> + super(field, Field::getGenericType);
> + }
> +
> + @Override
> + public Object bindObject(final Object target, final Object value) {
> + Objects.requireNonNull(target);
> + // FIXME: if we specify a default field value, @PluginAttribute's defaultType will override that
> + if (value == null) {
> + try {
> + Object defaultValue = element.get(target);
> + validate(defaultValue);
> + LOGGER.trace("Using default value {} for option {}", defaultValue, name);
> + } catch (final IllegalAccessException e) {
> + throw new OptionBindingException("Unable to validate option " + name, e);
> + }
> + return target;
> + }
> + validate(value);
> + try {
> + element.set(target, value);
> + LOGGER.trace("Using value {} for option {}", value, name);
> + return target;
> + } catch (final IllegalAccessException e) {
> + throw new OptionBindingException(name, value, e);
> + }
> + }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodOptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodOptionBinder.java
> new file mode 100644
> index 0000000..6398520
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodOptionBinder.java
> @@ -0,0 +1,26 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +import java.lang.reflect.InvocationTargetException;
> +import java.lang.reflect.Method;
> +import java.util.Objects;
> +
> +public class MethodOptionBinder extends AbstractOptionBinder<Method> {
> +
> + public MethodOptionBinder(final Method method) {
> + super(method, m -> m.getGenericParameterTypes()[0]);
> + }
> +
> + @Override
> + public Object bindObject(final Object target, final Object value) {
> + Objects.requireNonNull(target);
> + validate(value);
> + try {
> + element.invoke(target, value);
> + } catch (final IllegalAccessException e) {
> + throw new OptionBindingException(name, value, e);
> + } catch (final InvocationTargetException e) {
> + throw new OptionBindingException(name, value, e.getCause());
> + }
> + return target;
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBinder.java
> new file mode 100644
> index 0000000..6a6872f
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBinder.java
> @@ -0,0 +1,7 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +public interface OptionBinder {
> + Object bindString(final Object target, final String value);
> +
> + Object bindObject(final Object target, final Object value);
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBindingException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBindingException.java
> new file mode 100644
> index 0000000..0cc37b1
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/OptionBindingException.java
> @@ -0,0 +1,24 @@
> +package org.apache.logging.log4j.plugins.bind;
> +
> +public class OptionBindingException extends IllegalArgumentException {
> +
> + public OptionBindingException(final String name, final Object value) {
> + super("Invalid value '" + value + "' for option '" + name + "'");
> + }
> +
> + public OptionBindingException(final String name, final Object value, final Throwable cause) {
> + super("Unable to set option '" + name + "' to value '" + value + "'", cause);
> + }
> +
> + public OptionBindingException(final String s) {
> + super(s);
> + }
> +
> + public OptionBindingException(final String message, final Throwable cause) {
> + super(message, cause);
> + }
> +
> + public OptionBindingException(final Throwable cause) {
> + super(cause);
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjectionBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjectionBuilder.java
> deleted file mode 100644
> index 2039947..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjectionBuilder.java
> +++ /dev/null
> @@ -1,175 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.Logger;
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.convert.TypeConverters;
> -import org.apache.logging.log4j.status.StatusLogger;
> -import org.apache.logging.log4j.util.Strings;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.reflect.Member;
> -import java.util.Map;
> -import java.util.Objects;
> -import java.util.function.Function;
> -
> -/**
> - * Base class for InjectionStrategyBuilder implementations. Provides fields and setters for the builder leaving only
> - * {@link ConfigurationInjectionBuilder#build()} to be implemented.
> - *
> - * @param <Ann> the Plugin annotation type.
> - */
> -public abstract class AbstractConfigurationInjectionBuilder<Ann extends Annotation, Cfg> implements ConfigurationInjectionBuilder<Ann, Cfg> {
> -
> - /** Status logger. */
> - protected static final Logger LOGGER = StatusLogger.getLogger();
> -
> - /**
> - *
> - */
> - protected final Class<Ann> clazz;
> - /**
> - *
> - */
> - protected Ann annotation;
> - /**
> - *
> - */
> - protected String[] aliases;
> - /**
> - *
> - */
> - protected Class<?> conversionType;
> - /**
> - *
> - */
> - protected Member member;
> -
> - protected Cfg configuration;
> -
> - protected Node node;
> -
> - protected Function<String, String> stringSubstitutionStrategy;
> -
> - protected StringBuilder debugLog;
> -
> - /**
> - * This constructor must be overridden by implementation classes as a no-arg constructor.
> - *
> - * @param clazz the annotation class this PluginVisitor is for.
> - */
> - protected AbstractConfigurationInjectionBuilder(final Class<Ann> clazz) {
> - this.clazz = clazz;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withAnnotation(final Annotation anAnnotation) {
> - final Annotation a = Objects.requireNonNull(anAnnotation, "No annotation was provided");
> - if (this.clazz.isInstance(a)) {
> - this.annotation = clazz.cast(a);
> - }
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withAliases(final String... someAliases) {
> - this.aliases = someAliases;
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withConversionType(final Class<?> aConversionType) {
> - this.conversionType = Objects.requireNonNull(aConversionType, "No conversion type class was provided");
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withMember(final Member aMember) {
> - this.member = aMember;
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withConfiguration(final Cfg aConfiguration) {
> - this.configuration = aConfiguration;
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> aStringSubstitutionStrategy) {
> - this.stringSubstitutionStrategy = aStringSubstitutionStrategy;
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withDebugLog(final StringBuilder aDebugLog) {
> - this.debugLog = aDebugLog;
> - return this;
> - }
> -
> - @Override
> - public ConfigurationInjectionBuilder<Ann, Cfg> withConfigurationNode(final Node aNode) {
> - this.node = aNode;
> - return this;
> - }
> -
> - /**
> - * Removes an Entry from a given Map using a key name and aliases for that key. Keys are case-insensitive.
> - *
> - * @param attributes the Map to remove an Entry from.
> - * @param name the key name to look up.
> - * @param aliases optional aliases of the key name to look up.
> - * @return the value corresponding to the given key or {@code null} if nonexistent.
> - */
> - protected static String removeAttributeValue(final Map<String, String> attributes,
> - final String name,
> - final String... aliases) {
> - for (final Map.Entry<String, String> entry : attributes.entrySet()) {
> - final String key = entry.getKey();
> - final String value = entry.getValue();
> - if (key.equalsIgnoreCase(name)) {
> - attributes.remove(key);
> - return value;
> - }
> - if (aliases != null) {
> - for (final String alias : aliases) {
> - if (key.equalsIgnoreCase(alias)) {
> - attributes.remove(key);
> - return value;
> - }
> - }
> - }
> - }
> - return null;
> - }
> -
> - /**
> - * Converts the given value into the configured type falling back to the provided default value.
> - *
> - * @param value the value to convert.
> - * @param defaultValue the fallback value to use in case of no value or an error.
> - * @return the converted value whether that be based on the given value or the default value.
> - */
> - protected Object convert(final String value, final Object defaultValue) {
> - if (defaultValue instanceof String) {
> - return TypeConverters.convert(value, this.conversionType, Strings.trimToNull((String) defaultValue));
> - }
> - return TypeConverters.convert(value, this.conversionType, defaultValue);
> - }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java
> new file mode 100644
> index 0000000..2513cfd
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java
> @@ -0,0 +1,118 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.Logger;
> +import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.PluginAliases;
> +import org.apache.logging.log4j.plugins.bind.OptionBinder;
> +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
> +import org.apache.logging.log4j.status.StatusLogger;
> +
> +import java.lang.annotation.Annotation;
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Type;
> +import java.util.Arrays;
> +import java.util.Collection;
> +import java.util.Collections;
> +import java.util.Map;
> +import java.util.Objects;
> +import java.util.Optional;
> +import java.util.function.Function;
> +
> +public abstract class AbstractConfigurationInjector<Ann extends Annotation, Cfg> implements ConfigurationInjector<Ann, Cfg> {
> +
> + protected static final Logger LOGGER = StatusLogger.getLogger();
> +
> + protected Ann annotation;
> + protected AnnotatedElement annotatedElement;
> + protected Type conversionType;
> + protected String name;
> + protected Collection<String> aliases = Collections.emptyList();
> + protected OptionBinder optionBinder;
> + protected StringBuilder debugLog;
> + protected Function<String, String> stringSubstitutionStrategy = Function.identity();
> + protected Cfg configuration;
> + protected Node node;
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withAnnotation(final Ann annotation) {
> + this.annotation = Objects.requireNonNull(annotation);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withAnnotatedElement(final AnnotatedElement element) {
> + this.annotatedElement = Objects.requireNonNull(element);
> + withName(AnnotatedElementNameProvider.getName(element));
> + final PluginAliases aliases = element.getAnnotation(PluginAliases.class);
> + if (aliases != null) {
> + withAliases(aliases.value());
> + }
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withConversionType(final Type type) {
> + this.conversionType = Objects.requireNonNull(type);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withName(final String name) {
> + this.name = Objects.requireNonNull(name);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withAliases(final String... aliases) {
> + this.aliases = Arrays.asList(aliases);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withOptionBinder(final OptionBinder binder) {
> + this.optionBinder = binder;
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withDebugLog(final StringBuilder debugLog) {
> + this.debugLog = Objects.requireNonNull(debugLog);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> strategy) {
> + this.stringSubstitutionStrategy = Objects.requireNonNull(strategy);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withConfiguration(final Cfg configuration) {
> + this.configuration = Objects.requireNonNull(configuration);
> + return this;
> + }
> +
> + @Override
> + public ConfigurationInjector<Ann, Cfg> withNode(final Node node) {
> + this.node = Objects.requireNonNull(node);
> + return this;
> + }
> +
> + protected Optional<String> findAndRemoveNodeAttribute() {
> + Objects.requireNonNull(node);
> + Objects.requireNonNull(name);
> + final Map<String, String> attributes = node.getAttributes();
> + for (final String key : attributes.keySet()) {
> + if (key.equalsIgnoreCase(name)) {
> + return Optional.ofNullable(attributes.remove(key));
> + }
> + for (final String alias : aliases) {
> + if (key.equalsIgnoreCase(alias)) {
> + return Optional.ofNullable(attributes.remove(key));
> + }
> + }
> + }
> + return Optional.empty();
> + }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjectionBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjectionBuilder.java
> deleted file mode 100644
> index bfcfea3..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjectionBuilder.java
> +++ /dev/null
> @@ -1,105 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.util.Builder;
> -import org.apache.logging.log4j.status.StatusLogger;
> -
> -import java.lang.annotation.Annotation;
> -import java.lang.reflect.Member;
> -import java.util.Optional;
> -import java.util.function.Function;
> -
> -/**
> - * Builder strategy for parsing and injecting a configuration node. Implementations should contain a default constructor
> - * and must provide a {@link #build()} implementation. This provides type conversion based on the injection point via
> - * {@link org.apache.logging.log4j.plugins.convert.TypeConverters}.
> - *
> - * @param <Ann> the Annotation type.
> - * @param <Cfg> the Configuration type.
> - */
> -public interface ConfigurationInjectionBuilder<Ann extends Annotation, Cfg> extends Builder<Object> {
> -
> - /**
> - * Creates a ConfigurationInjectionBuilder instance for the given annotation class using metadata provided by the annotation's
> - * {@link InjectionStrategy} annotation. This instance must be further populated with
> - * data before being {@linkplain #build() built} to be useful.
> - *
> - * @param injectorType the Plugin annotation class to find a ConfigurationInjectionBuilder for.
> - * @return a ConfigurationInjectionBuilder instance if one could be created or empty.
> - */
> - @SuppressWarnings("unchecked")
> - static <Ann extends Annotation, Cfg> Optional<ConfigurationInjectionBuilder<Ann, Cfg>> findBuilderForInjectionStrategy(final Class<Ann> injectorType) {
> - return Optional.ofNullable(injectorType.getAnnotation(InjectionStrategy.class))
> - .flatMap(type -> {
> - try {
> - return Optional.of((ConfigurationInjectionBuilder<Ann, Cfg>) type.value().newInstance());
> - } catch (final Exception e) {
> - StatusLogger.getLogger().error("Error loading PluginBuilder [{}] for annotation [{}].", type.value(), injectorType, e);
> - return Optional.empty();
> - }
> - });
> - }
> -
> - /**
> - * Sets the Annotation to be used for this. If the given Annotation is not compatible with this class's type, then
> - * it is ignored.
> - *
> - * @param annotation the Annotation instance.
> - * @return {@code this}.
> - * @throws NullPointerException if the argument is {@code null}.
> - */
> - ConfigurationInjectionBuilder<Ann, Cfg> withAnnotation(Annotation annotation);
> -
> - /**
> - * Sets the list of aliases to use for this injection. No aliases are required, however.
> - *
> - * @param aliases the list of aliases to use.
> - * @return {@code this}.
> - */
> - ConfigurationInjectionBuilder<Ann, Cfg> withAliases(String... aliases);
> -
> - /**
> - * Sets the class to convert the plugin value to for injection. This should correspond with a class obtained from
> - * a factory method or builder class field. Not all ConfigurationInjectionBuilder implementations may need this value.
> - *
> - * @param conversionType the type to convert the plugin string to (if applicable).
> - * @return {@code this}.
> - * @throws NullPointerException if the argument is {@code null}.
> - */
> - ConfigurationInjectionBuilder<Ann, Cfg> withConversionType(Class<?> conversionType);
> -
> - /**
> - * Sets the Member that this builder is being used for injection upon. For instance, this could be the Field
> - * that is being used for injecting a value, or it could be the factory method being used to inject parameters
> - * into.
> - *
> - * @param member the member this builder is parsing a value for.
> - * @return {@code this}.
> - */
> - ConfigurationInjectionBuilder<Ann, Cfg> withMember(Member member);
> -
> - ConfigurationInjectionBuilder<Ann, Cfg> withStringSubstitutionStrategy(Function<String, String> stringSubstitutionStrategy);
> -
> - ConfigurationInjectionBuilder<Ann, Cfg> withDebugLog(StringBuilder debugLog);
> -
> - ConfigurationInjectionBuilder<Ann, Cfg> withConfiguration(Cfg configuration);
> -
> - ConfigurationInjectionBuilder<Ann, Cfg> withConfigurationNode(Node node);
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java
> new file mode 100644
> index 0000000..048298b
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java
> @@ -0,0 +1,48 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.bind.OptionBinder;
> +import org.apache.logging.log4j.util.ReflectionUtil;
> +
> +import java.lang.annotation.Annotation;
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Type;
> +import java.util.Optional;
> +import java.util.function.Function;
> +
> +public interface ConfigurationInjector<Ann extends Annotation, Cfg> {
> +
> + static <Cfg> Optional<ConfigurationInjector<Annotation, Cfg>> forAnnotatedElement(final AnnotatedElement element) {
> + for (final Annotation annotation : element.getAnnotations()) {
> + final InjectorStrategy strategy = annotation.annotationType().getAnnotation(InjectorStrategy.class);
> + if (strategy != null) {
> + @SuppressWarnings("unchecked") final ConfigurationInjector<Annotation, Cfg> injector =
> + (ConfigurationInjector<Annotation, Cfg>) ReflectionUtil.instantiate(strategy.value());
> + return Optional.of(injector.withAnnotatedElement(element).withAnnotation(annotation));
> + }
> + }
> + return Optional.empty();
> + }
> +
> + ConfigurationInjector<Ann, Cfg> withAnnotation(final Ann annotation);
> +
> + ConfigurationInjector<Ann, Cfg> withAnnotatedElement(final AnnotatedElement element);
> +
> + ConfigurationInjector<Ann, Cfg> withConversionType(final Type type);
> +
> + ConfigurationInjector<Ann, Cfg> withName(final String name);
> +
> + ConfigurationInjector<Ann, Cfg> withAliases(final String... aliases);
> +
> + ConfigurationInjector<Ann, Cfg> withOptionBinder(final OptionBinder binder);
> +
> + ConfigurationInjector<Ann, Cfg> withDebugLog(final StringBuilder debugLog);
> +
> + ConfigurationInjector<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> strategy);
> +
> + ConfigurationInjector<Ann, Cfg> withConfiguration(final Cfg configuration);
> +
> + ConfigurationInjector<Ann, Cfg> withNode(final Node node);
> +
> + Object inject(final Object target);
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectionStrategy.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectionStrategy.java
> deleted file mode 100644
> index 1a67331..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectionStrategy.java
> +++ /dev/null
> @@ -1,42 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import java.lang.annotation.Annotation;
> -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;
> -
> -/**
> - * Meta-annotation to denote the class name to use that implements
> - * {@link ConfigurationInjectionBuilder} for the annotated annotation.
> - */
> -@Documented
> -@Retention(RetentionPolicy.RUNTIME)
> -@Target(ElementType.ANNOTATION_TYPE)
> -public @interface InjectionStrategy {
> -
> - /**
> - * The class to use that implements {@link ConfigurationInjectionBuilder}
> - * for the given annotation. The generic annotation type in {@code ConfigurationInjectionBuilder} should match the
> - * annotation this annotation is applied to.
> - */
> - Class<? extends ConfigurationInjectionBuilder<? extends Annotation, ?>> value();
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java
> new file mode 100644
> index 0000000..d398cb9
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java
> @@ -0,0 +1,16 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import java.lang.annotation.Annotation;
> +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;
> +
> +@Documented
> +@Retention(RetentionPolicy.RUNTIME)
> +@Target(ElementType.ANNOTATION_TYPE)
> +// TODO: annotation processor to validate type matches (help avoid runtime errors)
> +public @interface InjectorStrategy {
> + Class<? extends ConfigurationInjector<? extends Annotation, ?>> value();
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeBuilder.java
> deleted file mode 100644
> index 733a3a7..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeBuilder.java
> +++ /dev/null
> @@ -1,81 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginAttribute;
> -import org.apache.logging.log4j.util.NameUtil;
> -import org.apache.logging.log4j.util.StringBuilders;
> -
> -import java.util.Collections;
> -import java.util.Map;
> -import java.util.concurrent.ConcurrentHashMap;
> -import java.util.function.Function;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginAttribute}.
> - */
> -public class PluginAttributeBuilder extends AbstractConfigurationInjectionBuilder<PluginAttribute, Object> {
> -
> - private static final Map<Class<?>, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
> -
> - static {
> - final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
> - extractors.put(int.class, PluginAttribute::defaultInt);
> - extractors.put(Integer.class, PluginAttribute::defaultInt);
> - extractors.put(long.class, PluginAttribute::defaultLong);
> - extractors.put(Long.class, PluginAttribute::defaultLong);
> - extractors.put(boolean.class, PluginAttribute::defaultBoolean);
> - extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
> - extractors.put(float.class, PluginAttribute::defaultFloat);
> - extractors.put(Float.class, PluginAttribute::defaultFloat);
> - extractors.put(double.class, PluginAttribute::defaultDouble);
> - extractors.put(Double.class, PluginAttribute::defaultDouble);
> - extractors.put(byte.class, PluginAttribute::defaultByte);
> - extractors.put(Byte.class, PluginAttribute::defaultByte);
> - extractors.put(char.class, PluginAttribute::defaultChar);
> - extractors.put(Character.class, PluginAttribute::defaultChar);
> - extractors.put(short.class, PluginAttribute::defaultShort);
> - extractors.put(Short.class, PluginAttribute::defaultShort);
> - extractors.put(Class.class, PluginAttribute::defaultClass);
> - DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
> - }
> -
> - public PluginAttributeBuilder() {
> - super(PluginAttribute.class);
> - }
> -
> - @Override
> - public Object build() {
> - final String name = this.annotation.value();
> - final Map<String, String> attributes = node.getAttributes();
> - final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> - final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> - final Object defaultValue = findDefaultValue();
> - final Object value = convert(replacedValue, defaultValue);
> - final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> - StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> - return value;
> - }
> -
> - private Object findDefaultValue() {
> - return DEFAULT_VALUE_EXTRACTORS.getOrDefault(
> - conversionType,
> - pluginAttribute -> stringSubstitutionStrategy.apply(pluginAttribute.defaultString())
> - ).apply(annotation);
> - }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java
> new file mode 100644
> index 0000000..1805844
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java
> @@ -0,0 +1,72 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginAttribute;
> +import org.apache.logging.log4j.util.NameUtil;
> +import org.apache.logging.log4j.util.StringBuilders;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.lang.reflect.Type;
> +import java.util.Collections;
> +import java.util.Map;
> +import java.util.Optional;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.function.Function;
> +
> +public class PluginAttributeInjector extends AbstractConfigurationInjector<PluginAttribute, Object> {
> +
> + private static final Map<Type, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
> +
> + static {
> + final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
> + extractors.put(int.class, PluginAttribute::defaultInt);
> + extractors.put(Integer.class, PluginAttribute::defaultInt);
> + extractors.put(long.class, PluginAttribute::defaultLong);
> + extractors.put(Long.class, PluginAttribute::defaultLong);
> + extractors.put(boolean.class, PluginAttribute::defaultBoolean);
> + extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
> + extractors.put(float.class, PluginAttribute::defaultFloat);
> + extractors.put(Float.class, PluginAttribute::defaultFloat);
> + extractors.put(double.class, PluginAttribute::defaultDouble);
> + extractors.put(Double.class, PluginAttribute::defaultDouble);
> + extractors.put(byte.class, PluginAttribute::defaultByte);
> + extractors.put(Byte.class, PluginAttribute::defaultByte);
> + extractors.put(char.class, PluginAttribute::defaultChar);
> + extractors.put(Character.class, PluginAttribute::defaultChar);
> + extractors.put(short.class, PluginAttribute::defaultShort);
> + extractors.put(Short.class, PluginAttribute::defaultShort);
> + extractors.put(Class.class, PluginAttribute::defaultClass);
> + DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
> + }
> +
> + @Override
> + public Object inject(final Object target) {
> + final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
> + if (value.isPresent()) {
> + final String s = value.get();
> + return optionBinder.bindString(target, s);
> + } else {
> + return injectDefaultValue(target);
> + }
> + }
> +
> + private Object injectDefaultValue(final Object target) {
> + final Function<PluginAttribute, Object> extractor = DEFAULT_VALUE_EXTRACTORS.get(conversionType);
> + if (extractor != null) {
> + final Object value = extractor.apply(annotation);
> + debugLog(value);
> + return optionBinder.bindObject(target, value);
> + }
> + final String value = stringSubstitutionStrategy.apply(annotation.defaultString());
> + if (Strings.isNotBlank(value)) {
> + debugLog(value);
> + return optionBinder.bindString(target, value);
> + }
> + return target;
> + }
> +
> + private void debugLog(final Object value) {
> + final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> + StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> + }
> +
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeBuilder.java
> deleted file mode 100644
> index 8c870db..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeBuilder.java
> +++ /dev/null
> @@ -1,49 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
> -import org.apache.logging.log4j.util.NameUtil;
> -import org.apache.logging.log4j.util.StringBuilders;
> -
> -import java.util.Map;
> -
> -/**
> - * ConfigurationInjectionBuilder for PluginBuilderAttribute. If {@code null} is returned for the
> - * {@link ConfigurationInjectionBuilder#build()}}
> - * method, then the default value of the field should remain untouched.
> - */
> -public class PluginBuilderAttributeBuilder extends AbstractConfigurationInjectionBuilder<PluginBuilderAttribute, Object> {
> -
> - public PluginBuilderAttributeBuilder() {
> - super(PluginBuilderAttribute.class);
> - }
> -
> - @Override
> - public Object build() {
> - final String overridden = this.annotation.value();
> - final String name = overridden.isEmpty() ? this.member.getName() : overridden;
> - final Map<String, String> attributes = node.getAttributes();
> - final String rawValue = removeAttributeValue(attributes, name, this.aliases);
> - final String replacedValue = stringSubstitutionStrategy.apply(rawValue);
> - final Object value = convert(replacedValue, null);
> - final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
> - StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> - return value;
> - }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java
> new file mode 100644
> index 0000000..db14fa7
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java
> @@ -0,0 +1,19 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
> +import org.apache.logging.log4j.util.NameUtil;
> +import org.apache.logging.log4j.util.StringBuilders;
> +
> +public class PluginBuilderAttributeInjector extends AbstractConfigurationInjector<PluginBuilderAttribute, Object> {
> + @Override
> + public Object inject(final Object target) {
> + return findAndRemoveNodeAttribute()
> + .map(stringSubstitutionStrategy)
> + .map(value -> {
> + String debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
> + StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
> + return optionBinder.bindString(target, value);
> + })
> + .orElseGet(() -> optionBinder.bindObject(target, null));
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementBuilder.java
> deleted file mode 100644
> index 9dc4aea..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementBuilder.java
> +++ /dev/null
> @@ -1,110 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.Node;
> -import org.apache.logging.log4j.plugins.PluginElement;
> -import org.apache.logging.log4j.plugins.util.PluginType;
> -
> -import java.lang.reflect.Array;
> -import java.util.ArrayList;
> -import java.util.Arrays;
> -import java.util.Collection;
> -import java.util.List;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginElement}. Supports arrays as well as singular values.
> - */
> -public class PluginElementBuilder extends AbstractConfigurationInjectionBuilder<PluginElement, Object> {
> - public PluginElementBuilder() {
> - super(PluginElement.class);
> - }
> -
> - @Override
> - public Object build() {
> - final String name = this.annotation.value();
> - if (this.conversionType.isArray()) {
> - withConversionType(this.conversionType.getComponentType());
> - final List<Object> values = new ArrayList<>();
> - final Collection<Node> used = new ArrayList<>();
> - debugLog.append("={");
> - boolean first = true;
> - for (final Node child : node.getChildren()) {
> - final PluginType<?> childType = child.getType();
> - if (name.equalsIgnoreCase(childType.getElementName()) ||
> - this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> - if (!first) {
> - debugLog.append(", ");
> - }
> - first = false;
> - used.add(child);
> - final Object childObject = child.getObject();
> - if (childObject == null) {
> - LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName());
> - continue;
> - }
> - if (childObject.getClass().isArray()) {
> - debugLog.append(Arrays.toString((Object[]) childObject)).append('}');
> - node.getChildren().removeAll(used);
> - return childObject;
> - }
> - debugLog.append(child.toString());
> - values.add(childObject);
> - }
> - }
> - debugLog.append('}');
> - // note that we need to return an empty array instead of null if the types are correct
> - if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) {
> - LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.",
> - name, values.get(0).getClass(), this.conversionType);
> - return null;
> - }
> - node.getChildren().removeAll(used);
> - // we need to use reflection here because values.toArray() will cause type errors at runtime
> - final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size());
> - for (int i = 0; i < array.length; i++) {
> - array[i] = values.get(i);
> - }
> - return array;
> - }
> - final Node namedNode = findNamedNode(name, node.getChildren());
> - if (namedNode == null) {
> - debugLog.append(name).append("=null");
> - return null;
> - }
> - debugLog.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')');
> - node.getChildren().remove(namedNode);
> - return namedNode.getObject();
> - }
> -
> - private Node findNamedNode(final String name, final Iterable<Node> children) {
> - for (final Node child : children) {
> - final PluginType<?> childType = child.getType();
> - if (childType == null) {
> - //System.out.println();
> - }
> - if (name.equalsIgnoreCase(childType.getElementName()) ||
> - this.conversionType.isAssignableFrom(childType.getPluginClass())) {
> - // FIXME: check child.getObject() for null?
> - // doing so would be more consistent with the array version
> - return child;
> - }
> - }
> - return null;
> - }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java
> new file mode 100644
> index 0000000..33f372c
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java
> @@ -0,0 +1,88 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.Node;
> +import org.apache.logging.log4j.plugins.PluginElement;
> +import org.apache.logging.log4j.plugins.util.PluginType;
> +import org.apache.logging.log4j.plugins.util.TypeUtil;
> +
> +import java.lang.reflect.Array;
> +import java.lang.reflect.Type;
> +import java.util.ArrayList;
> +import java.util.Arrays;
> +import java.util.Collection;
> +import java.util.List;
> +import java.util.Optional;
> +
> +public class PluginElementInjector extends AbstractConfigurationInjector<PluginElement, Object> {
> + @Override
> + public Object inject(final Object target) {
> + final Optional<Class<?>> componentType = getComponentType(conversionType);
> + if (componentType.isPresent()) {
> + final Class<?> compType = componentType.get();
> + final List<Object> values = new ArrayList<>();
> + final Collection<Node> used = new ArrayList<>();
> + debugLog.append("={");
> + boolean first = true;
> + for (final Node child : node.getChildren()) {
> + final PluginType<?> type = child.getType();
> + if (name.equalsIgnoreCase(type.getElementName()) || compType.isAssignableFrom(type.getPluginClass())) {
> + if (!first) {
> + debugLog.append(", ");
> + }
> + first = false;
> + used.add(child);
> + final Object childObject = child.getObject();
> + if (childObject == null) {
> + LOGGER.warn("Skipping null object returned for element {} in node {}", child.getName(), node.getName());
> + } else if (childObject.getClass().isArray()) {
> + Object[] children = (Object[]) childObject;
> + debugLog.append(Arrays.toString(children)).append('}');
> + node.getChildren().removeAll(used);
> + return optionBinder.bindObject(target, children);
> + } else {
> + debugLog.append(child.toString());
> + values.add(childObject);
> + }
> + }
> + }
> + debugLog.append('}');
> + if (!values.isEmpty() && !TypeUtil.isAssignable(compType, values.get(0).getClass())) {
> + LOGGER.error("Cannot assign element {} a list of {} as it is incompatible with {}", name, values.get(0).getClass(), compType);
> + return null;
> + }
> + node.getChildren().removeAll(used);
> + // using List::toArray here would cause type mismatch later on
> + final Object[] vals = (Object[]) Array.newInstance(compType, values.size());
> + for (int i = 0; i < vals.length; i++) {
> + vals[i] = values.get(i);
> + }
> + return optionBinder.bindObject(target, vals);
> + } else {
> + final Optional<Node> matchingChild = node.getChildren().stream().filter(this::isRequestedNode).findAny();
> + if (matchingChild.isPresent()) {
> + final Node child = matchingChild.get();
> + debugLog.append(child.getName()).append('(').append(child.toString()).append(')');
> + node.getChildren().remove(child);
> + return optionBinder.bindObject(target, child.getObject());
> + } else {
> + debugLog.append(name).append("=null");
> + return optionBinder.bindObject(target, null);
> + }
> + }
> + }
> +
> + private boolean isRequestedNode(final Node child) {
> + final PluginType<?> type = child.getType();
> + return name.equalsIgnoreCase(type.getElementName()) || TypeUtil.isAssignable(conversionType, type.getPluginClass());
> + }
> +
> + private static Optional<Class<?>> getComponentType(final Type type) {
> + if (type instanceof Class<?>) {
> + final Class<?> clazz = (Class<?>) type;
> + if (clazz.isArray()) {
> + return Optional.of(clazz.getComponentType());
> + }
> + }
> + return Optional.empty();
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeBuilder.java
> deleted file mode 100644
> index 06d06f6..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeBuilder.java
> +++ /dev/null
> @@ -1,39 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginNode;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginNode}.
> - */
> -public class PluginNodeBuilder extends AbstractConfigurationInjectionBuilder<PluginNode, Object> {
> - public PluginNodeBuilder() {
> - super(PluginNode.class);
> - }
> -
> - @Override
> - public Object build() {
> - if (this.conversionType.isInstance(node)) {
> - debugLog.append("Node=").append(node.getName());
> - return node;
> - }
> - LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass());
> - return null;
> - }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java
> new file mode 100644
> index 0000000..62353f0
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java
> @@ -0,0 +1,17 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginNode;
> +import org.apache.logging.log4j.plugins.util.TypeUtil;
> +
> +public class PluginNodeInjector extends AbstractConfigurationInjector<PluginNode, Object> {
> + @Override
> + public Object inject(final Object target) {
> + if (TypeUtil.isAssignable(conversionType, node.getClass())) {
> + debugLog.append("Node=").append(node.getName());
> + return optionBinder.bindObject(target, node);
> + } else {
> + LOGGER.error("Element with type {} annotated with @PluginNode not compatible with type {}.", conversionType, node.getClass());
> + return target;
> + }
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueBuilder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueBuilder.java
> deleted file mode 100644
> index 2cfe357..0000000
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueBuilder.java
> +++ /dev/null
> @@ -1,52 +0,0 @@
> -/*
> - * Licensed to the Apache Software Foundation (ASF) under one or more
> - * contributor license agreements. See the NOTICE file distributed with
> - * this work for additional information regarding copyright ownership.
> - * The ASF licenses this file to You under the Apache license, Version 2.0
> - * (the "License"); you may not use this file except in compliance with
> - * the License. You may obtain a copy of the License at
> - *
> - * http://www.apache.org/licenses/LICENSE-2.0
> - *
> - * Unless required by applicable law or agreed to in writing, software
> - * distributed under the License is distributed on an "AS IS" BASIS,
> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> - * See the license for the specific language governing permissions and
> - * limitations under the license.
> - */
> -
> -package org.apache.logging.log4j.plugins.inject;
> -
> -import org.apache.logging.log4j.plugins.PluginValue;
> -import org.apache.logging.log4j.util.StringBuilders;
> -import org.apache.logging.log4j.util.Strings;
> -
> -/**
> - * ConfigurationInjectionBuilder implementation for {@link PluginValue}.
> - */
> -public class PluginValueBuilder extends AbstractConfigurationInjectionBuilder<PluginValue, Object> {
> - public PluginValueBuilder() {
> - super(PluginValue.class);
> - }
> -
> - @Override
> - public Object build() {
> - final String name = this.annotation.value();
> - final String elementValue = node.getValue();
> - final String attributeValue = node.getAttributes().get("value");
> - String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
> - if (Strings.isNotEmpty(elementValue)) {
> - if (Strings.isNotEmpty(attributeValue)) {
> - LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" +
> - " value ({}). Please specify only one value. Using the element value.",
> - node.getName(), attributeValue, elementValue);
> - }
> - rawValue = elementValue;
> - } else {
> - rawValue = removeAttributeValue(node.getAttributes(), "value");
> - }
> - final String value = stringSubstitutionStrategy.apply(rawValue);
> - StringBuilders.appendKeyDqValue(debugLog, name, value);
> - return value;
> - }
> -}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java
> new file mode 100644
> index 0000000..0325bba
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java
> @@ -0,0 +1,27 @@
> +package org.apache.logging.log4j.plugins.inject;
> +
> +import org.apache.logging.log4j.plugins.PluginValue;
> +import org.apache.logging.log4j.util.StringBuilders;
> +import org.apache.logging.log4j.util.Strings;
> +
> +public class PluginValueInjector extends AbstractConfigurationInjector<PluginValue, Object> {
> + @Override
> + public Object inject(final Object target) {
> + final String elementValue = node.getValue();
> + final String attributeValue = node.getAttributes().get(name);
> + String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
> + if (Strings.isNotEmpty(elementValue)) {
> + if (Strings.isNotEmpty(attributeValue)) {
> + LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" +
> + " value ({}). Please specify only one value. Using the element value.",
> + node.getName(), attributeValue, elementValue);
> + }
> + rawValue = elementValue;
> + } else {
> + rawValue = findAndRemoveNodeAttribute().orElse(null);
> + }
> + final String value = stringSubstitutionStrategy.apply(rawValue);
> + StringBuilders.appendKeyDqValue(debugLog, name, value);
> + return optionBinder.bindString(target, value);
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
> index 47259e0..a2afb2b 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
> @@ -17,7 +17,7 @@
>
> /**
> * Injection builder classes for parsing data from a {@code Configuration} or {@link org.apache.logging.log4j.plugins.Node}
> - * corresponding to an {@link org.apache.logging.log4j.plugins.inject.InjectionStrategy}-annotated annotation.
> - * Injection strategies must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjectionBuilder}.
> + * corresponding to an {@link org.apache.logging.log4j.plugins.inject.InjectorStrategy}-annotated annotation.
> + * Injection strategies must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjector}.
> */
> package org.apache.logging.log4j.plugins.inject;
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java
> new file mode 100644
> index 0000000..a5d7faf
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java
> @@ -0,0 +1,69 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.util.ReflectionUtil;
> +
> +import java.beans.Introspector;
> +import java.lang.annotation.Annotation;
> +import java.lang.reflect.AnnotatedElement;
> +import java.lang.reflect.Field;
> +import java.lang.reflect.Method;
> +import java.lang.reflect.Parameter;
> +import java.util.Optional;
> +
> +/**
> + * Extracts a specified name for some configurable annotated element. A specified name is one given in a non-empty
> + * string in an annotation as opposed to relying on the default name taken from the annotated element itself.
> + *
> + * @param <A> plugin configuration annotation
> + */
> +public interface AnnotatedElementNameProvider<A extends Annotation> {
> +
> + static String getName(final AnnotatedElement element) {
> + for (final Annotation annotation : element.getAnnotations()) {
> + final Optional<String> specifiedName = getSpecifiedNameForAnnotation(annotation);
> + if (specifiedName.isPresent()) {
> + return specifiedName.get();
> + }
> + }
> +
> + if (element instanceof Field) {
> + return ((Field) element).getName();
> + }
> +
> + if (element instanceof Method) {
> + final Method method = (Method) element;
> + final String methodName = method.getName();
> + if (methodName.startsWith("set")) {
> + return Introspector.decapitalize(methodName.substring(3));
> + }
> + if (methodName.startsWith("with")) {
> + return Introspector.decapitalize(methodName.substring(4));
> + }
> + return method.getParameters()[0].getName();
> + }
> +
> + if (element instanceof Parameter) {
> + return ((Parameter) element).getName();
> + }
> +
> + throw new IllegalArgumentException("Unknown element type for naming: " + element.getClass());
> + }
> +
> + static <A extends Annotation> Optional<String> getSpecifiedNameForAnnotation(final A annotation) {
> + return Optional.ofNullable(annotation.annotationType().getAnnotation(NameProvider.class))
> + .map(NameProvider::value)
> + .flatMap(clazz -> {
> + @SuppressWarnings("unchecked") final AnnotatedElementNameProvider<A> factory =
> + (AnnotatedElementNameProvider<A>) ReflectionUtil.instantiate(clazz);
> + return factory.getSpecifiedName(annotation);
> + });
> + }
> +
> + /**
> + * Returns the specified name from this annotation if given or {@code Optional.empty()} if none given.
> + *
> + * @param annotation annotation value of configuration element
> + * @return specified name of configuration element or empty if none specified
> + */
> + Optional<String> getSpecifiedName(final A annotation);
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java
> new file mode 100644
> index 0000000..c374543
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java
> @@ -0,0 +1,15 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import java.lang.annotation.Annotation;
> +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;
> +
> +@Documented
> +@Retention(RetentionPolicy.RUNTIME)
> +@Target(ElementType.ANNOTATION_TYPE)
> +public @interface NameProvider {
> + Class<? extends AnnotatedElementNameProvider<? extends Annotation>> value();
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java
> new file mode 100644
> index 0000000..29790a8
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginAttribute;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginAttributeNameProvider implements AnnotatedElementNameProvider<PluginAttribute> {
> + @Override
> + public Optional<String> getSpecifiedName(final PluginAttribute annotation) {
> + return Strings.trimToOptional(annotation.value());
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java
> new file mode 100644
> index 0000000..ae2c12a
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginBuilderAttributeNameProvider implements AnnotatedElementNameProvider<PluginBuilderAttribute> {
> + @Override
> + public Optional<String> getSpecifiedName(final PluginBuilderAttribute annotation) {
> + return Strings.trimToOptional(annotation.value());
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java
> new file mode 100644
> index 0000000..b14eb81
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginElement;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginElementNameProvider implements AnnotatedElementNameProvider<PluginElement> {
> + @Override
> + public Optional<String> getSpecifiedName(final PluginElement annotation) {
> + return Strings.trimToOptional(annotation.value());
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java
> new file mode 100644
> index 0000000..515264c
> --- /dev/null
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java
> @@ -0,0 +1,13 @@
> +package org.apache.logging.log4j.plugins.name;
> +
> +import org.apache.logging.log4j.plugins.PluginValue;
> +import org.apache.logging.log4j.util.Strings;
> +
> +import java.util.Optional;
> +
> +public class PluginValueNameProvider implements AnnotatedElementNameProvider<PluginValue> {
> + @Override
> + public Optional<String> getSpecifiedName(final PluginValue annotation) {
> + return Strings.trimToOptional(annotation.value());
> + }
> +}
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
> index b161185..b515d96 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
> @@ -17,5 +17,8 @@
>
> /**
> * Annotations for Log4j 2 plugins.
> + *
> + * @see org.apache.logging.log4j.plugins.Plugin
> + * @see org.apache.logging.log4j.plugins.PluginFactory
> */
> package org.apache.logging.log4j.plugins;
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
> index 9b8a75d..dd0efa8 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
> @@ -29,7 +29,7 @@ import java.lang.annotation.*;
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.FIELD, ElementType.PARAMETER})
> +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
> @Constraint(RequiredValidator.class)
> public @interface Required {
>
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
> index 14dd9a8..6a31ef4 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
> @@ -30,7 +30,7 @@ import java.net.InetAddress;
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.FIELD, ElementType.PARAMETER})
> +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
> @Constraint(ValidHostValidator.class)
> public @interface ValidHost {
>
> diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
> index c4aba16..edb7e72 100644
> --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
> +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
> @@ -29,7 +29,7 @@ import java.lang.annotation.*;
> */
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> -@Target({ElementType.FIELD, ElementType.PARAMETER})
> +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
> @Constraint(ValidPortValidator.class)
> public @interface ValidPort {
>
> diff --git a/src/changes/changes.xml b/src/changes/changes.xml
> index 0a4d5f7..09be11f 100644
> --- a/src/changes/changes.xml
> +++ b/src/changes/changes.xml
> @@ -31,6 +31,15 @@
> - "remove" - Removed
> -->
> <release version="3.0.0" date="2019-xx-xx" description="GA Release 3.0.0">
> + <action issue="LOG4J2-2700" dev="mattsicker" type="add">
> + Add support for injecting plugin configuration via builder methods.
> + </action>
> + <action issue="LOG4J2-2693" dev="mattsicker" type="fix">
> + @PluginValue does not support attribute names besides "value".
> + </action>
> + <action issue="LOG4J2-860" dev="mattsicker" type="update">
> + Unify plugin builders and plugin factories.
> + </action>
> <action issue="LOG4J2-2690" dev="rgoers" type="update">
> Locate plugins in modules.
> </action>
>
--
Matt Sicker
Secretary, Apache Software Foundation
VP Logging Services, ASF