diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java
index e46d4cb3ae7..3c500e01af0 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java
@@ -460,7 +460,6 @@ void testRolling() {
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
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java
new file mode 100644
index 00000000000..246ffe2c432
--- /dev/null
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java
@@ -0,0 +1,360 @@
+/*
+ * 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.builder;
+
+import static org.assertj.core.api.Assertions.assertThatCollection;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+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.LoggerContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.CustomLevelConfig;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.api.Component;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
+import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder;
+import org.apache.logging.log4j.core.filter.ThresholdFilter;
+import org.junit.jupiter.api.Test;
+
+class CustomBuiltConfigurationTest {
+
+ private static final String CONFIG_NAME = "FooBar Configuration";
+
+ private static final FooBar FOOBAR = new FooBar("wingding");
+
+ /** Test that the build configuration contains the intended attributes. */
+ @Test
+ void testCustomBuiltConfiguration_Attributes() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+ assertEquals(CONFIG_NAME, config.getName());
+ assertEquals(10, config.getMonitorInterval());
+ assertEquals(5000, config.getShutdownTimeoutMillis());
+ assertThatCollection(config.getPluginPackages()).containsExactlyInAnyOrder("foo", "bar");
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /** Test that the custom constructor of the custom configuration was called and the test object is accessible. */
+ @Test
+ void testCustomBuiltConfiguration_CustomObject() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+
+ final FooBar fb = config.getFooBar();
+ assertNotNull(fb);
+ assertEquals(FOOBAR, fb);
+ assertEquals(FOOBAR.getValue(), fb.getValue());
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /** Test that the build configuration contains the intended appenders. */
+ @Test
+ void testCustomBuiltConfiguration_Appenders() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+ assertThatCollection(config.getAppenders().keySet()).containsExactlyInAnyOrder("Stdout", "Kafka");
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /** Test that the build configuration contains the custom levels. */
+ @Test
+ void testCustomBuiltConfiguration_CustomLevels() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+ assertThatCollection(config.getCustomLevels().stream()
+ .map(CustomLevelConfig::getLevelName)
+ .collect(Collectors.toList()))
+ .containsExactlyInAnyOrder("Panic");
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /** Test that the build configuration contains the intended filter. */
+ @Test
+ void testCustomBuiltConfiguration_Filter() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+ Filter filter = config.getFilter();
+ assertNotNull(filter);
+ assertInstanceOf(ThresholdFilter.class, filter);
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /** Test that the build configuration contains the intended loggers. */
+ @Test
+ void testCustomBuiltConfiguration_Loggers() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+ assertThatCollection(config.getLoggers().keySet())
+ .containsExactlyInAnyOrder("", "org.apache.logging.log4j", "org.apache.logging.log4j.core");
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /** Test that the build configuration correctly registers and resolves properties. */
+ @Test
+ void testCustomBuiltConfiguration_PropertyResolution() {
+
+ final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+ final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+ final FooBarConfiguration config = builder.build(false);
+
+ try {
+
+ // build the configuration and set it in the context to start the configuration
+ loggerContext.setConfiguration(config);
+
+ assertNotNull(config);
+ assertEquals("Wing", config.getConfigurationStrSubstitutor().replace("${P1}"));
+ assertEquals("WingDing", config.getConfigurationStrSubstitutor().replace("${P2}"));
+
+ Appender kafkaAppender = config.getAppender("Kafka");
+ assertNotNull(kafkaAppender);
+ assertInstanceOf(KafkaAppender.class, kafkaAppender);
+ final Property p2Property = Arrays.stream(((KafkaAppender) kafkaAppender).getPropertyArray())
+ .collect(Collectors.toMap(Property::getName, Function.identity()))
+ .get("P2");
+ assertNotNull(p2Property);
+ assertEquals("WingDing", p2Property.getValue());
+
+ } finally {
+
+ loggerContext.stop();
+ }
+ }
+
+ /**
+ * Creates, preconfigures and returns a test builder instance.
+ * @param loggerContext the logger context to use
+ * @return the created configuration builder
+ */
+ private FoobarConfigurationBuilder createTestBuilder(final LoggerContext loggerContext) {
+ final FoobarConfigurationBuilder builder = new FoobarConfigurationBuilder();
+ builder.setLoggerContext(loggerContext);
+ addTestFixtures(builder);
+ return builder;
+ }
+
+ /**
+ * Populates the given configuration-builder.
+ * @param builder the builder
+ */
+ private void addTestFixtures(final ConfigurationBuilder extends BuiltConfiguration> builder) {
+ builder.setConfigurationName(CONFIG_NAME);
+ builder.setStatusLevel(Level.ERROR);
+ builder.setMonitorInterval(10);
+ builder.setShutdownTimeout(5000, TimeUnit.MILLISECONDS);
+ builder.add(builder.newScriptFile("target/test-classes/scripts/filter.groovy")
+ .addIsWatched(true));
+ builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
+ .addAttribute("level", Level.DEBUG));
+
+ final AppenderComponentBuilder appenderBuilder =
+ builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
+ appenderBuilder.add(
+ builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
+ appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
+ .addAttribute("marker", "FLOW"));
+ builder.add(appenderBuilder);
+
+ final AppenderComponentBuilder appenderBuilder2 =
+ builder.newAppender("Kafka", "Kafka").addAttribute("topic", "my-topic");
+ appenderBuilder2.addComponent(builder.newProperty("bootstrap.servers", "localhost:9092"));
+ appenderBuilder2.addComponent(builder.newProperty("P2", "${P2}"));
+ appenderBuilder2.add(builder.newLayout("GelfLayout")
+ .addAttribute("host", "my-host")
+ .addComponent(builder.newKeyValuePair("extraField", "extraValue")));
+ builder.add(appenderBuilder2);
+
+ builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG, true)
+ .add(builder.newAppenderRef("Stdout"))
+ .addAttribute("additivity", false));
+ builder.add(builder.newLogger("org.apache.logging.log4j.core").add(builder.newAppenderRef("Stdout")));
+ builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
+
+ builder.addProperty("P1", "Wing");
+ builder.addProperty("P2", "${P1}Ding");
+ builder.add(builder.newCustomLevel("Panic", 17));
+ builder.setPackages("foo,bar");
+ }
+
+ //
+ // Test implementations
+ //
+
+ /** A custom {@link DefaultConfigurationBuilder} implementation that generates a {@link FooBarConfiguration}. */
+ public static class FoobarConfigurationBuilder extends DefaultConfigurationBuilder {
+
+ public FoobarConfigurationBuilder() {
+ super(FooBarConfiguration.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected FooBarConfiguration createNewConfigurationInstance(Class configurationClass) {
+ Objects.requireNonNull(configurationClass, "The 'configurationClass' argument must not be null.");
+ try {
+ final Constructor constructor = FooBarConfiguration.class.getConstructor(
+ LoggerContext.class, ConfigurationSource.class, Component.class, FooBar.class);
+ return constructor.newInstance(
+ getLoggerContext().orElse(null),
+ getConfigurationSource().orElse(null),
+ getRootComponent(),
+ FOOBAR);
+ } catch (final Exception ex) {
+ throw new IllegalStateException(
+ "Configuration class '" + configurationClass.getName() + "' cannot be instantiated.", ex);
+ }
+ }
+ }
+
+ /**
+ * A custom {@link BuiltConfiguration} implementation with a custom constructor that takes
+ * an additional {@code FooBar} argument.
+ */
+ public static class FooBarConfiguration extends BuiltConfiguration {
+
+ private int monitorInterval;
+
+ private final FooBar fooBar;
+
+ public FooBarConfiguration(
+ LoggerContext loggerContext, ConfigurationSource source, Component rootComponent, FooBar fooBar) {
+ super(loggerContext, source, rootComponent);
+ this.fooBar = Objects.requireNonNull(fooBar, "fooBar");
+ }
+
+ public FooBar getFooBar() {
+ return fooBar;
+ }
+
+ public int getMonitorInterval() {
+ return this.monitorInterval;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setMonitorInterval(int seconds) {
+ super.setMonitorInterval(seconds);
+ this.monitorInterval = seconds;
+ }
+ }
+
+ /** Test object used by custom configuration-builder and configuration. */
+ public static class FooBar {
+
+ private final String value;
+
+ public FooBar(final String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
index 3b9fc489614..b8c174f39e5 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
@@ -23,34 +23,43 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.util.Builder;
+import org.osgi.annotation.versioning.ProviderType;
/**
* Interface for building logging configurations.
* @param The Configuration type created by this builder.
* @since 2.4
*/
+@ProviderType
public interface ConfigurationBuilder extends Builder {
/**
* Adds a ScriptComponent.
- * @param builder The ScriptComponentBuilder with all of its attributes and sub components set.
+ * @param builder The ScriptComponentBuilder with all of its attributes and subcomponents set.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder add(ScriptComponentBuilder builder);
/**
* Adds a ScriptFileComponent.
- * @param builder The ScriptFileComponentBuilder with all of its attributes and sub components set.
+ * @param builder The ScriptFileComponentBuilder with all of its attributes and subcomponents set.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder add(ScriptFileComponentBuilder builder);
/**
* Adds an AppenderComponent.
- * @param builder The AppenderComponentBuilder with all of its attributes and sub components set.
+ * @param builder The AppenderComponentBuilder with all of its attributes and subcomponents set.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder add(AppenderComponentBuilder builder);
@@ -58,38 +67,61 @@ public interface ConfigurationBuilder extends Builder add(CustomLevelComponentBuilder builder);
/**
* Adds a Filter component.
- * @param builder the FilterComponentBuilder with all of its attributes and sub components set.
+ * @param builder the FilterComponentBuilder with all of its attributes and subcomponents set.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder add(FilterComponentBuilder builder);
/**
* Adds a Logger component.
- * @param builder The LoggerComponentBuilder with all of its attributes and sub components set.
+ * @param builder The LoggerComponentBuilder with all of its attributes and subcomponents set.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder add(LoggerComponentBuilder builder);
/**
* Adds the root Logger component.
- * @param builder The RootLoggerComponentBuilder with all of its attributes and sub components set.
+ * @param builder The RootLoggerComponentBuilder with all of its attributes and subcomponents set.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder add(RootLoggerComponentBuilder builder);
/**
* Adds a Property key and value.
+ *
+ * This is a convenience method which creates, configures and immediately adds
+ * a {@code PropertyComponentBuilder}.
+ *
* @param key The property key.
* @param value The property value.
* @return this builder instance.
+ * @throws ConfigurationException if an error occurs while building the created {@code PropertyComponentBuilder}
+ * @throws NullPointerException if the builder argument is null
*/
ConfigurationBuilder addProperty(String key, String value);
+ /**
+ * Adds a Property component.
+ * @param builder the PropertyComponentBuilder with all of its attributes and subcomponents set
+ * @return this builder instance
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if the builder argument is null
+ */
+ ConfigurationBuilder addProperty(PropertyComponentBuilder builder);
+
/**
* Returns a builder for creating Async Loggers.
* @param name The name of the Logger.
@@ -392,6 +424,9 @@ public interface ConfigurationBuilder extends Builder
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
* @param advertiser The Advertiser Plugin name.
* @return this builder instance.
*/
@@ -399,13 +434,19 @@ public interface ConfigurationBuilder extends Builder
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
+ * @param name the name of the {@link Configuration}. By default, the value is {@code "Built"}.
* @return this builder instance.
*/
ConfigurationBuilder setConfigurationName(String name);
/**
* Sets the configuration source, if one exists.
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
* @param configurationSource the ConfigurationSource.
* @return this builder instance.
*/
@@ -413,13 +454,29 @@ public interface ConfigurationBuilder extends Builder
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
* @param intervalSeconds The number of seconds that should pass between checks of the configuration file.
* @return this builder instance.
*/
ConfigurationBuilder setMonitorInterval(String intervalSeconds);
+ /**
+ * Sets the interval at which the configuration file should be checked for changes.
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
+ * @param intervalSeconds the number of seconds that should pass between checks of the configuraion file
+ * @return this builder instance.
+ */
+ ConfigurationBuilder setMonitorInterval(int intervalSeconds);
+
/**
* Sets the list of packages to search for plugins.
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
* @param packages The comma separated list of packages.
* @return this builder instance.
*/
@@ -427,30 +484,78 @@ public interface ConfigurationBuilder extends Builder
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
* @param flag "disable" will prevent the shutdown hook from being set.
* @return this builder instance.
*/
ConfigurationBuilder setShutdownHook(String flag);
/**
- * 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
+ * How long appenders and background tasks will get to shut down when the JVM shuts down.
+ * The 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".)
+ *
+ * If the timeout value is set to {@code null} any previously configured value will be cleared.
+ *
* @return this builder instance.
- *
+ * @throws NullPointerException if the given {@code timeUnit} is {@code null}
* @see LoggerContext#stop(long, TimeUnit)
*/
ConfigurationBuilder setShutdownTimeout(long timeout, TimeUnit timeUnit);
+ /**
+ * Sets the shutdown timeout for appenders and background tasks (in the specified time-unit) to shut down
+ * when the JVM is shutdown
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
+ * @param timeout the timeout (in the given time-unit
+ * @param timeUnit the time-unit of the {@code timeout} value (i.e. SECONDS, MILLISECONDS)
+ * @return this builder instance
+ * @throws IllegalArgumentException if the {@code timeout} can not be converted to a valid {@code Long}
+ * @throws NullPointerException if the {@code timeUnit} is {@code null}
+ */
+ ConfigurationBuilder setShutdownTimeout(String timeout, TimeUnit timeUnit);
+
+ /**
+ * Sets the shutdown timeout (in milliseconds) for appenders and background tasks to shut down when the JVM
+ * is shutdown.
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
+ * @param timeoutMillis the timeout in milliseconds
+ * @return this builder instance
+ */
+ ConfigurationBuilder setShutdownTimeout(long timeoutMillis);
+
/**
* Sets the level of the StatusLogger.
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
* @param level The logging level.
* @return this builder instance.
*/
ConfigurationBuilder setStatusLevel(Level level);
+ /**
+ * Sets the level of the StatusLogger.
+ *
+ * If the given value is {@code null}, any previous value will be cleared.
+ *
+ *
+ * If the given value is not {@code null}, it must be resolvable to a valid configured level
+ *
+ * @param level The logging level
+ * @return this builder instance
+ * @throws IllegalArgumentException if the given argument is not a valid level
+ */
+ ConfigurationBuilder setStatusLevel(String level);
+
/**
* Sets whether the logging should include constructing Plugins.
* @param verbosity "disable" will hide messages from plugin construction.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
index 273906a5d48..6d91c2d89d8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
@@ -20,7 +20,7 @@
* @since 2.4
*/
@Export
-@Version("2.20.1")
+@Version("2.25.0")
package org.apache.logging.log4j.core.config.builder.api;
import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
index f1ec35e06a0..07414399108 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
@@ -20,103 +20,183 @@
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
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.net.Advertiser;
import org.apache.logging.log4j.core.util.Patterns;
/**
- * This is the general version of the Configuration created by the Builder. It may be extended to
- * enhance its functionality.
+ * A {@link Configuration} that is built (assembled) via a {@link ConfigurationBuilder}.
+ *
+ * This base implementation may be extended to enhance its functionality.
+ *
+ * Important Notes for Extension:
+ *
+ *
+ * - This class is designed to be extended for custom configurations. Subclasses should maintain
+ * the contract of returning valid and non-{@code null} configuration components.
+ * - Fields such as {@code rootComponent} are intentionally made accessible to subclasses. However,
+ * direct modification of such fields is discouraged to prevent inconsistencies.
+ * - Where possible, subclasses should adhere to the public accessor methods and override provided hooks
+ * like {@link #setup()} to implement additional configuration logic.
+ *
+ *
+ * Backward Compatibility
+ *
+ * Certain design decisions in this class, such as the use of
+ * a {@code protected} field for {@code rootComponent}, have been retained for historical reasons and due to
+ * guarantees of binary compatibility for users of this class. Users of this API should avoid directly manipulating
+ * internal fields where possible and instead rely on public or protected accessor methods where available.
+ *
+ *
+ * This implementation is neither immutable nor thread-safe.
+ *
*
* @since 2.4
*/
public class BuiltConfiguration extends AbstractConfiguration {
+
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;
+
+ protected Component rootComponent; // should be private and accessed via getter, but backwards-compatibility...
+
private String contentType = "text";
+ /**
+ * Constructs a new instance.
+ *
+ * The configuration assembled using the {@link ConfigurationBuilder} is transported via the given
+ * {@code rootComponent} and used to populate this configuration's root {@code Node} in the
+ * {@link #setup} method implementation.
+ *
+ *
+ * This constructor is called via reflection from the {@link DefaultConfigurationBuilder}.
+ *
+ *
+ * @param loggerContext the logger-context (can be {@code null})
+ * @param configurationSource the configuration-source
+ * @param rootComponent the root-component created by the builder
+ * @throws NullPointerException if the {@code source} or {@code rootComponent} argument is {@code null}
+ */
public BuiltConfiguration(
- final LoggerContext loggerContext, final ConfigurationSource source, final Component rootComponent) {
- super(loggerContext, source);
- statusConfig = new StatusConfiguration().withStatus(getDefaultStatus());
- for (final Component component : rootComponent.getComponents()) {
- switch (component.getPluginType()) {
- case "Scripts": {
- scriptsComponent = component;
- break;
- }
- case "Loggers": {
- loggersComponent = component;
- break;
- }
- case "Appenders": {
- appendersComponent = component;
- break;
- }
- case "Filters": {
- filtersComponent = component;
- break;
- }
- case "Properties": {
- propertiesComponent = component;
- break;
- }
- case "CustomLevels": {
- customLevelsComponent = component;
- break;
- }
+ final LoggerContext loggerContext,
+ final ConfigurationSource configurationSource,
+ final Component rootComponent) {
+
+ super(
+ loggerContext,
+ Objects.requireNonNull(configurationSource, "The 'configurationSource' argument cannot be null"));
+
+ Objects.requireNonNull(rootComponent, "The 'rootComponent' argument cannot be null");
+
+ this.rootComponent = rootComponent;
+ this.statusConfig = new StatusConfiguration().withStatus(getDefaultStatus());
+
+ // process each of the root-component's attributes, dealing with special cases and assigning all to root node
+ for (Map.Entry entry : rootComponent.getAttributes().entrySet()) {
+ final String key = entry.getKey().trim();
+ final String value = entry.getValue();
+ this.getRootNode().getAttributes().put(key, value); // all attributes get passed to root-node
+ if ("advertise".equalsIgnoreCase(key)) {
+ createAdvertiser(value, getConfigurationSource());
+ } else if ("dest".equalsIgnoreCase(key)) {
+ statusConfig.withDestination(value);
+ } else if ("monitorInterval".equalsIgnoreCase(key)) {
+ setMonitorInterval(value, TimeUnit.SECONDS);
+ } else if ("name".equalsIgnoreCase(key)) {
+ setName(value);
+ } else if ("packages".equalsIgnoreCase(key)) {
+ setPluginPackages(value);
+ } else if ("shutdownHook".equalsIgnoreCase(key)) {
+ setShutdownHook(value);
+ } else if ("shutdownTimeout".equalsIgnoreCase(key)) {
+ setShutdownTimeout(value, TimeUnit.MILLISECONDS);
+ } else if ("status".equalsIgnoreCase(key)) {
+ statusConfig.withStatus(value);
}
}
- this.rootComponent = rootComponent;
+
+ this.statusConfig.initialize();
}
+ /**
+ * {@inheritDoc}
+ *
+ * Converts the {@code Component} children of the root component to Log4j configuration nodes and
+ * subsequently invalidates the root component.
+ *
+ */
@Override
public void setup() {
+
final List children = rootNode.getChildren();
- if (propertiesComponent.getComponents().size() > 0) {
- children.add(convertToNode(rootNode, propertiesComponent));
- }
- if (scriptsComponent.getComponents().size() > 0) {
- children.add(convertToNode(rootNode, scriptsComponent));
- }
- if (customLevelsComponent.getComponents().size() > 0) {
- children.add(convertToNode(rootNode, customLevelsComponent));
- }
- children.add(convertToNode(rootNode, loggersComponent));
- children.add(convertToNode(rootNode, appendersComponent));
- if (filtersComponent.getComponents().size() > 0) {
- if (filtersComponent.getComponents().size() == 1) {
- children.add(
- convertToNode(rootNode, filtersComponent.getComponents().get(0)));
- } else {
- children.add(convertToNode(rootNode, filtersComponent));
- }
+
+ if (this.rootComponent != null) {
+
+ getChildComponent("Properties")
+ .filter(c -> !c.getComponents().isEmpty())
+ .ifPresent(c -> children.add(convertToNode(rootNode, c)));
+ getChildComponent("Scripts")
+ .filter(c -> !c.getComponents().isEmpty())
+ .ifPresent(c -> children.add(convertToNode(rootNode, c)));
+ getChildComponent("CustomLevels")
+ .filter(c -> !c.getComponents().isEmpty())
+ .ifPresent(c -> children.add(convertToNode(rootNode, c)));
+
+ children.add(convertToNode(rootNode, getChildComponent("Loggers").orElse(new Component("Loggers"))));
+ children.add(convertToNode(rootNode, getChildComponent("Appenders").orElse(new Component("Appenders"))));
+
+ getChildComponent("Filters")
+ .filter(c -> !c.getComponents().isEmpty())
+ .ifPresent(c -> {
+ if (c.getComponents().size() == 1) {
+ children.add(
+ convertToNode(rootNode, c.getComponents().get(0)));
+ } else {
+ children.add(convertToNode(rootNode, c));
+ }
+ });
}
- rootComponent = null;
+
+ this.rootComponent = null;
}
+ /**
+ * Gets the content-type of the built configuration.
+ * @return the content-type
+ */
public String getContentType() {
return this.contentType;
}
+ /**
+ * Sets the content-type of the build configuration.
+ * @param contentType the content-type
+ */
public void setContentType(final String contentType) {
this.contentType = contentType;
}
- public void createAdvertiser(final String advertiserString, final ConfigurationSource configSource) {
+ /**
+ * Creates and sets this configuration's {@link Advertiser} for the given configuration-source.
+ * @param name the name of the advertiser
+ * @param configSource the configuration-source
+ */
+ public void createAdvertiser(final String name, final ConfigurationSource configSource) {
byte[] buffer = null;
try {
if (configSource != null) {
@@ -126,39 +206,146 @@ public void createAdvertiser(final String advertiserString, final ConfigurationS
}
}
} catch (final IOException ioe) {
- LOGGER.warn("Unable to read configuration source " + configSource.toString());
+ LOGGER.warn("Unable to read configuration source {}", configSource);
}
- super.createAdvertiser(advertiserString, configSource, buffer, contentType);
+ super.createAdvertiser(name, configSource, buffer, contentType);
}
+ /**
+ * Returns the status-logger fallback listener configuration.
+ *
+ * Implementations should always return a non-{@code null} value.
+ *
+ * @return the status configuration
+ */
public StatusConfiguration getStatusConfiguration() {
return statusConfig;
}
+ /**
+ * Sets the packages to search for plugin implementations.
+ * @param packages a comma-separated list of package names
+ */
public void setPluginPackages(final String packages) {
- pluginPackages.addAll(Arrays.asList(packages.split(Patterns.COMMA_SEPARATOR)));
+ Objects.requireNonNull(packages, "The 'packages' argument cannot be null");
+ List packageList = Arrays.stream(packages.split(Patterns.COMMA_SEPARATOR))
+ .map(String::trim)
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toList());
+ pluginPackages.addAll(packageList);
}
+ /**
+ * Sets the shutdown hook flag.
+ * @param flag if "disable" the shutdown hook will be disabled; otherwise, all other values enable it
+ */
public void setShutdownHook(final String flag) {
- isShutdownHookEnabled = !"disable".equalsIgnoreCase(flag);
+ setShutdownHook(!"disable".equalsIgnoreCase(flag));
}
- public void setShutdownTimeoutMillis(final long shutdownTimeoutMillis) {
- this.shutdownTimeoutMillis = shutdownTimeoutMillis;
+ /**
+ * Sets the enablement of the shutdown hook.
+ * @param flag {@code true} to enable the shutdown hook (default); otherwise, {@code false} to disable
+ */
+ public void setShutdownHook(final boolean flag) {
+ isShutdownHookEnabled = flag;
}
- public void setMonitorInterval(final int intervalSeconds) {
- if (this instanceof Reconfigurable && intervalSeconds > 0) {
- initializeWatchers((Reconfigurable) this, getConfigurationSource(), intervalSeconds);
+ /**
+ * Sets the shutdown timeout to the given value converted to milliseconds using the provided time-unit.
+ * @param value the value
+ * @param timeUnit the time-unit of the value (i.e. MILLISECONDS, SECONDS, MINUTES, etc.)
+ */
+ public void setShutdownTimeout(final String value, final TimeUnit timeUnit) {
+ Objects.requireNonNull(value, "The 'value' argument cannot be null");
+ Objects.requireNonNull(timeUnit, "The 'timeUnit' argument cannot be null");
+ try {
+ setShutdownTimeoutMillis(timeUnit.toMillis(Long.parseLong(value)));
+ } catch (final Exception ex) {
+ LOGGER.error("The given shudown timeoutt is invalid '{}'. Reason: {}", value, ex.getMessage());
}
}
- @Override
- public PluginManager getPluginManager() {
- return pluginManager;
+ /**
+ * Sets the shutdown timeout in milliseconds.
+ * @param millis the number of milliseconds to set
+ */
+ public void setShutdownTimeoutMillis(final long millis) {
+ this.shutdownTimeoutMillis = millis;
+ }
+
+ /**
+ * Sets the monitor interval to the given value converted to seconds using the provided time-unit.
+ * @param value the value
+ * @param timeUnit the time-unit of the value (i.e. MILLISECONDS, SECONDS, MINUTES, etc.)
+ */
+ public void setMonitorInterval(final String value, final TimeUnit timeUnit) {
+ Objects.requireNonNull(value, "The 'value' argument cannot be null");
+ Objects.requireNonNull(timeUnit, "The 'timeUnit' argument cannot be null");
+ try {
+ setMonitorInterval(Math.toIntExact(timeUnit.toSeconds(Integer.parseInt(value))));
+ } catch (final Exception ex) {
+ LOGGER.error("The given monitor interval is invalid '{}'. Reason: {}", value, ex.getMessage());
+ }
+ }
+
+ /**
+ * Sets the monitor interval to the given number of seconds.
+ *
+ * If the given value is greater than 0 and this instance implements {@link Reconfigurable},
+ * this method will trigger the {@link #initializeWatchers(Reconfigurable, ConfigurationSource, int)}
+ * method.
+ *
+ * @param seconds the number of seconds to set
+ */
+ public void setMonitorInterval(final int seconds) {
+ if (this instanceof Reconfigurable && seconds > 0) {
+ initializeWatchers((Reconfigurable) this, getConfigurationSource(), seconds);
+ }
+ }
+
+ /**
+ * Gets the root component.
+ *
+ * NOTE: After {@link #setup()} has been called this will always return an empty optional.
+ *
+ * @return an optional containing the root component or that is empty if undefined
+ */
+ protected final Optional getRootComponent() {
+ return Optional.ofNullable(rootComponent);
+ }
+
+ /**
+ * Gets the child component of the root component with the given plugin-type.
+ * @param pluginType the plugin-type to lookup
+ * @return an optional containing the resolved child component or that is empty if not found
+ */
+ protected final Optional getChildComponent(final String pluginType) {
+ Objects.requireNonNull(pluginType, "The 'pluginType' argument cannot be null");
+ return Optional.ofNullable(getChildComponents().get(pluginType));
+ }
+
+ /**
+ * Returns a map of all "named" child components.
+ * @return an immutable map of child components keyed by name
+ */
+ protected final Map getChildComponents() {
+
+ return this.rootComponent.getComponents().stream()
+ .filter(c -> Objects.nonNull(c.getPluginType()))
+ .collect(Collectors.toMap(Component::getPluginType, Function.identity()));
}
+ /**
+ * Converts the given configuration {@link Component} to a runtime configuration {@link Node}
+ * with the given parent node.
+ * @param parent the target parent node
+ * @param component the component to convert
+ * @return the constructed node
+ */
protected Node convertToNode(final Node parent, final Component component) {
+ Objects.requireNonNull(parent, "The 'parent' argument cannot be null");
+ Objects.requireNonNull(component, "The 'component' argument cannot be null");
final String name = component.getPluginType();
final PluginType> pluginType = pluginManager.getPluginType(name);
final Node node = new Node(parent, name, pluginType);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
index 633e619b281..593a0efb5b9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
@@ -22,8 +22,9 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
-import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
@@ -32,7 +33,6 @@
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
@@ -41,7 +41,6 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.LoggerConfig;
@@ -63,555 +62,930 @@
import org.apache.logging.log4j.core.util.Throwables;
/**
+ * A default ConfigurationBuilder that is used to build a BuiltConfiguration instance.
+ *
* @param The BuiltConfiguration type.
* @since 2.4
*/
public class DefaultConfigurationBuilder implements ConfigurationBuilder {
- private static final String INDENT = " ";
+ /** Indentation prefix used when generating an XML representation of this builder's configuration. */
+ private static final String XML_INDENT = " ";
+
+ /** The class of the configuration instance being built. */
+ private final Class configurationClass;
+ /** The root component. */
private final Component root = new Component();
- private Component loggers;
- private Component appenders;
- private Component filters;
- private Component properties;
- private Component customLevels;
- private Component scripts;
- private final Class clazz;
+
+ /** Standard component: "Appenders". */
+ private final Component appenders;
+
+ /** Standard component: "CustomLevels". */
+ private final Component customLevels;
+
+ /** Standard component: "Filters". */
+ private final Component filters;
+
+ /** Standard component: "Loggers". */
+ private final Component loggers;
+
+ /** Standard component: "Properties". */
+ private final Component properties;
+
+ /** Standard component: "Scripts". */
+ private final Component scripts;
+
+ /** The configuration source passed to the constructor of the built configuration instance. */
private ConfigurationSource source;
- private int monitorInterval;
- private Level level;
- private String destination;
- private String packages;
- private String shutdownFlag;
- private long shutdownTimeoutMillis;
- private String advertiser;
- private LoggerContext loggerContext;
- private String name;
- @SuppressFBWarnings(
- value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"},
- justification = "This method only uses internally generated data.")
- public static void formatXml(final Source source, final Result result)
- throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
- final Transformer transformer = TransformerFactory.newInstance().newTransformer();
- transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(INDENT.length()));
- transformer.setOutputProperty(OutputKeys.INDENT, "yes");
- transformer.transform(source, result);
- }
+ /** The logger context passed to the constructor of the built configuration instance. */
+ private LoggerContext loggerContext;
+ /**
+ * Constructs a new instance with a standard {@link BuiltConfiguration} target type.
+ */
@SuppressWarnings("unchecked")
public DefaultConfigurationBuilder() {
this((Class) BuiltConfiguration.class);
root.addAttribute("name", "Built");
}
+ /**
+ * Constructs a new instance with the given {@link BuiltConfiguration} implementation class.
+ * @param clazz the {@code Class} of the {@code BuiltConfiguration} implementation to build
+ * @throws NullPointerException if the argument is null
+ */
public DefaultConfigurationBuilder(final Class clazz) {
- if (clazz == null) {
- throw new IllegalArgumentException("A Configuration class must be provided");
- }
- this.clazz = clazz;
- final List components = root.getComponents();
- properties = new Component("Properties");
- components.add(properties);
- scripts = new Component("Scripts");
- components.add(scripts);
- customLevels = new Component("CustomLevels");
- components.add(customLevels);
- filters = new Component("Filters");
- components.add(filters);
- appenders = new Component("Appenders");
- components.add(appenders);
- loggers = new Component("Loggers");
- components.add(loggers);
+
+ super();
+
+ this.configurationClass = Objects.requireNonNull(clazz, "The 'configurationClass' argument must not be null.");
+ this.root.addAttribute("name", "Built");
+
+ properties = getOrCreateComponent("Properties");
+ scripts = getOrCreateComponent("Scripts");
+ customLevels = getOrCreateComponent("CustomLevels");
+ filters = getOrCreateComponent("Filters");
+ appenders = getOrCreateComponent("Appenders");
+ loggers = getOrCreateComponent("Loggers");
}
+ //
+ // ACCESSORS / MUTATORS
+ //
+
+ /**
+ * Adds the component built by the provided builder as a child component of the given parent component.
+ *
+ * @param parent the parent component to add to
+ * @param builder the builder to generate the child component from
+ * @throws ConfigurationException if an error occurs while building the given builder
+ * @throws NullPointerException if either argument is null
+ */
protected ConfigurationBuilder add(final Component parent, final ComponentBuilder> builder) {
+ Objects.requireNonNull(parent, "The 'parent' component must not be null.");
+ Objects.requireNonNull(builder, "The 'builder' component must not be null.");
parent.getComponents().add(builder.build());
return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder add(final AppenderComponentBuilder builder) {
- return add(appenders, builder);
+ add(this.appenders, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder add(final CustomLevelComponentBuilder builder) {
- return add(customLevels, builder);
+ add(this.customLevels, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder add(final FilterComponentBuilder builder) {
- return add(filters, builder);
+ add(this.filters, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder add(final ScriptComponentBuilder builder) {
- return add(scripts, builder);
+ add(this.scripts, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder add(final ScriptFileComponentBuilder builder) {
- return add(scripts, builder);
+ add(this.scripts, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder add(final LoggerComponentBuilder builder) {
- return add(loggers, builder);
+ add(this.loggers, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
- public ConfigurationBuilder add(final RootLoggerComponentBuilder builder) {
+ public ConfigurationBuilder add(RootLoggerComponentBuilder builder) {
for (final Component c : loggers.getComponents()) {
if (c.getPluginType().equals(LoggerConfig.ROOT)) {
throw new ConfigurationException("Root Logger was previously defined");
}
}
- return add(loggers, builder);
+ add(this.loggers, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
public ConfigurationBuilder addProperty(final String key, final String value) {
- properties.addComponent(newComponent(key, "Property", value).build());
+ add(this.properties, newProperty(key, value));
return this;
}
+ /** {@inheritDoc} */
@Override
- public T build() {
- return build(true);
+ public ConfigurationBuilder addProperty(final PropertyComponentBuilder builder) {
+ add(this.properties, builder);
+ return this;
}
+ /** {@inheritDoc} */
@Override
- public T build(final boolean initialize) {
- T configuration;
+ public ConfigurationBuilder addRootProperty(String key, String value) {
+ Objects.requireNonNull(key, "The 'key' argument must not be null.");
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("The 'key' argument must not be empty.");
+ }
+ if (value != null) {
+ this.getRootComponent().getAttributes().put(key, value);
+ } else {
+ this.getRootComponent().getAttributes().remove(key);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the value of a property (attribute) on the root component.
+ * @param key the property key
+ * @return an optional containing the property value or that is empty if not set
+ */
+ protected Optional getRootProperty(String key) {
+ Objects.requireNonNull(key, "The 'key' argument must not be null.");
+ if (key.isEmpty()) {
+ throw new IllegalArgumentException("The 'key' argument must not be empty.");
+ }
+ return Optional.ofNullable(this.getRootComponent().getAttributes().get(key));
+ }
+
+ /**
+ * Gets the child component with the given plugin-type.
+ * @param pluginType the plugin-type to lookup
+ * @return an optional containing the resolved component or that is empty if not found
+ */
+ protected Optional getComponent(final String pluginType) {
+ Objects.requireNonNull(pluginType, "The 'pluginType' argument must not be null.");
+ return this.root.getComponents().stream()
+ .filter(c -> c.getPluginType().equals(pluginType))
+ .findFirst();
+ }
+
+ /**
+ * Gets the root component.
+ *
+ * @return the root component (will never be null)
+ */
+ protected Component getRootComponent() {
+ return this.root;
+ }
+
+ /**
+ * Gets or creates the child component with the given plugin-type.
+ *
+ * The lookup is case-sensitive.
+ *
+ *
+ * @param pluginType the plugin-type to lookup
+ * @return either the existing component or a new component with the given plugin-type if not found (never null)
+ */
+ protected Component getOrCreateComponent(String pluginType) {
+ Objects.requireNonNull(pluginType, "The 'pluginType' must not be null.");
+ return getComponent(pluginType).orElseGet(() -> {
+ Component c = new Component(pluginType);
+ this.root.getComponents().add(c);
+ return c;
+ });
+ }
+
+ /**
+ * Gets the value of the "advertiser" property.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getAdvertiser() {
+ return getRootProperty("advertiser");
+ }
+
+ /**
+ * Gets the value of the "name" property.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getConfigurationName() {
+ return getRootProperty("name");
+ }
+
+ /**
+ * Gets the configuration-source.
+ * @return an optional containing the configuration-source or that is empy if undefined
+ */
+ protected Optional getConfigurationSource() {
+ return Optional.ofNullable(this.source);
+ }
+
+ /**
+ * Gets the value of the "dest" property.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getDestination() {
+ return getRootProperty("dest");
+ }
+
+ /**
+ * Gets the logger-context.
+ *
+ * @return an optional containing the logger-context or that is empty if undefined
+ */
+ protected Optional getLoggerContext() {
+ return Optional.ofNullable(this.loggerContext);
+ }
+
+ /**
+ * Returns the configured monitor interval (in seconds) as an {@code Integer}.
+ * @return an optional containing the configured monitor interval or that is empty if undefined (or invalid)
+ */
+ protected Optional getMonitorInterval() {
try {
- if (source == null) {
- source = ConfigurationSource.NULL_SOURCE;
- }
- final Constructor constructor =
- clazz.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class);
- configuration = constructor.newInstance(loggerContext, source, root);
- configuration.getRootNode().getAttributes().putAll(root.getAttributes());
- if (name != null) {
- configuration.setName(name);
- }
- if (level != null) {
- configuration.getStatusConfiguration().withStatus(level);
- }
- if (destination != null) {
- configuration.getStatusConfiguration().withDestination(destination);
- }
- if (packages != null) {
- configuration.setPluginPackages(packages);
- }
- if (shutdownFlag != null) {
- configuration.setShutdownHook(shutdownFlag);
- }
- if (shutdownTimeoutMillis > 0) {
- configuration.setShutdownTimeoutMillis(shutdownTimeoutMillis);
- }
- if (advertiser != null) {
- configuration.createAdvertiser(advertiser, source);
- }
- configuration.setMonitorInterval(monitorInterval);
+ return getRootProperty("monitorInterval").map(Integer::parseInt);
} catch (final Exception ex) {
- throw new IllegalArgumentException("Invalid Configuration class specified", ex);
+ return Optional.empty();
}
- configuration.getStatusConfiguration().initialize();
- if (initialize) {
- configuration.initialize();
+ }
+
+ /**
+ * Gets the value of the "packages" property.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getPackages() {
+ return getRootProperty("packages");
+ }
+
+ /**
+ * Gets the value of the "shutdownHook" property.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getShutdownHook() {
+ return getRootProperty("shutdownHook");
+ }
+
+ /**
+ * Gets the value of the "shutdownTimeout" property as a {@code Long}.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getShutdownTimeout() {
+ try {
+ return getRootProperty("shutdownTimeout").map(Long::parseLong);
+ } catch (final Exception ex) {
+ return Optional.empty();
}
- return configuration;
}
- private String formatXml(final String xml)
- throws TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError {
- final StringWriter writer = new StringWriter();
- formatXml(new StreamSource(new StringReader(xml)), new StreamResult(writer));
- return writer.toString();
+ /**
+ * Gets the value of the "status" property as a {@code Level}.
+ * @return an optional containing the property value or that is empty if undefined
+ */
+ protected Optional getStatusLevel() {
+ return getRootProperty("status").map(Level::getLevel);
}
+ /**
+ * Gets the "Appenders" component.
+ *
+ * @return the appenders component
+ */
+ protected final Component getAppenders() {
+ return this.appenders;
+ }
+
+ /**
+ * Gets the "CustomLevels" component.
+ *
+ * @return the custom-levels component
+ */
+ protected final Component getCustomLevels() {
+ return this.customLevels;
+ }
+
+ /**
+ * Gets the "Filters" component.
+ *
+ * @return the filters component
+ */
+ protected final Component getFilters() {
+ return this.filters;
+ }
+
+ /**
+ * Gets the "Loggers" component.
+ *
+ * @return the loggers component
+ */
+ protected final Component getLoggers() {
+ return this.loggers;
+ }
+
+ /**
+ * Gets the "Properties" component.
+ *
+ * @return the properties component
+ */
+ protected final Component getProperties() {
+ return this.properties;
+ }
+
+ /**
+ * Gets the "Scripts" component.
+ *
+ * @return the scripts component
+ */
+ protected final Component getScripts() {
+ return this.scripts;
+ }
+
+ /** {@inheritDoc} */
@Override
- public void writeXmlConfiguration(final OutputStream output) throws IOException {
- try {
- final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
- writeXmlConfiguration(xmlWriter);
- xmlWriter.close();
- } catch (final XMLStreamException e) {
- if (e.getNestedException() instanceof IOException) {
- throw (IOException) e.getNestedException();
+ public ConfigurationBuilder setAdvertiser(final String advertiser) {
+ this.addRootProperty("advertiser", advertiser);
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setConfigurationName(final String name) {
+ this.addRootProperty("name", name);
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setConfigurationSource(final ConfigurationSource configurationSource) {
+ source = configurationSource;
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setDestination(final String destination) {
+ this.addRootProperty("dest", (destination == null) ? null : destination.trim());
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setMonitorInterval(final String intervalSeconds) {
+ int iMonitorInterval = 0;
+ if (intervalSeconds != null && !intervalSeconds.isEmpty() && !"0".equals(intervalSeconds.trim())) {
+ try {
+ iMonitorInterval = Integers.parseInt(intervalSeconds.trim());
+ } catch (final Exception ex) {
+ throw new IllegalArgumentException("Invalid monitor interval: " + intervalSeconds, ex);
}
- Throwables.rethrow(e);
}
+ return this.setMonitorInterval(iMonitorInterval);
}
+ /** {@inheritDoc} */
@Override
- public String toXmlConfiguration() {
- final StringWriter writer = new StringWriter();
- try {
- final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
- writeXmlConfiguration(xmlWriter);
- xmlWriter.close();
- return formatXml(writer.toString());
- } catch (final XMLStreamException | TransformerException e) {
- Throwables.rethrow(e);
- }
- return writer.toString();
+ public ConfigurationBuilder setMonitorInterval(final int intervalSeconds) {
+ addRootProperty("monitorInterval", intervalSeconds <= 0 ? null : String.valueOf(intervalSeconds));
+ return this;
}
- private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException {
- xmlWriter.writeStartDocument();
- xmlWriter.writeStartElement("Configuration");
- if (name != null) {
- xmlWriter.writeAttribute("name", name);
- }
- if (level != null) {
- xmlWriter.writeAttribute("status", level.name());
- }
- if (destination != null) {
- xmlWriter.writeAttribute("dest", destination);
- }
- if (packages != null) {
- xmlWriter.writeAttribute("packages", packages);
- }
- if (shutdownFlag != null) {
- xmlWriter.writeAttribute("shutdownHook", shutdownFlag);
- }
- if (shutdownTimeoutMillis > 0) {
- xmlWriter.writeAttribute("shutdownTimeout", String.valueOf(shutdownTimeoutMillis));
- }
- if (advertiser != null) {
- xmlWriter.writeAttribute("advertiser", advertiser);
- }
- if (monitorInterval > 0) {
- xmlWriter.writeAttribute("monitorInterval", String.valueOf(monitorInterval));
- }
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setPackages(final String packages) {
+ addRootProperty("packages", packages);
+ return this;
+ }
- writeXmlSection(xmlWriter, properties);
- writeXmlSection(xmlWriter, scripts);
- writeXmlSection(xmlWriter, customLevels);
- if (filters.getComponents().size() == 1) {
- writeXmlComponent(xmlWriter, filters.getComponents().get(0));
- } else if (filters.getComponents().size() > 1) {
- writeXmlSection(xmlWriter, filters);
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setShutdownHook(final String flag) {
+ addRootProperty("shutdownHook", flag);
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setShutdownTimeout(final String timeout, final TimeUnit timeUnit) {
+ Objects.requireNonNull(timeUnit, "The 'timeUnit' argument must not be null.");
+ long shutdownTimeoutMillis = 0L;
+ if (timeout != null && !timeout.isEmpty()) {
+ try {
+ shutdownTimeoutMillis = timeUnit.toMillis(Long.parseLong(timeout));
+ } catch (final Exception ex) {
+ throw new IllegalArgumentException("Invalid shutdown timeout: " + timeout, ex);
+ }
}
- writeXmlSection(xmlWriter, appenders);
- writeXmlSection(xmlWriter, loggers);
+ return this.setShutdownTimeout(shutdownTimeoutMillis, timeUnit);
+ }
- xmlWriter.writeEndElement(); // "Configuration"
- xmlWriter.writeEndDocument();
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setShutdownTimeout(final long timeout, final TimeUnit timeUnit) {
+ Objects.requireNonNull(timeUnit, "The 'timeUnit' argument must not be null.");
+ return this.setShutdownTimeout(timeUnit.toMillis(timeout));
}
- private void writeXmlSection(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
- if (!component.getAttributes().isEmpty()
- || !component.getComponents().isEmpty()
- || component.getValue() != null) {
- writeXmlComponent(xmlWriter, component);
- }
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setShutdownTimeout(final long timeoutMillis) {
+ this.addRootProperty("shutdownTimeout", timeoutMillis <= 0L ? null : String.valueOf(timeoutMillis));
+ return this;
}
- private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component)
- throws XMLStreamException {
- if (!component.getComponents().isEmpty() || component.getValue() != null) {
- xmlWriter.writeStartElement(component.getPluginType());
- writeXmlAttributes(xmlWriter, component);
- for (final Component subComponent : component.getComponents()) {
- writeXmlComponent(xmlWriter, subComponent);
- }
- if (component.getValue() != null) {
- xmlWriter.writeCharacters(component.getValue());
- }
- xmlWriter.writeEndElement();
- } else {
- xmlWriter.writeEmptyElement(component.getPluginType());
- writeXmlAttributes(xmlWriter, component);
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setStatusLevel(final Level level) {
+ this.addRootProperty("status", (level != null) ? level.toString() : null);
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigurationBuilder setStatusLevel(final String level) {
+ return this.setStatusLevel((level != null) ? Level.getLevel(level.trim()) : null);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated This method is ineffective and only kept for binary backward compatibility.
+ */
+ @Override
+ @Deprecated
+ public ConfigurationBuilder setVerbosity(final String verbosity) {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setLoggerContext(final LoggerContext loggerContext) {
+ this.loggerContext = loggerContext;
+ }
+
+ //
+ // BUILD
+ //
+
+ /** {@inheritDoc} */
+ @Override
+ public T build() {
+ return build(true);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public T build(final boolean initialize) {
+ if (source == null) {
+ source = ConfigurationSource.NULL_SOURCE;
}
+ T configuration = createNewConfigurationInstance(this.configurationClass);
+ if (initialize) {
+ configuration.initialize();
+ }
+ return configuration;
}
- private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component)
- throws XMLStreamException {
- for (final Map.Entry attribute :
- component.getAttributes().entrySet()) {
- xmlWriter.writeAttribute(attribute.getKey(), attribute.getValue());
+ /**
+ * Instantiate a new instance of the {@code Configuration} implementation.
+ *
+ * Subclasses may override this if they need to provide some non-standard behaviour.
+ *
+ *
+ * @return the new configuration instance
+ * @throws IllegalStateException if the configuration cannot be instantiated
+ */
+ protected T createNewConfigurationInstance(Class configurationClass) {
+ Objects.requireNonNull(configurationClass, "The 'configurationClass' argument must not be null.");
+ try {
+ final Constructor constructor =
+ configurationClass.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class);
+ return constructor.newInstance(loggerContext, source, root);
+ } catch (final Exception ex) {
+ throw new IllegalStateException(
+ "Configuration class '" + configurationClass.getName() + "' cannot be instantiated.", ex);
}
}
+ //
+ // BUILDER FACTORY METHODS
+ //
+
+ /** {@inheritDoc} */
@Override
public ScriptComponentBuilder newScript(final String name, final String language, final String text) {
return new DefaultScriptComponentBuilder(this, name, language, text);
}
+ /** {@inheritDoc} */
@Override
public ScriptFileComponentBuilder newScriptFile(final String path) {
return new DefaultScriptFileComponentBuilder(this, path, path);
}
+ /** {@inheritDoc} */
@Override
public ScriptFileComponentBuilder newScriptFile(final String name, final String path) {
return new DefaultScriptFileComponentBuilder(this, name, path);
}
+ /** {@inheritDoc} */
@Override
public AppenderComponentBuilder newAppender(final String name, final String type) {
return new DefaultAppenderComponentBuilder(this, name, type);
}
+ /** {@inheritDoc} */
@Override
public AppenderRefComponentBuilder newAppenderRef(final String ref) {
return new DefaultAppenderRefComponentBuilder(this, ref);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newAsyncLogger(final String name) {
return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger");
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newAsyncLogger(final String name, final boolean includeLocation) {
return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger", includeLocation);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newAsyncLogger(final String name, final Level level) {
return new DefaultLoggerComponentBuilder(this, name, level.toString(), "AsyncLogger");
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newAsyncLogger(final String name, final Level level, final boolean includeLocation) {
return new DefaultLoggerComponentBuilder(this, name, level.toString(), "AsyncLogger", includeLocation);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newAsyncLogger(final String name, final String level) {
return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger");
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newAsyncLogger(final String name, final String level, final boolean includeLocation) {
return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger", includeLocation);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newAsyncRootLogger() {
return new DefaultRootLoggerComponentBuilder(this, "AsyncRoot");
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newAsyncRootLogger(final boolean includeLocation) {
return new DefaultRootLoggerComponentBuilder(this, null, "AsyncRoot", includeLocation);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newAsyncRootLogger(final Level level) {
return new DefaultRootLoggerComponentBuilder(this, level.toString(), "AsyncRoot");
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newAsyncRootLogger(final Level level, final boolean includeLocation) {
return new DefaultRootLoggerComponentBuilder(this, level.toString(), "AsyncRoot", includeLocation);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newAsyncRootLogger(final String level) {
return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot");
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newAsyncRootLogger(final String level, final boolean includeLocation) {
return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot", includeLocation);
}
+ /** {@inheritDoc} */
@Override
public > ComponentBuilder newComponent(final String type) {
return new DefaultComponentBuilder<>(this, type);
}
+ /** {@inheritDoc} */
@Override
public > ComponentBuilder newComponent(final String name, final String type) {
return new DefaultComponentBuilder<>(this, name, type);
}
+ /** {@inheritDoc} */
@Override
public > ComponentBuilder newComponent(
final String name, final String type, final String value) {
return new DefaultComponentBuilder<>(this, name, type, value);
}
+ /** {@inheritDoc} */
@Override
public PropertyComponentBuilder newProperty(final String name, final String value) {
return new DefaultPropertyComponentBuilder(this, name, value);
}
+ /** {@inheritDoc} */
@Override
public KeyValuePairComponentBuilder newKeyValuePair(final String key, final String value) {
return new DefaultKeyValuePairComponentBuilder(this, key, value);
}
+ /** {@inheritDoc} */
@Override
public CustomLevelComponentBuilder newCustomLevel(final String name, final int level) {
return new DefaultCustomLevelComponentBuilder(this, name, level);
}
+ /** {@inheritDoc} */
@Override
public FilterComponentBuilder newFilter(
final String type, final Filter.Result onMatch, final Filter.Result onMismatch) {
return new DefaultFilterComponentBuilder(this, type, onMatch.name(), onMismatch.name());
}
+ /** {@inheritDoc} */
@Override
public FilterComponentBuilder newFilter(final String type, final String onMatch, final String onMismatch) {
return new DefaultFilterComponentBuilder(this, type, onMatch, onMismatch);
}
+ /** {@inheritDoc} */
@Override
public LayoutComponentBuilder newLayout(final String type) {
return new DefaultLayoutComponentBuilder(this, type);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newLogger(final String name) {
return new DefaultLoggerComponentBuilder(this, name, null);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newLogger(final String name, final boolean includeLocation) {
return new DefaultLoggerComponentBuilder(this, name, null, includeLocation);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newLogger(final String name, final Level level) {
return new DefaultLoggerComponentBuilder(this, name, level.toString());
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newLogger(final String name, final Level level, final boolean includeLocation) {
return new DefaultLoggerComponentBuilder(this, name, level.toString(), includeLocation);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newLogger(final String name, final String level) {
return new DefaultLoggerComponentBuilder(this, name, level);
}
+ /** {@inheritDoc} */
@Override
public LoggerComponentBuilder newLogger(final String name, final String level, final boolean includeLocation) {
return new DefaultLoggerComponentBuilder(this, name, level, includeLocation);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newRootLogger() {
return new DefaultRootLoggerComponentBuilder(this, null);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newRootLogger(final boolean includeLocation) {
return new DefaultRootLoggerComponentBuilder(this, null, includeLocation);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newRootLogger(final Level level) {
return new DefaultRootLoggerComponentBuilder(this, level.toString());
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newRootLogger(final Level level, final boolean includeLocation) {
return new DefaultRootLoggerComponentBuilder(this, level.toString(), includeLocation);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newRootLogger(final String level) {
return new DefaultRootLoggerComponentBuilder(this, level);
}
+ /** {@inheritDoc} */
@Override
public RootLoggerComponentBuilder newRootLogger(final String level, final boolean includeLocation) {
return new DefaultRootLoggerComponentBuilder(this, level, includeLocation);
}
- @Override
- public ConfigurationBuilder setAdvertiser(final String advertiser) {
- this.advertiser = advertiser;
- return this;
- }
+ //
+ // XML SERIALIZATION
+ //
- /**
- * Set the name of the configuration.
- *
- * @param name the name of the {@link Configuration}. By default is {@code "Assembled"}.
- * @return this builder instance
- */
- @Override
- public ConfigurationBuilder setConfigurationName(final String name) {
- this.name = name;
- return this;
+ private String formatXml(final String xml) throws TransformerException, TransformerFactoryConfigurationError {
+ final StringWriter writer = new StringWriter();
+ formatXml(new StreamSource(new StringReader(xml)), new StreamResult(writer));
+ return writer.toString();
}
- /**
- * Set the ConfigurationSource.
- *
- * @param configurationSource the {@link ConfigurationSource}
- * @return this builder instance
- */
- @Override
- public ConfigurationBuilder setConfigurationSource(final ConfigurationSource configurationSource) {
- source = configurationSource;
- return this;
+ @SuppressFBWarnings(
+ value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"},
+ justification = "This method only uses internally generated data.")
+ public static void formatXml(final Source source, final Result result)
+ throws TransformerFactoryConfigurationError, TransformerException {
+ final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(
+ "{http://xml.apache.org/xslt}indent-amount", Integer.toString(XML_INDENT.length()));
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.transform(source, result);
}
+ /** {@inheritDoc} */
@Override
- public ConfigurationBuilder setMonitorInterval(final String intervalSeconds) {
- monitorInterval = Integers.parseInt(intervalSeconds);
- return this;
+ public void writeXmlConfiguration(final OutputStream output) throws IOException {
+ try {
+ final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
+ writeXmlConfiguration(xmlWriter);
+ xmlWriter.close();
+ } catch (final XMLStreamException e) {
+ if (e.getNestedException() instanceof IOException) {
+ throw (IOException) e.getNestedException();
+ }
+ Throwables.rethrow(e);
+ }
}
+ /** {@inheritDoc} */
@Override
- public ConfigurationBuilder setPackages(final String packages) {
- this.packages = packages;
- return this;
+ public String toXmlConfiguration() {
+ final StringWriter writer = new StringWriter();
+ try {
+ final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
+ writeXmlConfiguration(xmlWriter);
+ xmlWriter.close();
+ return formatXml(writer.toString());
+ } catch (final XMLStreamException | TransformerException e) {
+ Throwables.rethrow(e);
+ }
+ return writer.toString();
}
- @Override
- public ConfigurationBuilder setShutdownHook(final String flag) {
- this.shutdownFlag = flag;
- return this;
+ private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException {
+ xmlWriter.writeStartDocument();
+ xmlWriter.writeStartElement("Configuration");
+ writeXmlAttributes(xmlWriter);
+ writeXmlSections(xmlWriter);
+ xmlWriter.writeEndElement(); // "Configuration"
+ xmlWriter.writeEndDocument();
}
- @Override
- public ConfigurationBuilder setShutdownTimeout(final long timeout, final TimeUnit timeUnit) {
- this.shutdownTimeoutMillis = timeUnit.toMillis(timeout);
- return this;
+ /**
+ * Writes the top-level attributes of the main "Configuration" root element.
+ *
+ * @param xmlWriter the XML writer to write to
+ * @throws XMLStreamException if an error occurs while writing the XML document
+ */
+ protected void writeXmlAttributes(final XMLStreamWriter xmlWriter) throws XMLStreamException {
+ writeXmlAttribute(xmlWriter, "name", this.getConfigurationName().orElse(null));
+ writeXmlAttribute(
+ xmlWriter, "status", this.getStatusLevel().map(Level::toString).orElse(null));
+ writeXmlAttribute(xmlWriter, "dest", this.getDestination().orElse(null));
+ writeXmlAttribute(xmlWriter, "packages", this.getPackages().orElse(null));
+ writeXmlAttribute(xmlWriter, "shutdownHook", this.getShutdownHook().orElse(null));
+ writeXmlAttribute(
+ xmlWriter,
+ "shutdownTimeout",
+ this.getShutdownTimeout().map(String::valueOf).orElse(null));
+ writeXmlAttribute(xmlWriter, "advertiser", this.getAdvertiser().orElse(null));
+ writeXmlAttribute(
+ xmlWriter,
+ "monitorInterval",
+ this.getMonitorInterval().map(String::valueOf).orElse(null));
}
- @Override
- public ConfigurationBuilder setStatusLevel(final Level level) {
- this.level = level;
- return this;
+ /**
+ * Writes the sections (sub-elements) of the main "Configuration" root element.
+ * @param xmlWriter the XML writer to write to
+ * @throws XMLStreamException if an error occurs while writing the XML document
+ */
+ protected void writeXmlSections(final XMLStreamWriter xmlWriter) throws XMLStreamException {
+ writeXmlSection(xmlWriter, properties);
+ writeXmlSection(xmlWriter, scripts);
+ writeXmlSection(xmlWriter, customLevels);
+
+ if (filters.getComponents().size() == 1) {
+ writeXmlComponent(xmlWriter, filters.getComponents().get(0));
+ } else if (filters.getComponents().size() > 1) {
+ writeXmlSection(xmlWriter, filters);
+ }
+
+ writeXmlSection(xmlWriter, appenders);
+ writeXmlSection(xmlWriter, loggers);
}
/**
- * @deprecated This method is ineffective and only kept for binary backward compatibility.
+ * Writes an attribute to the given writer.
+ *
+ * If the provided value is {@code null} no attribute will be written.
+ *
+ * @param xmlWriter the writer to write to
+ * @param name the attribute name
+ * @param value the attribute value (if {@code null} no action will be taken
+ * @throws NullPointerException if either the {@code xmlWriter} or {@code name} attributes are {@code null}
+ * @throws IllegalArgumentException if the {@code name} attribute is blank
+ * @throws XMLStreamException if an error occurs while writing the attribute
*/
- @Override
- @Deprecated
- public ConfigurationBuilder setVerbosity(final String verbosity) {
- return this;
+ private void writeXmlAttribute(XMLStreamWriter xmlWriter, String name, String value) throws XMLStreamException {
+ Objects.requireNonNull(xmlWriter, "The 'xmlWriter' argument must not be null.");
+ Objects.requireNonNull(name, "The 'name' argument must not be null.");
+ if (name.trim().isEmpty()) {
+ throw new IllegalArgumentException("The 'name' argument must not be blank.");
+ }
+ if (value != null) {
+ xmlWriter.writeAttribute(name, value);
+ }
}
- @Override
- public ConfigurationBuilder setDestination(final String destination) {
- this.destination = destination;
- return this;
+ private void writeXmlSection(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
+ if (!component.getAttributes().isEmpty()
+ || !component.getComponents().isEmpty()
+ || component.getValue() != null) {
+ writeXmlComponent(xmlWriter, component);
+ }
}
- @Override
- public void setLoggerContext(final LoggerContext loggerContext) {
- this.loggerContext = loggerContext;
+ private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component)
+ throws XMLStreamException {
+ if (!component.getComponents().isEmpty() || component.getValue() != null) {
+ xmlWriter.writeStartElement(component.getPluginType());
+ writeXmlAttributes(xmlWriter, component);
+ for (final Component subComponent : component.getComponents()) {
+ writeXmlComponent(xmlWriter, subComponent);
+ }
+ if (component.getValue() != null) {
+ xmlWriter.writeCharacters(component.getValue());
+ }
+ xmlWriter.writeEndElement();
+ } else {
+ xmlWriter.writeEmptyElement(component.getPluginType());
+ writeXmlAttributes(xmlWriter, component);
+ }
}
- @Override
- public ConfigurationBuilder addRootProperty(final String key, final String value) {
- root.getAttributes().put(key, value);
- return this;
+ private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component)
+ throws XMLStreamException {
+ for (final Map.Entry attribute :
+ component.getAttributes().entrySet()) {
+ xmlWriter.writeAttribute(attribute.getKey(), attribute.getValue());
+ }
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
index c56f92230f3..32a69487d75 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
@@ -20,7 +20,7 @@
* @since 2.4
*/
@Export
-@Version("2.20.2")
+@Version("2.25.0")
package org.apache.logging.log4j.core.config.builder.impl;
import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
index 1563a62e0ec..fae889a28ff 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
@@ -18,7 +18,7 @@
* Configuration using Properties files.
*/
@Export
-@Version("2.20.1")
+@Version("2.25.0")
package org.apache.logging.log4j.core.config.properties;
import org.osgi.annotation.bundle.Export;
diff --git a/src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml b/src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml
new file mode 100644
index 00000000000..7757a5184f3
--- /dev/null
+++ b/src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ Makes DefaultConfigurationBuilder more extensible and updates BuiltConfiguration
+ to more closely match behavior of other Configuration implementations.
+
+