ppkarwasz commented on code in PR #3505: URL: https://github.com/apache/logging-log4j2/pull/3505#discussion_r1976724741
########## log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java: ########## @@ -82,15 +81,15 @@ void after() { @Test void testClosingLoggerContext() { // initial config with indexed rollover - try (final LoggerContext loggerContext1 = + try (final LoggerContext tLoggerContext1 = Review Comment: This isn't really necessary (and the 3 changes below). ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java: ########## @@ -19,78 +19,199 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.util.Builder; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** * Builds arbitrary components and is the base type for the provided components. * @param <T> The ComponentBuilder's own type for fluent APIs. * @since 2.4 */ +@ProviderType public interface ComponentBuilder<T extends ComponentBuilder<T>> extends Builder<Component> { /** - * Adds a String attribute. + * Returns the name of the component. + * @return The component's name or {@code null} if it doesn't have one. + */ + @Nullable + String getName(); + + /** + * Retrieves the {@code ConfigurationBuilder}. + * @return The ConfigurationBuilder. + */ + ConfigurationBuilder<? extends Configuration> getBuilder(); + + /** + * Adds a sub component. + * @param builder The Assembler for the subcomponent with all of its attributes and sub-components set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} + */ + T addComponent(ComponentBuilder<?> builder); + + /** + * Sets a String attribute. + * <p> + * If the given {@code level} value is {@code null}, the component attribute with the given key + * will be removed (if present). + * </p> * @param key The attribute key. * @param value The value of the attribute. - * @return This ComponentBuilder. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code key} is {@code null} */ - T addAttribute(String key, String value); + T setAttribute(String key, @Nullable String value); /** - * Adds a logging Level attribute. + * Sets a logging Level attribute. + * <p> + * If the given {@code level} value is {@code null}, the component attribute with the given key + * will be removed (if present). + * </p> * @param key The attribute key. * @param level The logging Level. - * @return This ComponentBuilder. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code key} is {@code null} */ - T addAttribute(String key, Level level); + T setAttribute(String key, @Nullable Level level); Review Comment: We can probably provide a `default` implementation for these methods. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java: ########## @@ -16,430 +16,587 @@ */ package org.apache.logging.log4j.core.config.builder.api; +import aQute.bnd.annotation.baseline.BaselineIgnore; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.async.AsyncLogger; +import org.apache.logging.log4j.core.config.AppenderRef; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.CustomLevels; +import org.apache.logging.log4j.core.config.LoggerConfig.RootLogger; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.script.Script; +import org.apache.logging.log4j.core.script.ScriptFile; import org.apache.logging.log4j.core.util.Builder; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** * Interface for building logging configurations. * @param <T> The Configuration type created by this builder. * @since 2.4 */ +@ProviderType public interface ConfigurationBuilder<T extends Configuration> extends Builder<T> { /** - * Adds a ScriptComponent. - * @param builder The ScriptComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Script} component built by the given {@code ScriptComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code ScriptComponentBuilder} to add with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(ScriptComponentBuilder builder); /** - * Adds a ScriptFileComponent. - * @param builder The ScriptFileComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link ScriptFile} component built by the given {@code ScriptFileComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code ScriptFileComponentBuilder} to add with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(ScriptFileComponentBuilder builder); /** - * Adds an AppenderComponent. - * @param builder The AppenderComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Appender} component built by the given {@code AppenderComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code AppenderComponentBuilder} to add with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(AppenderComponentBuilder builder); /** - * Adds a CustomLevel component. - * @param builder The CustomLevelComponentBuilder with all of its attributes set. - * @return this builder instance. + * Adds the {@link CustomLevels} component built by the given {@code CustomLevelComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code CustomLevelComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(CustomLevelComponentBuilder builder); /** - * Adds a Filter component. - * @param builder the FilterComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Filter} component built by the given {@code FilterComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code FilterComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(FilterComponentBuilder builder); /** - * Adds a Logger component. - * @param builder The LoggerComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Logger} component built by the given {@code LoggerComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code LoggerComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(LoggerComponentBuilder builder); /** - * Adds the root Logger component. - * @param builder The RootLoggerComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link RootLogger} component built by the given {@code RootLoggerComponentBuilder} + * to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code RootLoggerComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(RootLoggerComponentBuilder builder); /** - * Adds a Property key and value. - * @param key The property key. - * @param value The property value. - * @return this builder instance. + * Adds a the {@link Property} component built by the given {@link PropertyComponentBuilder}. + * @param builder the {@code PropertyComponentBuilder} to add + * @return this builder (for chaining) */ - ConfigurationBuilder<T> addProperty(String key, String value); + ConfigurationBuilder<T> add(PropertyComponentBuilder builder); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @param language The script language - * @param text The script to execute. - * @return A new ScriptComponentBuilder. + * Adds a {@link Property} component with the given {@code key} and {@code value}. + * + * @param name the property name + * @param value the property value + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} + * @deprecated use {@link #add(PropertyComponentBuilder)} */ - ScriptComponentBuilder newScript(String name, String language, String text); + @Deprecated + ConfigurationBuilder<T> addProperty(@Nullable String name, @Nullable String value); Review Comment: The `@throws` description is a copy/paste problem. Neither `name` nor `value` should be `null` IMHO. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/CompositeFilterComponentBuilder.java: ########## @@ -16,9 +16,71 @@ */ package org.apache.logging.log4j.core.config.builder.api; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.jspecify.annotations.Nullable; + /** - * Wraps multiple Filter Component builders. + * A builder interface for constructing and configuring {@link CompositeFilter} components in a Log4j configuration. + * + * <p> + * Instances of this builder are designed for single-threaded use and are not thread-safe. Developers + * should avoid sharing instances between threads. + * </p> * * @since 2.4 */ -public interface CompositeFilterComponentBuilder extends FilterableComponentBuilder<CompositeFilterComponentBuilder> {} +public interface CompositeFilterComponentBuilder extends FilterableComponentBuilder<CompositeFilterComponentBuilder> { + + /** + * Adds the 'onMatch' attribute to the filter component. + * <p> + * If the given {@code onMatch} argument is {@code} the attribute will be removed (if present). + * </p> + * + * @param onMatch the attribute value + * @return this builder (for chaining) + */ + default CompositeFilterComponentBuilder setOnMatchAttribute(@Nullable String onMatch) { + return setAttribute("onMatch", onMatch); + } Review Comment: `CompositeFilter` does not have neither an `onMatch` nor an `onMismatch` attribute. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java: ########## @@ -17,104 +17,217 @@ package org.apache.logging.log4j.core.config.builder.impl; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.builder.api.Component; import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** - * Generic component that captures attributes and Components in preparation for assembling the Appender's - * Component. + * Generic base default {@link ComponentBuilder} implementation that captures attributes and children + * which are used to build a new {@link Component} instance. * + * @param <T> the type of the component builder + * @param <CB> the type of the configuration builder * @since 2.4 */ +@ProviderType class DefaultComponentBuilder<T extends ComponentBuilder<T>, CB extends ConfigurationBuilder<? extends Configuration>> implements ComponentBuilder<T> { private final CB builder; - private final String type; + private final String pluginType; private final Map<String, String> attributes = new LinkedHashMap<>(); private final List<Component> components = new ArrayList<>(); - private final String name; - private final String value; - - public DefaultComponentBuilder(final CB builder, final String type) { - this(builder, null, type, null); + private final @Nullable String name; + private final @Nullable String value; + + /** + * Constructs a new instance with the given configuration builder and plugin-type with {@code null} name and value. + * @param builder the configuration builder + * @param pluginType the plugin-type of the component being built + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder(final CB builder, final String pluginType) { + this(builder, pluginType, null, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type) { - this(builder, name, type, null); + /** + * Constructs a new instancer with the given configuration builder, plugin-type, name, and {@code null} value. + * @param builder the configuration builder + * @param pluginType the plugin-type of the component being built + * @param name the component name + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder(final CB builder, final String pluginType, final @Nullable String name) { + this(builder, pluginType, name, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type, final String value) { - this.type = type; - this.builder = builder; + /** + * Constructs a new instance with the given configuration builder, plugin-type, name and value. + * @param builder the configuration builder + * @param pluginType the type (plugin-type) of the component being built + * @param name the component name + * @param value the component value + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder( + final CB builder, final String pluginType, final @Nullable String name, final @Nullable String value) { + super(); + this.builder = Objects.requireNonNull(builder, "The 'builder' argument must not be null."); + this.pluginType = Objects.requireNonNull(pluginType, "The 'type' argument must not be null."); this.name = name; this.value = value; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final boolean value) { - return put(key, Boolean.toString(value)); + public Component build() { + final Component component = new Component(pluginType, name, value); + component.getAttributes().putAll(attributes); + component.getComponents().addAll(components); + return component; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Enum<?> value) { - return put(key, value.name()); + public T addComponent(final ComponentBuilder<?> builder) { + Objects.requireNonNull(builder, "The 'builder' argument must not be null."); + components.add(builder.build()); + return self(); } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final int value) { - return put(key, Integer.toString(value)); + public @NonNull CB getBuilder() { Review Comment: ```suggestion public CB getBuilder() { ``` Isn't `@NonNull` the default? ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java: ########## @@ -334,7 +332,7 @@ private static <B extends ComponentBuilder<B>> ComponentBuilder<B> createCompone final ComponentBuilder<?> parent, final String key, final Properties properties) { final String name = (String) properties.remove(CONFIG_NAME); final String type = (String) properties.remove(CONFIG_TYPE); - if (Strings.isEmpty(type)) { + if (type == null || Strings.isEmpty(type)) { Review Comment: ```suggestion if (Strings.isEmpty(type)) { ``` `Strings.isEmpty` is `null`-safe. ########## src/changelog/.2.x.x/2791_rework_ComponentBuilder_API.xml: ########## @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://logging.apache.org/xml/ns" + xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="changed"> + <issue id="2791" link="https://github.com/apache/logging-log4j2/issues/2791"/> + <description format="asciidoc"> + Fix problem when null attribute values are set on DefaultComponentBuilder. GitHub issue #2791. + Added JVerify annotations to config.builder.* packages. Review Comment: ```suggestion Added JSpecify annotations to config.builder.* packages. ``` ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/Component.java: ########## @@ -74,7 +94,27 @@ public String getPluginType() { return pluginType; } - public String getValue() { + public @Nullable String getValue() { return value; } + + /** + * Puts the given key/value pair to the attribute map. + * <p> + * If the new value is {@code null}, than any existing entry with the given {@code key} is ejected from the map. + * </p> + * @param key the key + * @param newValue the new value + * @return the previous value or {@code null} if none was set + */ + protected @Nullable String putAttribute(final String key, final @Nullable String newValue) { Review Comment: Why is this `protected`? It has a better sounding name than `addAttribute`. Maybe `addAttribute` should be deprecated (possibly with an Error Prone [`@InlineMe`](https://errorprone.info/docs/inlineme) annotation) and this should be `public`? ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/AppenderRefComponentBuilder.java: ########## @@ -16,8 +16,58 @@ */ package org.apache.logging.log4j.core.config.builder.api; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.jspecify.annotations.Nullable; + /** - * Assembler for constructing AppenderRef Components. + * A builder interface for constructing and configuring {@link AppenderRef} components in a Log4j configuration. + * + * <p> + * Instances of this builder are designed for single-threaded use and are not thread-safe. Developers + * should avoid sharing instances between threads. + * </p> + * * @since 2.4 */ -public interface AppenderRefComponentBuilder extends FilterableComponentBuilder<AppenderRefComponentBuilder> {} +public interface AppenderRefComponentBuilder extends FilterableComponentBuilder<AppenderRefComponentBuilder> { + + /** + * Sets the "{@code level}" attribute on the appender-reference component. + * <p> + * If the given {@code level} is {@code null}, the attribute will be removed from the component. + * </p> + * + * @param level the level + * @return this builder (for chaining) + */ + default AppenderRefComponentBuilder setLevelAttribute(@Nullable String level) { Review Comment: IMHO `setLevel` would be a better naming choice: it is not ambiguous and it is shorter. Should we remove the `Attribute` suffix from all new methods? ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/Component.java: ########## @@ -20,45 +20,65 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** * Container for building Configurations. This class is not normally directly manipulated by users * of the Assembler API. * @since 2.4 */ +@ProviderType Review Comment: ```suggestion ``` This class is not meant to be extended by neither consumers nor providers, so no annotation is needed. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/LoggableComponentBuilder.java: ########## @@ -16,17 +16,97 @@ */ package org.apache.logging.log4j.core.config.builder.api; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.config.LoggerConfig.RootLogger; +import org.jspecify.annotations.Nullable; + /** - * Common component builder for Logger and RootLogger elements. + * A common builder interface for constructing and configuring {@link Logger} and {@link RootLogger} + * components in a Log4j configuration. + * + * <p> + * Instances of this builder are designed for single-threaded use and are not thread-safe. Developers + * should avoid sharing instances between threads. + * </p> * * @since 2.6 */ public interface LoggableComponentBuilder<T extends ComponentBuilder<T>> extends FilterableComponentBuilder<T> { + + /** + * Add an {@link AppenderRefComponentBuilder} to this logger component builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code AppenderRefComponentBuilder} with all of its attributes and subcomponents set. + * @return this component builder (for chaining) + * @throws NullPointerException if the given {@code builder} argument is {@code null} + */ + T add(AppenderRefComponentBuilder builder); + + /** + * Sets the "{@code additivity}" attribute on the logger component. + * + * @param additivity {@code true} if additive; otherwise, {@code false} + * @return this builder (for chaining) + */ + default T setAdditivityAttribute(boolean additivity) { Review Comment: This is not useful for all logger builders. For example `Root` and `AsyncRoot` do not have an `additivity` attribute, since they don't have a parent logger. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java: ########## @@ -20,38 +20,64 @@ import java.io.InputStream; import java.util.Arrays; import java.util.List; +import java.util.Objects; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Reconfigurable; import org.apache.logging.log4j.core.config.builder.api.Component; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Patterns; +import org.jspecify.annotations.Nullable; /** - * This is the general version of the Configuration created by the Builder. It may be extended to - * enhance its functionality. + * A {@link Configuration} implementation that is built using a {@link ConfigurationBuilder} implementaion. + * <p> + * This class may be extended to enhance its functionality. + * </p> * * @since 2.4 */ public class BuiltConfiguration extends AbstractConfiguration { + + private static final String DEFAULT_CONTENT_TYPE = "text"; + private final StatusConfiguration statusConfig; - protected Component rootComponent; - private Component loggersComponent; - private Component appendersComponent; - private Component filtersComponent; - private Component propertiesComponent; - private Component customLevelsComponent; - private Component scriptsComponent; - private String contentType = "text"; + private String contentType = DEFAULT_CONTENT_TYPE; + + protected @Nullable Component rootComponent; Review Comment: ```suggestion protected Component rootComponent; ``` It is assigned to a non-null value in the constructor. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java: ########## @@ -78,45 +104,63 @@ public BuiltConfiguration( customLevelsComponent = component; break; } + default: { + // NO-OP + break; + } } } + this.rootComponent = rootComponent; } + /** {@inheritDoc} */ @Override public void setup() { + final List<Node> children = rootNode.getChildren(); - if (propertiesComponent.getComponents().size() > 0) { + + if (propertiesComponent != null && !propertiesComponent.getComponents().isEmpty()) { children.add(convertToNode(rootNode, propertiesComponent)); } - if (scriptsComponent.getComponents().size() > 0) { + + if (scriptsComponent != null && !scriptsComponent.getComponents().isEmpty()) { Review Comment: While this only modifies pre-existing code, I don't think this logic is at all necessary: - we extract from `rootComponent` **some**, but not all the children. - then we convert those `Component` objects into `Node`. - we add those nodes to `rootNode`. I think we can simplify this logic to a single loop on **all** the children of `rootComponent`. Maybe we could treat filters differently, but not necessarily. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultAppenderRefComponentBuilder.java: ########## @@ -16,24 +16,40 @@ */ package org.apache.logging.log4j.core.config.builder.impl; +import org.apache.logging.log4j.core.config.AppenderRef; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder; +import org.osgi.annotation.versioning.ProviderType; /** - * Holds the Appender Component attributes and subcomponents. + * A default implementation of the {@link AppenderRefComponentBuilder} interface for building + * an {@link AppenderRef} component for a Log4j configuration. + * + * <p> + * Note: This builder is not thread-safe. Instances should not be shared between threads. + * </p> * * @since 2.4 */ +@ProviderType Review Comment: `@ProviderType` makes sense on API classes, but not much on implementations: no one is expected to extend the implementations and they don't contain abstract methods (which is the only component treated differently by `@ConsumerType` and `@ProviderType`. ########## log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java: ########## @@ -430,42 +429,42 @@ void testBuilder() { @Test void testRolling() { - final ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + final ConfigurationBuilder<?> builder = ConfigurationBuilderFactory.newConfigurationBuilder(); builder.setStatusLevel(Level.ERROR); builder.setConfigurationName("RollingBuilder"); // create the console appender AppenderComponentBuilder appenderBuilder = - builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); + builder.newAppender("Stdout", "CONSOLE").setAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); appenderBuilder.add( - builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); + builder.newLayout("PatternLayout").setAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); builder.add(appenderBuilder); final LayoutComponentBuilder layoutBuilder = - builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n"); - final ComponentBuilder triggeringPolicy = builder.newComponent("Policies") - .addComponent(builder.newComponent("CronTriggeringPolicy").addAttribute("schedule", "0 0 0 * * ?")) - .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "100M")); + builder.newLayout("PatternLayout").setAttribute("pattern", "%d [%t] %-5level: %msg%n"); + final ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies") + .addComponent(builder.newComponent("CronTriggeringPolicy").setAttribute("schedule", "0 0 0 * * ?")) + .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").setAttribute("size", "100M")); appenderBuilder = builder.newAppender("rolling", "RollingFile") - .addAttribute("fileName", "target/rolling.log") - .addAttribute("filePattern", "target/archive/rolling-%d{MM-dd-yy}.log.gz") + .setAttribute("fileName", "target/rolling.log") + .setAttribute("filePattern", "target/archive/rolling-%d{MM-dd-yy}.log.gz") .add(layoutBuilder) .addComponent(triggeringPolicy); builder.add(appenderBuilder); // create the new logger builder.add(builder.newLogger("TestLogger", Level.DEBUG) .add(builder.newAppenderRef("rolling")) - .addAttribute("additivity", false)); + .setAdditivityAttribute(false)); builder.add(builder.newRootLogger(Level.DEBUG).add(builder.newAppenderRef("rolling"))); final Configuration config = builder.build(); config.initialize(); assertNotNull(config.getAppender("rolling"), "No rolling file appender"); assertEquals("RollingBuilder", config.getName(), "Unexpected Configuration"); // Initialize the new configuration - final LoggerContext ctx = Configurator.initialize(config); - Configurator.shutdown(ctx); + final LoggerContext tCtx = Configurator.initialize(config); + Configurator.shutdown(tCtx); Review Comment: I don't really see the purpose of this refactoring. ########## log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java: ########## @@ -80,8 +81,9 @@ void testPatternAndParameterizedMessageDateLookup() { @Test void testDefaultDisabledLookup() { - final Configuration config = - new DefaultConfigurationBuilder().addProperty("foo", "bar").build(true); + final ConfigurationBuilder<?> configurationBuilder = ConfigurationBuilderFactory.newConfigurationBuilder(); + configurationBuilder.add(configurationBuilder.newProperty("foo", "bar")); + final Configuration config = configurationBuilder.build(true); Review Comment: Should we profit from the fluent API here? ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java: ########## @@ -17,104 +17,217 @@ package org.apache.logging.log4j.core.config.builder.impl; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.builder.api.Component; import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** - * Generic component that captures attributes and Components in preparation for assembling the Appender's - * Component. + * Generic base default {@link ComponentBuilder} implementation that captures attributes and children + * which are used to build a new {@link Component} instance. * + * @param <T> the type of the component builder + * @param <CB> the type of the configuration builder * @since 2.4 */ +@ProviderType class DefaultComponentBuilder<T extends ComponentBuilder<T>, CB extends ConfigurationBuilder<? extends Configuration>> implements ComponentBuilder<T> { private final CB builder; - private final String type; + private final String pluginType; private final Map<String, String> attributes = new LinkedHashMap<>(); private final List<Component> components = new ArrayList<>(); - private final String name; - private final String value; - - public DefaultComponentBuilder(final CB builder, final String type) { - this(builder, null, type, null); + private final @Nullable String name; + private final @Nullable String value; + + /** + * Constructs a new instance with the given configuration builder and plugin-type with {@code null} name and value. + * @param builder the configuration builder + * @param pluginType the plugin-type of the component being built + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder(final CB builder, final String pluginType) { + this(builder, pluginType, null, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type) { - this(builder, name, type, null); + /** + * Constructs a new instancer with the given configuration builder, plugin-type, name, and {@code null} value. + * @param builder the configuration builder + * @param pluginType the plugin-type of the component being built + * @param name the component name + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder(final CB builder, final String pluginType, final @Nullable String name) { + this(builder, pluginType, name, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type, final String value) { - this.type = type; - this.builder = builder; + /** + * Constructs a new instance with the given configuration builder, plugin-type, name and value. + * @param builder the configuration builder + * @param pluginType the type (plugin-type) of the component being built + * @param name the component name + * @param value the component value + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder( + final CB builder, final String pluginType, final @Nullable String name, final @Nullable String value) { + super(); + this.builder = Objects.requireNonNull(builder, "The 'builder' argument must not be null."); + this.pluginType = Objects.requireNonNull(pluginType, "The 'type' argument must not be null."); this.name = name; this.value = value; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final boolean value) { - return put(key, Boolean.toString(value)); + public Component build() { + final Component component = new Component(pluginType, name, value); + component.getAttributes().putAll(attributes); + component.getComponents().addAll(components); + return component; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Enum<?> value) { - return put(key, value.name()); + public T addComponent(final ComponentBuilder<?> builder) { + Objects.requireNonNull(builder, "The 'builder' argument must not be null."); + components.add(builder.build()); + return self(); } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final int value) { - return put(key, Integer.toString(value)); + public @NonNull CB getBuilder() { + return builder; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Level level) { - return put(key, level.toString()); + public @Nullable String getName() { + return name; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Object value) { - return put(key, value.toString()); + public T setAttribute(final String key, final boolean value) { + return putAttribute(key, Boolean.toString(value)); } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final String value) { - return put(key, value); + public T setAttribute(final String key, final int value) { + return putAttribute(key, Integer.toString(value)); } + /** {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - public T addComponent(final ComponentBuilder<?> builder) { - components.add(builder.build()); - return (T) this; + public T setAttribute(final String key, final @Nullable Enum<?> value) { + return putAttribute(key, (value != null) ? value.name() : null); } + /** {@inheritDoc} */ @Override - public Component build() { - final Component component = new Component(type, name, value); - component.getAttributes().putAll(attributes); - component.getComponents().addAll(components); - return component; + public T setAttribute(final String key, final @Nullable Level level) { + return putAttribute(key, (level != null) ? level.toString() : null); } + /** {@inheritDoc} */ @Override - public CB getBuilder() { - return builder; + public T setAttribute(final String key, final @Nullable Object value) { + return putAttribute(key, Objects.toString(value, null)); } + /** {@inheritDoc} */ @Override - public String getName() { - return name; + public T setAttribute(final String key, final @Nullable String value) { + return putAttribute(key, value); + } + + /** + * Clears the internal state removing all configured attributes and components. + * <p> + * This method is primarily intended to be used in testing. + * </p> + */ + protected void clear() { + synchronized (this) { + attributes.clear(); + components.clear(); + } Review Comment: Since the class is not thread-safe anyway, do we need this? ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java: ########## @@ -16,430 +16,587 @@ */ package org.apache.logging.log4j.core.config.builder.api; +import aQute.bnd.annotation.baseline.BaselineIgnore; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.async.AsyncLogger; +import org.apache.logging.log4j.core.config.AppenderRef; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.CustomLevels; +import org.apache.logging.log4j.core.config.LoggerConfig.RootLogger; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.script.Script; +import org.apache.logging.log4j.core.script.ScriptFile; import org.apache.logging.log4j.core.util.Builder; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** * Interface for building logging configurations. * @param <T> The Configuration type created by this builder. * @since 2.4 */ +@ProviderType public interface ConfigurationBuilder<T extends Configuration> extends Builder<T> { /** - * Adds a ScriptComponent. - * @param builder The ScriptComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Script} component built by the given {@code ScriptComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code ScriptComponentBuilder} to add with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(ScriptComponentBuilder builder); /** - * Adds a ScriptFileComponent. - * @param builder The ScriptFileComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link ScriptFile} component built by the given {@code ScriptFileComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code ScriptFileComponentBuilder} to add with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(ScriptFileComponentBuilder builder); /** - * Adds an AppenderComponent. - * @param builder The AppenderComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Appender} component built by the given {@code AppenderComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code AppenderComponentBuilder} to add with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(AppenderComponentBuilder builder); /** - * Adds a CustomLevel component. - * @param builder The CustomLevelComponentBuilder with all of its attributes set. - * @return this builder instance. + * Adds the {@link CustomLevels} component built by the given {@code CustomLevelComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code CustomLevelComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(CustomLevelComponentBuilder builder); /** - * Adds a Filter component. - * @param builder the FilterComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Filter} component built by the given {@code FilterComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code FilterComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(FilterComponentBuilder builder); /** - * Adds a Logger component. - * @param builder The LoggerComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link Logger} component built by the given {@code LoggerComponentBuilder} to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code LoggerComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(LoggerComponentBuilder builder); /** - * Adds the root Logger component. - * @param builder The RootLoggerComponentBuilder with all of its attributes and sub components set. - * @return this builder instance. + * Adds the {@link RootLogger} component built by the given {@code RootLoggerComponentBuilder} + * to this builder. + * <p> + * Note: the provided {@code builder} will be built by this method; therefore, it must be fully configured + * <i>before</i> calling this method. Changes to the builder after calling this method will not have + * any effect. + * </p> + * + * @param builder The {@code RootLoggerComponentBuilder} with all of its attributes and subcomponents set. + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} */ ConfigurationBuilder<T> add(RootLoggerComponentBuilder builder); /** - * Adds a Property key and value. - * @param key The property key. - * @param value The property value. - * @return this builder instance. + * Adds a the {@link Property} component built by the given {@link PropertyComponentBuilder}. + * @param builder the {@code PropertyComponentBuilder} to add + * @return this builder (for chaining) */ - ConfigurationBuilder<T> addProperty(String key, String value); + ConfigurationBuilder<T> add(PropertyComponentBuilder builder); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @param language The script language - * @param text The script to execute. - * @return A new ScriptComponentBuilder. + * Adds a {@link Property} component with the given {@code key} and {@code value}. + * + * @param name the property name + * @param value the property value + * @return this builder (for chaining) + * @throws NullPointerException if the given {@code builder} is {@code null} + * @deprecated use {@link #add(PropertyComponentBuilder)} */ - ScriptComponentBuilder newScript(String name, String language, String text); + @Deprecated + ConfigurationBuilder<T> addProperty(@Nullable String name, @Nullable String value); /** - * Returns a builder for creating Async Loggers. - * @param path The location of the script file. - * @return A new ScriptFileComponentBuilder. + * Returns a {@link ScriptComponentBuilder} for creating a {@link Script} component. + * @param name the script name + * @param language the script language + * @param text the script to execute + * @return the new component builder instance */ - ScriptFileComponentBuilder newScriptFile(String path); + ScriptComponentBuilder newScript(@Nullable String name, @Nullable String language, @Nullable String text); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the script file. - * @param path The location of the script file. - * @return A new ScriptFileComponentBuilder. + * Returns a {@link ScriptFileComponentBuilder} for creating a {@link ScriptFile} component. + * @param path the script file path + * @return the new component builder instance */ - ScriptFileComponentBuilder newScriptFile(String name, String path); + ScriptFileComponentBuilder newScriptFile(@Nullable String path); /** - * Returns a builder for creating Appenders. - * @param name The name of the Appender. - * @param pluginName The Plugin type of the Appender. - * @return A new AppenderComponentBuilder. + * Returns a {@link ScriptFileComponentBuilder} for creating a {@link ScriptFile} component. + * @param name the script file name + * @param path the script file path + * @return the new component builder instance */ - AppenderComponentBuilder newAppender(String name, String pluginName); + ScriptFileComponentBuilder newScriptFile(@Nullable String name, @Nullable String path); /** - * Returns a builder for creating AppenderRefs. - * @param ref The name of the Appender being referenced. - * @return A new AppenderRefComponentBuilder. + * Returns a {@link AppenderComponentBuilder} for creating an {@link Appender} component. + * @param name the appender name + * @param pluginName the appender plugin-type + * @return the new component builder instance */ - AppenderRefComponentBuilder newAppenderRef(String ref); + AppenderComponentBuilder newAppender(@Nullable String name, String pluginName); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @return A new LoggerComponentBuilder. + * Returns a {@link AppenderRefComponentBuilder} for creating an {@link AppenderRef} component. + * @param ref the name of the {@code Appender} being referenced + * @return the new component builder instance */ - LoggerComponentBuilder newAsyncLogger(String name); + AppenderRefComponentBuilder newAppenderRef(@Nullable String ref); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @param includeLocation If true include location information. - * @return A new LoggerComponentBuilder. + * Returns a {@link LoggerComponentBuilder} builder for creating an {@link AsyncLogger} component. + * @param name the logger name + * @return the new component builder instance */ - LoggerComponentBuilder newAsyncLogger(String name, boolean includeLocation); + LoggerComponentBuilder newAsyncLogger(@Nullable String name); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @return A new LoggerComponentBuilder. + * Returns a {@link LoggerComponentBuilder} for creating an {@link AsyncLogger} component. + * @param name the logger name + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - LoggerComponentBuilder newAsyncLogger(String name, Level level); + LoggerComponentBuilder newAsyncLogger(@Nullable String name, boolean includeLocation); /** - * Returns a builder for creating Async Loggers. + * Returns a {@link LoggerComponentBuilder} for creating an {@link AsyncLogger} component. * @param name The name of the Logger. * @param level The logging Level to be assigned to the Logger. - * @param includeLocation If true include location information. * @return A new LoggerComponentBuilder. */ - LoggerComponentBuilder newAsyncLogger(String name, Level level, boolean includeLocation); + LoggerComponentBuilder newAsyncLogger(@Nullable String name, @Nullable Level level); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @return A new LoggerComponentBuilder. + * Returns a {@link LoggerComponentBuilder} for creating an {@link AsyncLogger} component. + * @param name the logger name + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - LoggerComponentBuilder newAsyncLogger(String name, String level); + LoggerComponentBuilder newAsyncLogger(@Nullable String name, @Nullable Level level, boolean includeLocation); /** - * Returns a builder for creating Async Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @param includeLocation If true include location information. - * @return A new LoggerComponentBuilder. + * Returns a {@link LoggerComponentBuilder} for creating an {@link AsyncLogger} component + * @param name the logger name + * @param level the logger level + * @return the new component builder instance */ - LoggerComponentBuilder newAsyncLogger(String name, String level, boolean includeLocation); + LoggerComponentBuilder newAsyncLogger(@Nullable String name, @Nullable String level); /** - * Returns a builder for creating the async root Logger. - * @return A new RootLoggerComponentBuilder. + * Returns a {@link LoggerComponentBuilder} for creating an {@link AsyncLogger} component. + * @param name the logger name + * @param level the logger level + * @param includeLocation {@code true} to include location inforrmation; otherwise, {@code false} + * @return the new component builder instance + */ + LoggerComponentBuilder newAsyncLogger(@Nullable String name, @Nullable String level, boolean includeLocation); + + /** + * Returns a {@link RootLoggerComponentBuilder} for creating a root {@link AsyncLogger} component. + * @return the new component builder instance */ RootLoggerComponentBuilder newAsyncRootLogger(); /** - * Returns a builder for creating the async root Logger. - * @param includeLocation If true include location information. - * @return A new RootLoggerComponentBuilder. + * Returns a {@link RootLoggerComponentBuilder} for creating a root {@link AsyncLogger} component. + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ RootLoggerComponentBuilder newAsyncRootLogger(boolean includeLocation); /** - * Returns a builder for creating the async root Logger. - * @param level The logging Level to be assigned to the root Logger. - * @return A new RootLoggerComponentBuilder. + * Returns a {@link RootLoggerComponentBuilder} for creating a root {@link AsyncLogger} component. + * @param level the logger level + * @return the new component builder instance + */ + RootLoggerComponentBuilder newAsyncRootLogger(@Nullable Level level); + + /** + * Returns a {@link RootLoggerComponentBuilder} for creating a root {@link AsyncLogger} component. + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - RootLoggerComponentBuilder newAsyncRootLogger(Level level); + RootLoggerComponentBuilder newAsyncRootLogger(@Nullable Level level, boolean includeLocation); /** - * Returns a builder for creating the async root Logger. - * @param level The logging Level to be assigned to the root Logger. - * @param includeLocation If true include location information. - * @return A new RootLoggerComponentBuilder. + * Returns a {@link RootLoggerComponentBuilder} for creating a root {@link AsyncLogger} component. + * @param level the logger level + * @return the new component builder instance */ - RootLoggerComponentBuilder newAsyncRootLogger(Level level, boolean includeLocation); + RootLoggerComponentBuilder newAsyncRootLogger(@Nullable String level); /** - * Returns a builder for creating the async root Logger. - * @param level The logging Level to be assigned to the root Logger. - * @return A new RootLoggerComponentBuilder. + * Returns a {@link RootLoggerComponentBuilder} for creating a root {@link AsyncLogger} component. + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - RootLoggerComponentBuilder newAsyncRootLogger(String level); + RootLoggerComponentBuilder newAsyncRootLogger(@Nullable String level, boolean includeLocation); /** - * Returns a builder for creating the async root Logger. - * @param level The logging Level to be assigned to the root Logger. - * @param includeLocation If true include location information. - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link ComponentBuilder} for creating a generic {@link Component}. + * @param <B> the {@code ComponentBuilder} target type + * @param pluginType the component plugin type + * @return the new component builder instance */ - RootLoggerComponentBuilder newAsyncRootLogger(String level, boolean includeLocation); + <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(String pluginType); /** - * Returns a builder for creating generic components. - * @param <B> ComponentBuilder target type - * @param pluginName The Plugin type of the component. - * @return A new ComponentBuilder. + * Returns a new {@link ComponentBuilder} for creating a generic {@link Component}. + * @param <B> the {@code ComponentBuilder} target type + * @param name the component name + * @param pluginType the component plugin type + * @return the new component builder instance */ - <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(String pluginName); + <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(@Nullable String name, String pluginType); /** - * Returns a builder for creating generic components. - * @param <B> ComponentBuilder target type - * @param name The name of the component (may be null). - * @param pluginName The Plugin type of the component. - * @return A new ComponentBuilder. + * Returns a new {@link ComponentBuilder} for creating a generic {@link Component}. + * @param <B> the {@code ComponentBuilder} target type + * @param name the component name + * @param pluginType the component plugin type + * @param value the component value + * @return the new component builder instance */ - <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(String name, String pluginName); + <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent( + @Nullable String name, String pluginType, @Nullable String value); /** - * Returns a builder for creating generic components. - * @param <B> ComponentBuilder target type - * @param name The name of the component (may be null). - * @param pluginName The Plugin type of the component. - * @param value The value of the component. - * @return A new ComponentBuilder. + * Returns a new {@link PropertyComponentBuilder} for creating a {@link Property} component. + * @param name the property name + * @param value the property value + * @return the new component builder instance */ - <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(String name, String pluginName, String value); + PropertyComponentBuilder newProperty(@Nullable String name, @Nullable String value); /** - * Returns a builder for creating Property:s - * @param name The name of the property. - * @param value The value of the component. - * @return A new PropertyComponentBuilder. + * Returns a new {@link PropertyComponentBuilder} for creating a {@link KeyValuePair} component. + * @param key the key + * @param value the value + * @return the new component builder instance */ - PropertyComponentBuilder newProperty(String name, String value); + KeyValuePairComponentBuilder newKeyValuePair(@Nullable String key, @Nullable String value); /** - * Returns a builder for creating KeyValuePair:s - * @param key The name - * @param value The value - * @return A new KeyValuePairComponentBuilder. + * Returns a new {@link CustomLevelComponentBuilder} for creating a {@link CustomLevels} component. + * @param name the custom level name + * @param intLevel the integer value to be assigned to the level + * @return the new component builder instance */ - KeyValuePairComponentBuilder newKeyValuePair(String key, String value); + CustomLevelComponentBuilder newCustomLevel(@Nullable String name, int intLevel); /** - * Returns a builder for creating CustomLevels - * @param name The name of the custom level. - * @param level The integer value to be assigned to the level. - * @return A new CustomLevelComponentBuilder. + * Returns a new {@link FilterComponentBuilder} for creating a {@link Filter} component. + * @param pluginType the plugin type of the filter + * @return the new component builder instance */ - CustomLevelComponentBuilder newCustomLevel(String name, int level); + FilterComponentBuilder newFilter(String pluginType); /** - * Returns a builder for creating Filters. - * @param pluginName The Plugin type of the Filter. + * Returns a new {@link FilterComponentBuilder} for creating a {@code Filter} component. + * @param pluginType The Plugin type of the Filter. * @param onMatch "ACCEPT", "DENY", or "NEUTRAL" * @param onMismatch "ACCEPT", "DENY", or "NEUTRAL" - * @return A new FilterComponentBuilder. + * @return the new component builder instance */ - FilterComponentBuilder newFilter(String pluginName, Filter.Result onMatch, Filter.Result onMismatch); + FilterComponentBuilder newFilter(String pluginType, @Nullable Result onMatch, @Nullable Result onMismatch); /** - * Returns a builder for creating Filters. - * @param pluginName The Plugin type of the Filter. + * Returns a new {@link FilterComponentBuilder} for creating a {@link Filter} component. + * @param pluginType the plugin type of the filter * @param onMatch "ACCEPT", "DENY", or "NEUTRAL" * @param onMismatch "ACCEPT", "DENY", or "NEUTRAL" - * @return A new FilterComponentBuilder. + * @return the new component builder instance */ - FilterComponentBuilder newFilter(String pluginName, String onMatch, String onMismatch); + FilterComponentBuilder newFilter(String pluginType, @Nullable String onMatch, @Nullable String onMismatch); /** - * Returns a builder for creating Layouts. - * @param pluginName The Plugin type of the Layout. - * @return A new LayoutComponentBuilder. + * Returns a new {@link LayoutComponentBuilder} for creating a {@link Layout} component. + * @param pluginType the plugin type of the layout + * @return the new component builder instance */ - LayoutComponentBuilder newLayout(String pluginName); + LayoutComponentBuilder newLayout(String pluginType); /** - * Returns a builder for creating Loggers. - * @param name The name of the Logger. - * @return A new LoggerComponentBuilder. + * Returns a new {@link LayoutComponentBuilder} for creating a {@link Layout} component. + * @param name the logger name + * @return the new component builder instance */ - LoggerComponentBuilder newLogger(String name); + LoggerComponentBuilder newLogger(@Nullable String name); /** - * Returns a builder for creating Loggers. - * @param name The name of the Logger. - * @param includeLocation If true include location information. - * @return A new LoggerComponentBuilder. + * Returns a new {@link LoggerComponentBuilder} for creating a {@link Logger} component. + * @param name the logger name + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - LoggerComponentBuilder newLogger(String name, boolean includeLocation); + LoggerComponentBuilder newLogger(@Nullable String name, boolean includeLocation); /** - * Returns a builder for creating Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @return A new LoggerComponentBuilder. + * Returns a new {@link LoggerComponentBuilder} for creating a {@link Logger} component. + * @param name the logger name + * @param level the logger level + * @return the new component builder instance */ - LoggerComponentBuilder newLogger(String name, Level level); + LoggerComponentBuilder newLogger(@Nullable String name, @Nullable Level level); /** - * Returns a builder for creating Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @param includeLocation If true include location information. - * @return A new LoggerComponentBuilder. + * Returns a new {@link LoggerComponentBuilder} for creating a {@link Logger} component. + * @param name the logger name + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - LoggerComponentBuilder newLogger(String name, Level level, boolean includeLocation); + LoggerComponentBuilder newLogger(@Nullable String name, @Nullable Level level, boolean includeLocation); /** - * Returns a builder for creating Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @return A new LoggerComponentBuilder. + * Returns a new {@link LoggerComponentBuilder} for creating a {@link Logger} component. + * @param name the logger name + * @param level the logger level + * @return the new component builder instance */ - LoggerComponentBuilder newLogger(String name, String level); + LoggerComponentBuilder newLogger(@Nullable String name, @Nullable String level); /** - * Returns a builder for creating Loggers. - * @param name The name of the Logger. - * @param level The logging Level to be assigned to the Logger. - * @param includeLocation If true include location information. - * @return A new LoggerComponentBuilder. + * Returns a new {@link LoggerComponentBuilder} for creating a {@link Logger} component. + * @param name the logger name + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - LoggerComponentBuilder newLogger(String name, String level, boolean includeLocation); + LoggerComponentBuilder newLogger(@Nullable String name, @Nullable String level, boolean includeLocation); /** - * Returns a builder for creating the root Logger. - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link RootLoggerComponentBuilder} for creating a {@link RootLogger} component. + * @return the new component builder instance */ RootLoggerComponentBuilder newRootLogger(); /** - * Returns a builder for creating the root Logger. - * @param includeLocation If true include location information. - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link RootLoggerComponentBuilder} for creating a {@link RootLogger} component. + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instancec */ RootLoggerComponentBuilder newRootLogger(boolean includeLocation); /** - * Returns a builder for creating the root Logger. - * @param level The logging Level to be assigned to the root Logger. - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link RootLoggerComponentBuilder} for creating a {@link RootLogger} component. + * @param level the logger level + * @return the new component builder instance */ - RootLoggerComponentBuilder newRootLogger(Level level); + RootLoggerComponentBuilder newRootLogger(@Nullable Level level); /** - * Returns a builder for creating the root Logger. - * @param level The logging Level to be assigned to the root Logger. - * @param includeLocation If true include location information. - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link RootLoggerComponentBuilder} for creating a {@link RootLogger} component. + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - RootLoggerComponentBuilder newRootLogger(Level level, boolean includeLocation); + RootLoggerComponentBuilder newRootLogger(@Nullable Level level, boolean includeLocation); /** - * Returns a builder for creating the root Logger. - * @param level The logging Level to be assigned to the root Logger. - * - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link RootLoggerComponentBuilder} for creating a {@link RootLogger} component. + * @param level the logger level + * @return the new component builder instance */ - RootLoggerComponentBuilder newRootLogger(String level); + RootLoggerComponentBuilder newRootLogger(@Nullable String level); /** - * Returns a builder for creating the root Logger. - * @param level The logging Level to be assigned to the root Logger. - * - * @return A new RootLoggerComponentBuilder. + * Returns a new {@link RootLoggerComponentBuilder} for creating a {@link RootLogger} component. + * @param level the logger level + * @param includeLocation {@code true} to include location information; otherwise, {@code false} + * @return the new component builder instance */ - RootLoggerComponentBuilder newRootLogger(String level, boolean includeLocation); + RootLoggerComponentBuilder newRootLogger(@Nullable String level, boolean includeLocation); /** * Set the Advertiser Plugin name. * @param advertiser The Advertiser Plugin name. * @return this builder instance. */ - ConfigurationBuilder<T> setAdvertiser(String advertiser); + ConfigurationBuilder<T> setAdvertiser(@Nullable String advertiser); /** * Sets the name of the configuration. * @param name the name of the {@link Configuration}. By default is {@code "Constructed"}. * @return this builder instance. */ - ConfigurationBuilder<T> setConfigurationName(String name); + ConfigurationBuilder<T> setConfigurationName(@Nullable String name); /** - * Sets the configuration source, if one exists. - * @param configurationSource the ConfigurationSource. - * @return this builder instance. + * Sets the configuration source. + * @param configurationSource the configuration source + * @return this builder instance (for chaining) */ - ConfigurationBuilder<T> setConfigurationSource(ConfigurationSource configurationSource); + ConfigurationBuilder<T> setConfigurationSource(@Nullable ConfigurationSource configurationSource); /** - * Sets the interval at which the configuration file should be checked for changes. - * @param intervalSeconds The number of seconds that should pass between checks of the configuration file. + * Specifies the destination for StatusLogger events. This can be {@code out} (default) for using + * {@link System#out standard out}, {@code err} for using {@link System#err standard error}, or a file URI to + * which log events will be written. If the provided URI is invalid, then the default destination of standard + * out will be used. + * + * @param destination where status log messages should be output. * @return this builder instance. */ - ConfigurationBuilder<T> setMonitorInterval(String intervalSeconds); + ConfigurationBuilder<T> setDestination(@Nullable String destination); /** - * Sets the list of packages to search for plugins. - * @param packages The comma separated list of packages. - * @return this builder instance. + * Sets the logger context. + * @param loggerContext the logger context. */ - ConfigurationBuilder<T> setPackages(String packages); + @BaselineIgnore("2.25.0") + ConfigurationBuilder<T> setLoggerContext(@Nullable LoggerContext loggerContext); /** - * Sets whether the shutdown hook should be disabled. - * @param flag "disable" will prevent the shutdown hook from being set. - * @return this builder instance. + * Sets the configuration's "monitorInterval" attribute. + * <p> + * The monitor interval specifies the number of seconds between checks for changes to the configuration source. + * </p> + * @param intervalSeconds the number of seconds that should pass between checks of the configuration source + * @return this builder instance */ - ConfigurationBuilder<T> setShutdownHook(String flag); + ConfigurationBuilder<T> setMonitorInterval(int intervalSeconds); /** - * How long appenders and background tasks will get to shutdown when the JVM shuts down. - * Default is zero which mean that each appender uses its default timeout, and don't wait for background - * tasks. Not all appenders will honor this, it is a hint and not an absolute guarantee that the shutdown - * procedure will not take longer. Setting this too low increase the risk of losing outstanding log events - * not yet written to the final destination. (Not used if {@link #setShutdownHook(String)} is set to "disable".) - * @return this builder instance. + * Sets the configuration's "monitorInterval" attribute. + * <p> + * The monitor interval specifies the number of seconds between checks for changes to the configuration source. + * </p> + * @param intervalSeconds the number of seconds that should pass between checks of the configuration source + * @return this builder instance + * @throws NumberFormatException if the {@code intervalSeconds} argument is not a valid integer representation + */ + ConfigurationBuilder<T> setMonitorInterval(String intervalSeconds); + + /** + * Sets the configuration's list of packages to search for Log4j plugins. + * @param packages a comma separated list of packages + * @return this builder (for chaining) + */ + ConfigurationBuilder<T> setPackages(@Nullable String packages); Review Comment: This is deprecated and we could mark it as such. ########## log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultComponentBuilder.java: ########## @@ -17,104 +17,217 @@ package org.apache.logging.log4j.core.config.builder.impl; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.builder.api.Component; import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; /** - * Generic component that captures attributes and Components in preparation for assembling the Appender's - * Component. + * Generic base default {@link ComponentBuilder} implementation that captures attributes and children + * which are used to build a new {@link Component} instance. * + * @param <T> the type of the component builder + * @param <CB> the type of the configuration builder * @since 2.4 */ +@ProviderType class DefaultComponentBuilder<T extends ComponentBuilder<T>, CB extends ConfigurationBuilder<? extends Configuration>> implements ComponentBuilder<T> { private final CB builder; - private final String type; + private final String pluginType; private final Map<String, String> attributes = new LinkedHashMap<>(); private final List<Component> components = new ArrayList<>(); - private final String name; - private final String value; - - public DefaultComponentBuilder(final CB builder, final String type) { - this(builder, null, type, null); + private final @Nullable String name; + private final @Nullable String value; + + /** + * Constructs a new instance with the given configuration builder and plugin-type with {@code null} name and value. + * @param builder the configuration builder + * @param pluginType the plugin-type of the component being built + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder(final CB builder, final String pluginType) { + this(builder, pluginType, null, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type) { - this(builder, name, type, null); + /** + * Constructs a new instancer with the given configuration builder, plugin-type, name, and {@code null} value. + * @param builder the configuration builder + * @param pluginType the plugin-type of the component being built + * @param name the component name + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder(final CB builder, final String pluginType, final @Nullable String name) { + this(builder, pluginType, name, null); } - public DefaultComponentBuilder(final CB builder, final String name, final String type, final String value) { - this.type = type; - this.builder = builder; + /** + * Constructs a new instance with the given configuration builder, plugin-type, name and value. + * @param builder the configuration builder + * @param pluginType the type (plugin-type) of the component being built + * @param name the component name + * @param value the component value + * @throws NullPointerException if either {@code builder} or {@code type} argument is null + */ + public DefaultComponentBuilder( + final CB builder, final String pluginType, final @Nullable String name, final @Nullable String value) { + super(); + this.builder = Objects.requireNonNull(builder, "The 'builder' argument must not be null."); + this.pluginType = Objects.requireNonNull(pluginType, "The 'type' argument must not be null."); this.name = name; this.value = value; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final boolean value) { - return put(key, Boolean.toString(value)); + public Component build() { + final Component component = new Component(pluginType, name, value); + component.getAttributes().putAll(attributes); + component.getComponents().addAll(components); + return component; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Enum<?> value) { - return put(key, value.name()); + public T addComponent(final ComponentBuilder<?> builder) { + Objects.requireNonNull(builder, "The 'builder' argument must not be null."); + components.add(builder.build()); + return self(); } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final int value) { - return put(key, Integer.toString(value)); + public @NonNull CB getBuilder() { + return builder; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Level level) { - return put(key, level.toString()); + public @Nullable String getName() { + return name; } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final Object value) { - return put(key, value.toString()); + public T setAttribute(final String key, final boolean value) { + return putAttribute(key, Boolean.toString(value)); } + /** {@inheritDoc} */ @Override - public T addAttribute(final String key, final String value) { - return put(key, value); + public T setAttribute(final String key, final int value) { + return putAttribute(key, Integer.toString(value)); } + /** {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - public T addComponent(final ComponentBuilder<?> builder) { - components.add(builder.build()); - return (T) this; + public T setAttribute(final String key, final @Nullable Enum<?> value) { + return putAttribute(key, (value != null) ? value.name() : null); } + /** {@inheritDoc} */ @Override - public Component build() { - final Component component = new Component(type, name, value); - component.getAttributes().putAll(attributes); - component.getComponents().addAll(components); - return component; + public T setAttribute(final String key, final @Nullable Level level) { + return putAttribute(key, (level != null) ? level.toString() : null); } + /** {@inheritDoc} */ @Override - public CB getBuilder() { - return builder; + public T setAttribute(final String key, final @Nullable Object value) { + return putAttribute(key, Objects.toString(value, null)); } + /** {@inheritDoc} */ @Override - public String getName() { - return name; + public T setAttribute(final String key, final @Nullable String value) { + return putAttribute(key, value); + } + + /** + * Clears the internal state removing all configured attributes and components. + * <p> + * This method is primarily intended to be used in testing. + * </p> + */ + protected void clear() { + synchronized (this) { + attributes.clear(); + components.clear(); + } + } + + /** + * Gets the value of the component attribute with the given key. + * + * @param key the key + * @return the attribute value or {@code null} if not found + */ + protected @Nullable String getAttribute(final @NonNull String key) { + + Objects.requireNonNull(key, "The 'key' argument must not be null."); + + return this.attributes.get(key); + } + + /** + * Gets the map of key/value component attributes. + * <p> + * The result map is guaranteed to have both non-{@code null} keys and values. + * </p> + * @return an <i>immutable</i> map of the key/value attributes + */ + protected Map<String, String> getAttributes() { + return Collections.unmodifiableMap(attributes); + } + + /** + * Gets the list of child components. + * @return an <i>immutable</i> list of the child components + */ + protected List<Component> getComponents() { Review Comment: ```suggestion protected List<? extends Component> getComponents() { ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: notifications-unsubscr...@logging.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org