diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java index 4e5f1f3f9b5..26edfbc33d8 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java @@ -23,7 +23,6 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.plugins.di.Binding; /** * Base Configuration for Log4j 1. @@ -64,7 +63,7 @@ public BuilderManager getBuilderManager() { */ @Override public void initialize() { - instanceFactory.registerBinding(Binding.from(Configuration.KEY).toInstance(this)); + instanceFactory.registerBinding(Configuration.KEY, () -> this); getStrSubstitutor().setConfiguration(this); getConfigurationStrSubstitutor().setConfiguration(this); super.getScheduler().start(); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java index 56e8b0a3eb0..4ef5a610076 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java @@ -29,9 +29,9 @@ import org.apache.log4j.spi.LoggingEvent; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.Log4jPropertyKey; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.ConfigurationFactoryType; import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ObjectMessage; @@ -47,6 +47,7 @@ /** * Tests of Category. */ +@ConfigurationFactoryType(BasicConfigurationFactory.class) public class CategoryTest { private static final String VERSION1_APPENDER_NAME = "Version1List"; @@ -58,15 +59,11 @@ public class CategoryTest { public static void setupClass() { appender.start(); version1Appender.setName(VERSION1_APPENDER_NAME); - System.setProperty( - Log4jPropertyKey.CONFIG_CONFIGURATION_FACTORY_CLASS_NAME.getSystemKey(), - BasicConfigurationFactory.class.getName()); } @AfterAll public static void cleanupClass() { appender.stop(); - System.clearProperty(Log4jPropertyKey.CONFIG_CONFIGURATION_FACTORY_CLASS_NAME.getSystemKey()); } @BeforeEach diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java index 8071b91b3f5..6cb6c7ec55e 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java @@ -31,10 +31,9 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.impl.Log4jPropertyKey; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.junit.jupiter.api.AfterAll; +import org.apache.logging.log4j.core.test.junit.ConfigurationFactoryType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -42,6 +41,7 @@ /** * Used for internal unit testing the Logger class. */ +@ConfigurationFactoryType(BasicConfigurationFactory.class) public class LoggerTest { Appender a1; @@ -64,15 +64,6 @@ public static void setUpClass() { rbCH = ResourceBundle.getBundle("L7D", new Locale("fr", "CH")); assertNotNull(rbCH, "Got a null resource bundle."); - - System.setProperty( - Log4jPropertyKey.CONFIG_CONFIGURATION_FACTORY_CLASS_NAME.getSystemKey(), - BasicConfigurationFactory.class.getName()); - } - - @AfterAll - public static void tearDownClass() { - System.clearProperty(Log4jPropertyKey.CONFIG_CONFIGURATION_FACTORY_CLASS_NAME.getSystemKey()); } @AfterEach diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java index cbc2d63b445..8ff94674e75 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java @@ -28,7 +28,7 @@ import org.apache.log4j.spi.LoggingEvent; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.LegacyLoggerContextSource; import org.junit.jupiter.api.Test; /** @@ -37,7 +37,7 @@ public class AutoConfigTest { @Test - @LoggerContextSource(value = "log4j.xml", v1config = true) + @LegacyLoggerContextSource("log4j.xml") public void testListAppender(final org.apache.logging.log4j.core.LoggerContext context) { final Logger logger = LogManager.getLogger("test"); logger.debug("This is a test of the root logger"); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java index a38cb0118fd..8ddb6fdd6f2 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java @@ -16,7 +16,11 @@ */ package org.apache.log4j.config; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.as; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.MAP; +import static org.assertj.core.api.InstanceOfAssertFactories.STRING; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.HashMap; import java.util.List; @@ -29,7 +33,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.LegacyLoggerContextSource; import org.apache.logging.log4j.test.junit.UsingThreadContextMap; import org.junit.jupiter.api.Test; @@ -40,14 +44,14 @@ public class MapRewriteAppenderTest { @Test - @LoggerContextSource(value = "log4j1-mapRewrite.xml", v1config = true) + @LegacyLoggerContextSource("log4j1-mapRewrite.xml") public void testRewrite() { final Logger logger = LogManager.getLogger("test"); final Map map = new HashMap<>(); map.put("message", "This is a test"); map.put("hello", "world"); logger.debug(map); - final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + final LoggerContext context = LoggerContext.getContext(false); final Configuration configuration = context.getConfiguration(); final Map appenders = configuration.getAppenders(); ListAppender eventAppender = null; @@ -58,9 +62,13 @@ public void testRewrite() { } assertNotNull(eventAppender, "No Event Appender"); final List events = eventAppender.getEvents(); - assertTrue(events != null && events.size() > 0, "No events"); - assertNotNull(events.get(0).getProperties(), "No properties in the event"); - assertTrue(events.get(0).getProperties().containsKey("hello"), "Key was not inserted"); - assertEquals("world", events.get(0).getProperties().get("hello"), "Key value is incorrect"); + assertThat(events) + .isNotNull() + .isNotEmpty() + .first() + .extracting(LoggingEvent::getProperties, as(MAP)) + .containsKey("hello") + .extractingByKey("hello", as(STRING)) + .isEqualTo("world"); } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java index a0c0882ec69..4d26783b890 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java @@ -21,11 +21,10 @@ import java.nio.file.Paths; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.LegacyLoggerContextSource; import org.apache.logging.log4j.test.junit.CleanUpDirectories; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; /** * Test configuration from Properties. @@ -34,19 +33,10 @@ public class PropertiesRollingWithPropertiesTest { private static final String TEST_DIR = "target/PropertiesRollingWithPropertiesTest"; - @BeforeAll - static void beforeAll() { - System.setProperty("test.directory", TEST_DIR); - } - - @AfterAll - static void afterAll() { - System.clearProperty("test.directory"); - } - @Test + @SetSystemProperty(key = "test.directory", value = TEST_DIR) @CleanUpDirectories(TEST_DIR) - @LoggerContextSource(value = "log4j1-rolling-properties.properties", v1config = true) + @LegacyLoggerContextSource("log4j1-rolling-properties.properties") public void testProperties(final LoggerContext context) throws Exception { final Logger logger = context.getLogger("test"); logger.debug("This is a test of the root logger"); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java index b340af2b71c..9ebece4525a 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java @@ -16,7 +16,9 @@ */ package org.apache.log4j.config; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Map; @@ -29,7 +31,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.LegacyLoggerContextSource; import org.apache.logging.log4j.test.junit.UsingThreadContextMap; import org.junit.jupiter.api.Test; @@ -40,7 +42,7 @@ public class RewriteAppenderTest { @Test - @LoggerContextSource(value = "log4j1-rewrite.xml", v1config = true) + @LegacyLoggerContextSource("log4j1-rewrite.xml") public void testRewrite(final LoggerContext context) { final Logger logger = LogManager.getLogger("test"); ThreadContext.put("key1", "This is a test"); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java index e5f2538a645..9503b3b8ca8 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java @@ -21,11 +21,10 @@ import java.nio.file.Paths; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.LegacyLoggerContextSource; import org.apache.logging.log4j.test.junit.CleanUpDirectories; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; /** * Test configuration from Properties. @@ -34,19 +33,10 @@ public class XmlRollingWithPropertiesTest { private static final String TEST_DIR = "target/XmlRollingWithPropertiesTest"; - @BeforeAll - static void beforeAll() { - System.setProperty("test.directory", TEST_DIR); - } - - @AfterAll - static void afterAll() { - System.clearProperty("test.directory"); - } - @Test + @SetSystemProperty(key = "test.directory", value = TEST_DIR) @CleanUpDirectories(TEST_DIR) - @LoggerContextSource(value = "log4j1-rolling-properties.xml", v1config = true) + @LegacyLoggerContextSource("log4j1-rolling-properties.xml") public void testProperties(final LoggerContext context) { // ${test.directory}/logs/etl.log final Logger logger = context.getLogger("test"); diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java index eed1952c324..7be65155f5c 100644 --- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java @@ -112,8 +112,4 @@ public static void testPut() { ThreadContext.put("testKey", "testValue"); assertEquals("testValue", ThreadContext.get("testKey")); } - - public static void reset() { - ThreadContext.init(); - } } diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java index 0e82c01b8e1..da3e37c1c8a 100644 --- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java @@ -16,20 +16,22 @@ */ package org.apache.logging.log4j.test.junit; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import org.apache.logging.log4j.lang.NullMarked; +import org.apache.logging.log4j.lang.Nullable; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.platform.commons.PreconditionViolationException; +@NullMarked public class ExtensionContextAnchor implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback, AfterEachCallback { public static Namespace LOG4J2_NAMESPACE = Namespace.create("org.apache.logging.log4j.junit"); - private static final ThreadLocal EXTENSION_CONTEXT = new InheritableThreadLocal<>(); + private static final ThreadLocal<@Nullable ExtensionContext> EXTENSION_CONTEXT = new InheritableThreadLocal<>(); private static void bind(final ExtensionContext context) { EXTENSION_CONTEXT.set(context); @@ -39,27 +41,45 @@ private static void unbind(final ExtensionContext context) { EXTENSION_CONTEXT.set(context.getParent().orElse(null)); } - public static ExtensionContext getContext() { + public static @Nullable ExtensionContext getContext() { return EXTENSION_CONTEXT.get(); } - public static ExtensionContext getContext(final ExtensionContext context) { + public static @Nullable ExtensionContext getContext(final @Nullable ExtensionContext context) { return context != null ? context : EXTENSION_CONTEXT.get(); } - public static T getAttribute(final Object key, final Class clazz, final ExtensionContext context) { + public static ExtensionContext getRequiredContext(final @Nullable ExtensionContext context) { final ExtensionContext actualContext = getContext(context); - assertNotNull(actualContext, "missing ExtensionContext"); - return actualContext.getStore(LOG4J2_NAMESPACE).get(key, clazz); + if (actualContext == null) { + throw new PreconditionViolationException("No ExtensionContext available"); + } + return actualContext; } - public static void setAttribute(final Object key, final Object value, final ExtensionContext context) { - final ExtensionContext actualContext = getContext(context); - assertNotNull(actualContext, "missing ExtensionContext"); - actualContext.getStore(LOG4J2_NAMESPACE).put(key, value); + public static ExtensionContext.Store getRequiredStore(final @Nullable ExtensionContext context) { + return getRequiredContext(context).getStore(LOG4J2_NAMESPACE); + } + + public static @Nullable T getAttribute( + final Object key, final Class clazz, final @Nullable ExtensionContext context) { + return getRequiredStore(context).get(key, clazz); + } + + public static T getRequiredAttribute( + final Object key, final Class clazz, final @Nullable ExtensionContext context) { + final T attribute = getRequiredStore(context).get(key, clazz); + if (attribute == null) { + throw new PreconditionViolationException("Unable to find instance of " + clazz.getCanonicalName()); + } + return attribute; + } + + public static void setAttribute(final Object key, final Object value, final @Nullable ExtensionContext context) { + getRequiredStore(context).put(key, value); } - public static void removeAttribute(final Object key, final ExtensionContext context) { + public static void removeAttribute(final Object key, final @Nullable ExtensionContext context) { final ExtensionContext actualContext = getContext(context); if (actualContext != null) { actualContext.getStore(LOG4J2_NAMESPACE).remove(key); diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TypeBasedParameterResolver.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TypeBasedParameterResolver.java deleted file mode 100644 index 970c1aff73c..00000000000 --- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TypeBasedParameterResolver.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.test.junit; - -import java.lang.reflect.Type; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; - -public abstract class TypeBasedParameterResolver implements ParameterResolver { - - private final Type supportedParameterType; - - public TypeBasedParameterResolver(final Type supportedParameterType) { - this.supportedParameterType = supportedParameterType; - } - - @Override - public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - return this.supportedParameterType.equals( - parameterContext.getParameter().getParameterizedType()); - } - - @Override - public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException; -} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java index 387e86c8870..c1cb6d77516 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java @@ -40,12 +40,15 @@ import org.apache.logging.log4j.message.StringFormatterMessageFactory; import org.apache.logging.log4j.message.StructuredDataMessage; import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.TestLoggerContextFactory; +import org.apache.logging.log4j.test.junit.LoggerContextFactoryExtension; import org.apache.logging.log4j.test.junit.Resources; import org.apache.logging.log4j.test.junit.UsingThreadContextMap; import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junitpioneer.jupiter.ReadsSystemProperty; @@ -54,6 +57,10 @@ @ReadsSystemProperty public class LoggerTest { + @RegisterExtension + public static final LoggerContextFactoryExtension EXTENSION = + new LoggerContextFactoryExtension(new TestLoggerContextFactory()); + private static class TestParameterizedMessageFactory { // empty } @@ -75,7 +82,7 @@ public void builder() { assertThat( "Incorrect message 1", results.get(0), - equalTo(" DEBUG org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:71) Hello")); + equalTo(" DEBUG org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:78) Hello")); assertThat("Incorrect message 2", results.get(1), equalTo("test ERROR Hello John")); assertThat( "Incorrect message 3", @@ -84,7 +91,7 @@ public void builder() { assertThat( "Throwable incorrect in message 3", results.get(2), - containsString("org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:73)")); + containsString("org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:80)")); } @Test diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderManagerResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderManagerResolver.java deleted file mode 100644 index 7a22951f8ad..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderManagerResolver.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; - -import java.lang.reflect.Parameter; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.appender.AbstractManager; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.plugins.di.Keys; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.support.ReflectionSupport; - -/** - * Resolves parameters that extend {@link AbstractManager} and have a {@link org.apache.logging.log4j.plugins.Named} - * parameter of the corresponding appender that uses the manager. - */ -class AppenderManagerResolver implements ParameterResolver { - @Override - public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - final Parameter parameter = parameterContext.getParameter(); - return AbstractManager.class.isAssignableFrom(parameter.getType()) && Keys.hasName(parameter); - } - - @Override - public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - final LoggerContext loggerContext = getLoggerContext(extensionContext); - if (loggerContext == null) { - throw new ParameterResolutionException("No LoggerContext defined"); - } - final Configuration configuration = loggerContext.getConfiguration(); - final Parameter parameter = parameterContext.getParameter(); - final String name = Keys.getName(parameter); - final Appender appender = configuration.getAppender(name); - if (appender == null) { - throw new ParameterResolutionException("No appender named " + name); - } - final Class appenderClass = appender.getClass(); - final Object manager = ReflectionSupport.findMethod(appenderClass, "getManager") - .map(method -> ReflectionSupport.invokeMethod(method, appender)) - .orElseThrow(() -> - new ParameterResolutionException("Cannot find getManager() on appender " + appenderClass)); - final Class parameterType = parameter.getType(); - if (!parameterType.isInstance(manager)) { - throw new ParameterResolutionException( - "Expected type " + parameterType + " but got type " + manager.getClass()); - } - return manager; - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.java deleted file mode 100644 index a9b951d7fec..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; - -import java.lang.reflect.Parameter; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.plugins.di.Keys; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; - -/** - * Resolves parameters that implement {@link Appender} and have a {@link org.apache.logging.log4j.plugins.Named} - * value of the name of the appender. - */ -class AppenderResolver implements ParameterResolver { - @Override - public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - final Parameter parameter = parameterContext.getParameter(); - return Appender.class.isAssignableFrom(parameter.getType()) && Keys.hasName(parameter); - } - - @Override - public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - final LoggerContext loggerContext = getLoggerContext(extensionContext); - if (loggerContext == null) { - throw new ParameterResolutionException("No LoggerContext defined"); - } - final String name = Keys.getName(parameterContext.getParameter()); - if (name.isEmpty()) { - throw new ParameterResolutionException("No named annotation present after checking earlier"); - } - final Appender appender = loggerContext.getConfiguration().getAppender(name); - if (appender == null) { - throw new ParameterResolutionException("No appender named " + name); - } - return appender; - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryType.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryType.java index cb5724f3483..01f67048266 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryType.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryType.java @@ -22,15 +22,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.junit.jupiter.api.extension.ExtendWith; /** - * Specifies a particular {@link ConfigurationFactory} class to use for a test instead of the default. + * Specifies a particular {@link ConfigurationFactory} class to use for a test class or method instead of the default. */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Target({ElementType.TYPE, ElementType.METHOD}) @Inherited -@ExtendWith(ConfigurationFactoryTypeCallback.class) +@Log4jTest public @interface ConfigurationFactoryType { Class value(); } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryTypeCallback.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryTypeCallback.java deleted file mode 100644 index ac14b96985e..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationFactoryTypeCallback.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.impl.Log4jContextFactory; -import org.apache.logging.log4j.plugins.di.Binding; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; -import org.apache.logging.log4j.plugins.di.DI; -import org.apache.logging.log4j.spi.LoggingSystem; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.support.AnnotationSupport; - -class ConfigurationFactoryTypeCallback implements BeforeAllCallback, AfterAllCallback { - @Override - public void beforeAll(final ExtensionContext context) throws Exception { - AnnotationSupport.findAnnotation(context.getTestClass(), ConfigurationFactoryType.class) - .map(ConfigurationFactoryType::value) - .ifPresent(configurationFactoryType -> { - final ConfigurableInstanceFactory factory = DI.createInitializedFactory(); - factory.registerBinding( - Binding.from(ConfigurationFactory.KEY).to(factory.getFactory(configurationFactoryType))); - final Log4jContextFactory contextFactory = factory.getInstance(Log4jContextFactory.class); - LoggingSystem.getInstance().setLoggerContextFactory(contextFactory); - }); - } - - @Override - public void afterAll(final ExtensionContext context) throws Exception { - if (AnnotationSupport.isAnnotated(context.getTestClass(), ConfigurationFactoryType.class)) { - LoggingSystem.getInstance().setLoggerContextFactory(null); - } - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.java deleted file mode 100644 index 8f29d398305..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; - -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; - -class ConfigurationResolver extends TypeBasedParameterResolver { - - public ConfigurationResolver() { - super(Configuration.class); - } - - @Override - public Configuration resolveParameter( - final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - final LoggerContext loggerContext = getLoggerContext(extensionContext); - if (loggerContext == null) { - throw new ParameterResolutionException("No LoggerContext defined"); - } - return loggerContext.getConfiguration(); - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java deleted file mode 100644 index 28e69ade548..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.impl.Log4jContextFactory; -import org.apache.logging.log4j.core.selector.ContextSelector; -import org.apache.logging.log4j.plugins.di.Binding; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; -import org.apache.logging.log4j.plugins.di.DI; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.platform.commons.support.AnnotationSupport; - -public class ContextSelectorCallback implements BeforeAllCallback, AfterAllCallback { - @Override - public void beforeAll(final ExtensionContext context) throws Exception { - AnnotationSupport.findAnnotation(context.getTestClass(), ContextSelectorType.class) - .map(ContextSelectorType::value) - .ifPresent(contextSelectorClass -> { - final ConfigurableInstanceFactory instanceFactory = DI.createFactory(); - instanceFactory.registerBinding( - Binding.from(ContextSelector.KEY).to(instanceFactory.getFactory(contextSelectorClass))); - DI.initializeFactory(instanceFactory); - final Log4jContextFactory factory = instanceFactory.getInstance(Log4jContextFactory.class); - LogManager.setFactory(factory); - }); - } - - @Override - public void afterAll(final ExtensionContext context) throws Exception { - AnnotationSupport.findAnnotation(context.getTestClass(), ContextSelectorType.class) - .ifPresent(ignored -> LogManager.setFactory(null)); - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java index 8ea73c65f47..af5c6c04ff5 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java @@ -16,26 +16,21 @@ */ package org.apache.logging.log4j.core.test.junit; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.apache.logging.log4j.core.selector.ContextSelector; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.extension.ExtendWith; @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) @Inherited -@Tag("functional") -@ExtendWith(ContextSelectorCallback.class) +@Log4jTest public @interface ContextSelectorType { /** - * Specifies the {@link ContextSelector} class to use for this test class. + * Specifies the {@link ContextSelector} class to use for this test class or method. */ Class value(); } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LegacyLoggerContextSource.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LegacyLoggerContextSource.java new file mode 100644 index 00000000000..13a7df5c6fa --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LegacyLoggerContextSource.java @@ -0,0 +1,37 @@ +/* + * 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.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Alternative test extension to {@link LoggerContextSource} that uses a v1-style configuration file. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Inherited +@LoggingResolvers +public @interface LegacyLoggerContextSource { + /** Path to configuration file to use in test. */ + String value(); +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jExtension.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jExtension.java new file mode 100644 index 00000000000..1cb0c2d93b7 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jExtension.java @@ -0,0 +1,255 @@ +/* + * 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.test.junit; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.impl.Log4jPropertyKey; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.ReflectionSupport; + +/** + * Test extension for setting up various Log4j subsystems. This extension is enabled by using one or more + * test extension annotations that have been annotated with {@link Log4jTest}. When Log4j test extensions are + * present on test classes, then these extensions apply to all tests within the class and are set up before + * all tests (via {@link BeforeAllCallback}). When Log4j test extensions are present on test methods, then + * these extensions apply to the individually-annotated tests. + * + * @see LoggerContextSource + * @see LegacyLoggerContextSource + * @see Log4jTest + * @see LoggingResolvers + */ +class Log4jExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { + private static final String FQCN = Log4jExtension.class.getName(); + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(Log4jExtension.class); + private static final List EXTENSIONS = List.of(".xml", ".json", ".properties"); + + @Override + public void beforeAll(final ExtensionContext context) { + final Class testClass = context.getRequiredTestClass(); + if (AnnotationSupport.isAnnotated(testClass, Log4jTest.class)) { + final ExtensionContext.Store store = context.getStore(NAMESPACE); + final DI.FactoryBuilder builder = store.getOrComputeIfAbsent(DI.FactoryBuilder.class); + configure(builder, store, testClass, testClass); + } + } + + @Override + public void beforeEach(final ExtensionContext context) { + final Class testClass = context.getRequiredTestClass(); + final Method testMethod = context.getRequiredTestMethod(); + final ExtensionContext.Store store = context.getStore(NAMESPACE); + if (AnnotationSupport.isAnnotated(testMethod, Log4jTest.class)) { + final DI.FactoryBuilder builder; + if (AnnotationSupport.isAnnotated(testClass, Log4jTest.class)) { + builder = store.get(DI.FactoryBuilder.class, DI.FactoryBuilder.class) + .copy(); + } else { + builder = store.getOrComputeIfAbsent(DI.FactoryBuilder.class); + } + configure(builder, store, testMethod, testClass); + } + AnnotationSupport.findAnnotation(testClass, LoggerContextSource.class) + .map(LoggerContextSource::reconfigure) + .filter(ReconfigurationPolicy.BEFORE_EACH::equals) + .ifPresent(ignored -> getRequiredLoggerContext(store).reconfigure()); + } + + @Override + public void afterEach(final ExtensionContext context) { + AnnotationSupport.findAnnotation(context.getTestClass(), LoggerContextSource.class) + .map(LoggerContextSource::reconfigure) + .filter(ReconfigurationPolicy.AFTER_EACH::equals) + .ifPresent(ignored -> getRequiredLoggerContext(context).reconfigure()); + } + + private static void configure( + final DI.FactoryBuilder builder, + final ExtensionContext.Store store, + final AnnotatedElement element, + final Class testClass) { + final ConfigurableInstanceFactory instanceFactory = configureInstanceFactory(builder, element); + final Log4jContextFactory factory = instanceFactory.getInstance(Log4jContextFactory.class); + store.put(LoggerContextFactoryHolder.class, new LoggerContextFactoryHolder(factory)); + if (AnnotationSupport.isAnnotated(element, LoggingResolvers.class)) { + AnnotationSupport.findAnnotation(element, LoggerContextSource.class) + .map(source -> configureLoggerContextSource(source, testClass, factory)) + .or(() -> AnnotationSupport.findAnnotation(element, LegacyLoggerContextSource.class) + .map(source -> configureLegacyLoggerContextSource(source, testClass, factory))) + .ifPresent(provider -> store.put(LoggerContextProvider.class, provider)); + } + } + + private static ConfigurableInstanceFactory configureInstanceFactory( + final DI.FactoryBuilder builder, final AnnotatedElement element) { + AnnotationSupport.findRepeatableAnnotations(element, TestBinding.class) + .forEach(testBinding -> registerTestBinding(builder, testBinding)); + AnnotationSupport.findAnnotation(element, ContextSelectorType.class) + .map(ContextSelectorType::value) + .ifPresent(clazz -> builder.addInitialBindingFrom(ContextSelector.KEY) + .toFunction(instanceFactory -> instanceFactory.getFactory(clazz))); + AnnotationSupport.findAnnotation(element, ConfigurationFactoryType.class) + .map(ConfigurationFactoryType::value) + .ifPresent(clazz -> builder.addBindingFrom(ConfigurationFactory.KEY) + .toFunction(instanceFactory -> instanceFactory.getFactory(clazz))); + return builder.build(); + } + + private static void registerTestBinding(final DI.FactoryBuilder builder, final TestBinding testBinding) { + final Class apiClass = testBinding.api(); + final Class implementation = testBinding.implementation(); + final String implementationClassName = testBinding.implementationClassName(); + final Class implementationClass; + if (!implementationClassName.isEmpty()) { + implementationClass = ReflectionSupport.tryToLoadClass(implementationClassName) + .getOrThrow(e -> new IllegalArgumentException( + String.format( + "Unable to configure test binding apiClassName=%s, implementationClassName=%s", + apiClass, implementationClassName), + e)); + } else { + implementationClass = implementation; + } + register(builder, apiClass, implementationClass); + } + + private static void register( + final DI.FactoryBuilder builder, final Class apiClass, final Class implementationClass) { + final Class implementation = implementationClass.asSubclass(apiClass); + builder.addInitialBindingFrom(apiClass) + .toFunction(instanceFactory -> instanceFactory.getFactory(implementation)); + } + + private static LoggerContextProvider configureLoggerContextSource( + final LoggerContextSource source, final Class testClass, final Log4jContextFactory factory) { + final String configLocation = source.value(); + final URI configUri; + if (configLocation.isEmpty()) { + final URL configUrl = findTestConfiguration(testClass); + if (configUrl != null) { + try { + configUri = configUrl.toURI(); + } catch (final URISyntaxException e) { + throw new ExtensionConfigurationException("Invalid configuration location", e); + } + } else { + configUri = null; + } + } else { + configUri = NetUtils.toURI(configLocation); + } + final LoggerContext loggerContext = + factory.getContext(FQCN, testClass.getClassLoader(), null, false, configUri, testClass.getSimpleName()); + if (loggerContext == null) { + throw new ExtensionConfigurationException("Unable to set up LoggerContext from config URI " + configUri); + } + return new LoggerContextResource(loggerContext, source.timeout(), source.unit()); + } + + private static LoggerContextProvider configureLegacyLoggerContextSource( + final LegacyLoggerContextSource source, final Class testClass, final Log4jContextFactory factory) { + final String configLocation = source.value(); + System.setProperty(Log4jPropertyKey.CONFIG_V1_FILE_NAME.getSystemKey(), configLocation); + System.setProperty(Log4jPropertyKey.CONFIG_V1_COMPATIBILITY_ENABLED.getSystemKey(), "true"); + final String contextName = testClass.getSimpleName(); + final LoggerContext context = + factory.getContext(FQCN, testClass.getClassLoader(), null, false, (URI) null, contextName); + if (context == null) { + throw new ExtensionConfigurationException( + "Unable to set up LoggerContext from v1 config location " + configLocation); + } + final Runnable cleaner = () -> { + context.close(); + System.clearProperty(Log4jPropertyKey.CONFIG_V1_FILE_NAME.getSystemKey()); + System.clearProperty(Log4jPropertyKey.CONFIG_V1_COMPATIBILITY_ENABLED.getSystemKey()); + }; + return new LoggerContextResource(context, cleaner); + } + + private static URL findTestConfiguration(final Class testClass) { + for (Class clazz = testClass; clazz != null; clazz = clazz.getSuperclass()) { + final List baseFileNames = + List.of(clazz.getSimpleName(), clazz.getName().replaceAll("[.$]", "/")); + for (final String baseFileName : baseFileNames) { + for (final String extension : EXTENSIONS) { + final URL url = clazz.getResource(baseFileName + extension); + if (url != null) { + return url; + } + } + } + } + return null; + } + + static LoggerContext getRequiredLoggerContext(final ExtensionContext context) { + return getRequiredLoggerContext(context.getStore(NAMESPACE)); + } + + private static LoggerContext getRequiredLoggerContext(final ExtensionContext.Store store) { + final LoggerContextProvider provider = store.get(LoggerContextProvider.class, LoggerContextProvider.class); + if (provider == null) { + throw new PreconditionViolationException("Unable to find instance of " + LoggerContextProvider.class); + } + return provider.loggerContext(); + } + + private record LoggerContextFactoryHolder(LoggerContextFactory factory) + implements ExtensionContext.Store.CloseableResource { + LoggerContextFactoryHolder { + LogManager.setFactory(factory); + } + + @Override + public void close() { + LogManager.setFactory(null); + } + } + + private record LoggerContextResource(LoggerContext loggerContext, Runnable cleaner) + implements LoggerContextProvider, ExtensionContext.Store.CloseableResource { + LoggerContextResource(LoggerContext loggerContext, long shutdownTimeout, TimeUnit unit) { + this(loggerContext, () -> loggerContext.stop(shutdownTimeout, unit)); + } + + @Override + public void close() { + cleaner.run(); + } + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jTest.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jTest.java new file mode 100644 index 00000000000..0cdbf78e93d --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jTest.java @@ -0,0 +1,37 @@ +/* + * 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.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotates a test extension annotation to indicate that it sets up Log4j fixtures. + */ +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Tag("functional") +@ExtendWith(Log4jExtension.class) +public @interface Log4jTest {} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextProvider.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextProvider.java new file mode 100644 index 00000000000..5bea128a15e --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextProvider.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.LoggerContext; + +public interface LoggerContextProvider { + /** + * Returns the logger context for the configured test scope. + */ + LoggerContext loggerContext(); +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java deleted file mode 100644 index 1389f6f99af..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.LoggerContextAccessor; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.impl.Log4jContextFactory; -import org.apache.logging.log4j.core.selector.ContextSelector; -import org.apache.logging.log4j.core.util.NetUtils; -import org.apache.logging.log4j.plugins.di.Binding; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; -import org.apache.logging.log4j.plugins.di.DI; -import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; -import org.junit.jupiter.api.extension.ExtensionContextException; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.platform.commons.support.AnnotationSupport; - -class LoggerContextResolver extends TypeBasedParameterResolver - implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { - private static final String FQCN = LoggerContextResolver.class.getName(); - private static final Namespace BASE_NAMESPACE = Namespace.create(LoggerContext.class); - - public LoggerContextResolver() { - super(LoggerContext.class); - } - - @Override - public void beforeAll(final ExtensionContext context) throws Exception { - final Class testClass = context.getRequiredTestClass(); - AnnotationSupport.findAnnotation(testClass, LoggerContextSource.class) - .ifPresent(testSource -> setUpLoggerContext(testSource, context)); - } - - @Override - public void beforeEach(final ExtensionContext context) throws Exception { - final Class testClass = context.getRequiredTestClass(); - if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { - final Store testClassStore = context.getStore(BASE_NAMESPACE.append(testClass)); - final LoggerContextAccessor accessor = - testClassStore.get(LoggerContextAccessor.class, LoggerContextAccessor.class); - if (accessor == null) { - throw new IllegalStateException( - "Specified @LoggerContextSource but no LoggerContext found for test class " - + testClass.getCanonicalName()); - } - if (testClassStore.get(ReconfigurationPolicy.class, ReconfigurationPolicy.class) - == ReconfigurationPolicy.BEFORE_EACH) { - accessor.getLoggerContext().reconfigure(); - } - } - AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), LoggerContextSource.class) - .ifPresent(source -> { - final LoggerContext loggerContext = setUpLoggerContext(source, context); - if (source.reconfigure() == ReconfigurationPolicy.BEFORE_EACH) { - loggerContext.reconfigure(); - } - }); - } - - @Override - public void afterEach(final ExtensionContext context) throws Exception { - final Class testClass = context.getRequiredTestClass(); - if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { - final Store testClassStore = getTestStore(context); - if (testClassStore.get(ReconfigurationPolicy.class, ReconfigurationPolicy.class) - == ReconfigurationPolicy.AFTER_EACH) { - testClassStore - .get(LoggerContextAccessor.class, LoggerContextAccessor.class) - .getLoggerContext() - .reconfigure(); - } - } - } - - @Override - public LoggerContext resolveParameter( - final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - return getLoggerContext(extensionContext); - } - - static LoggerContext getLoggerContext(final ExtensionContext context) { - final Store store = getTestStore(context); - final LoggerContextAccessor accessor = store.get(LoggerContextAccessor.class, LoggerContextAccessor.class); - assertNotNull(accessor); - return accessor.getLoggerContext(); - } - - private static Store getTestStore(final ExtensionContext context) { - return context.getStore(BASE_NAMESPACE.append(context.getRequiredTestClass())); - } - - private static LoggerContext setUpLoggerContext( - final LoggerContextSource source, final ExtensionContext extensionContext) { - final String displayName = extensionContext.getDisplayName(); - final ConfigurableInstanceFactory instanceFactory = DI.createFactory(); - extensionContext.getTestInstance().ifPresent(instanceFactory::registerBundle); - final Class contextSelectorClass = source.selector(); - if (contextSelectorClass != ContextSelector.class) { - instanceFactory.registerBinding( - Binding.from(ContextSelector.KEY).to(instanceFactory.getFactory(contextSelectorClass))); - } - DI.initializeFactory(instanceFactory); - final Log4jContextFactory loggerContextFactory; - if (source.bootstrap()) { - loggerContextFactory = new Log4jContextFactory(instanceFactory); - LogManager.setFactory(loggerContextFactory); - } else { - loggerContextFactory = (Log4jContextFactory) LogManager.getFactory(); - } - final Class testClass = extensionContext.getRequiredTestClass(); - final ClassLoader classLoader = testClass.getClassLoader(); - final Map.Entry injectorContext = - Map.entry(ConfigurableInstanceFactory.class.getName(), instanceFactory); - final String configLocation = getConfigLocation(source, extensionContext); - final URI configUri; - if (source.v1config()) { - System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY.getSystemKey(), configLocation); - configUri = null; // handled by system property - } else { - configUri = NetUtils.toURI(configLocation); - } - final LoggerContext context = - loggerContextFactory.getContext(FQCN, classLoader, injectorContext, false, configUri, displayName); - assertNotNull( - context, () -> "No LoggerContext created for " + testClass + " and config file " + configLocation); - final Store store = getTestStore(extensionContext); - store.put(ReconfigurationPolicy.class, source.reconfigure()); - store.put(LoggerContextAccessor.class, new ContextHolder(context, source.timeout(), source.unit())); - return context; - } - - private static String getConfigLocation(final LoggerContextSource source, final ExtensionContext extensionContext) { - final String value = source.value(); - if (value.isEmpty()) { - Class clazz = extensionContext.getRequiredTestClass(); - while (clazz != null) { - final URL url = clazz.getResource(clazz.getSimpleName() + ".xml"); - if (url != null) { - try { - return url.toURI().toString(); - } catch (URISyntaxException e) { - throw new ExtensionContextException("An error occurred accessing the configuration.", e); - } - } - clazz = clazz.getSuperclass(); - } - return extensionContext.getRequiredTestClass().getName().replaceAll("[.$]", "/") + ".xml"; - } - return value; - } - - private static final class ContextHolder implements Store.CloseableResource, LoggerContextAccessor { - private final LoggerContext context; - private final long shutdownTimeout; - private final TimeUnit unit; - - private ContextHolder(final LoggerContext context, final long shutdownTimeout, final TimeUnit unit) { - this.context = context; - this.shutdownTimeout = shutdownTimeout; - this.unit = unit; - } - - @Override - public LoggerContext getLoggerContext() { - return context; - } - - @Override - public void close() throws Throwable { - try { - context.stop(shutdownTimeout, unit); - } finally { - System.clearProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL.getSystemKey()); - System.clearProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY.getSystemKey()); - } - } - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java index 0033bcfe6aa..58c40c0f059 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java @@ -32,7 +32,6 @@ import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.core.util.NetUtils; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.status.StatusLogger; @@ -132,13 +131,13 @@ public void evaluate() throws Throwable { System.setProperty(SYS_PROP_KEY_CLASS_NAME, description.getClassName()); final String displayName = description.getDisplayName(); System.setProperty(SYS_PROP_KEY_DISPLAY_NAME, displayName); - final ConfigurableInstanceFactory instanceFactory = DI.createFactory(); + final DI.FactoryBuilder builder = DI.builder(); if (contextSelectorClass != null) { - instanceFactory.registerBinding( - Binding.from(ContextSelector.KEY).to(instanceFactory.getFactory(contextSelectorClass))); + builder.addInitialBindingFrom(ContextSelector.KEY) + .toFunction(instanceFactory -> instanceFactory.getFactory(contextSelectorClass)); } - DI.initializeFactory(instanceFactory); - final Log4jContextFactory factory = new Log4jContextFactory(instanceFactory); + final ConfigurableInstanceFactory instanceFactory = builder.build(); + final Log4jContextFactory factory = instanceFactory.getInstance(Log4jContextFactory.class); LogManager.setFactory(factory); final String fqcn = getClass().getName(); final ClassLoader classLoader = description.getTestClass().getClassLoader(); diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java index e5e159b8044..6966d6c9f74 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java @@ -28,15 +28,12 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractManager; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; -import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.test.junit.TempLoggingDirectory; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; /** - * Specifies a configuration file to use for unit tests. This configuration file will be loaded once and used for all tests - * executed in the annotated test class unless otherwise specified by {@link #reconfigure()}. When annotated on a test method, + * Specifies a configuration file to use for unit tests. This configuration file will be loaded for each test + * executed in the annotated test class. When annotated on a test method, * this will override the class-level configuration if provided for that method. By using this JUnit 5 extension, the following * types can be injected into tests via constructor or method parameters: * @@ -52,18 +49,17 @@ * rely on configuration files and production code. * * @since 2.14.0 + * @see org.apache.logging.log4j.test.junit.SetTestProperty + * @see ConfigurationFactoryType + * @see ContextSelectorType + * @see TestBinding */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Inherited -@Tag("functional") @ExtendWith(TempLoggingDirectory.class) -@ExtendWith(LoggerContextResolver.class) -@ExtendWith(ConfigurationResolver.class) -@ExtendWith(AppenderResolver.class) -@ExtendWith(AppenderManagerResolver.class) -@ExtendWith(LoggerResolver.class) +@LoggingResolvers public @interface LoggerContextSource { /** * Specifies the name of the configuration file to use for the annotated test. @@ -84,20 +80,4 @@ * Specifies the time unit {@link #timeout()} is measured in. */ TimeUnit unit() default TimeUnit.SECONDS; - - /** - * Toggles Log4j1 configuration file compatibility mode for XML and properties files. - */ - boolean v1config() default false; - - /** - * Determines whether to bootstrap a fresh LoggerContextFactory. - */ - boolean bootstrap() default false; - - /** - * Overrides the {@link ContextSelector} to use by default. If unset, the test instance can still - * define a context selector factory to override the default {@link ClassLoaderContextSelector}. - */ - Class selector() default ContextSelector.class; } diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerResolver.java deleted file mode 100644 index 90589d32e8f..00000000000 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerResolver.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.core.test.junit; - -import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; - -import java.lang.reflect.Parameter; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.plugins.di.Keys; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; - -class LoggerResolver implements ParameterResolver { - @Override - public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - return parameterContext.getParameter().getType().isAssignableFrom(Logger.class); - } - - @Override - public Logger resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) - throws ParameterResolutionException { - final LoggerContext loggerContext = getLoggerContext(extensionContext); - if (loggerContext == null) { - throw new ParameterResolutionException("No LoggerContext defined"); - } - final String loggerName; - final Parameter parameter = parameterContext.getParameter(); - if (Keys.hasName(parameter)) { - loggerName = Keys.getName(parameter); - } else { - loggerName = extensionContext.getRequiredTestClass().getCanonicalName(); - } - return loggerContext.getLogger(loggerName); - } -} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggingResolvers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggingResolvers.java new file mode 100644 index 00000000000..534a2fef841 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggingResolvers.java @@ -0,0 +1,166 @@ +/* + * 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.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Parameter; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.plugins.di.Keys; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; +import org.junit.platform.commons.support.ReflectionSupport; + +/** + * Annotates a test extension annotation to indicate that it sets up Log4j and can resolve parameters of various kinds. + * These supported types include {@link LoggerContext}, {@link Configuration}, configured {@link Appender} plugins + * with a {@linkplain org.apache.logging.log4j.plugins.Named name}, named {@link AbstractManager} instances, and + * named {@link Logger} instances. + */ +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Log4jTest +@ExtendWith(LoggerContextResolver.class) +@ExtendWith(ConfigurationResolver.class) +@ExtendWith(AppenderResolver.class) +@ExtendWith(AppenderManagerResolver.class) +@ExtendWith(LoggerResolver.class) +public @interface LoggingResolvers {} + +/** + * Handles parameter resolution of a {@link LoggerContext}. + */ +class LoggerContextResolver extends TypeBasedParameterResolver { + @Override + public LoggerContext resolveParameter(final ParameterContext parameterContext, final ExtensionContext context) + throws ParameterResolutionException { + return Log4jExtension.getRequiredLoggerContext(context); + } +} + +/** + * Handles parameter resolution of a {@link Configuration}. + */ +class ConfigurationResolver extends TypeBasedParameterResolver { + @Override + public Configuration resolveParameter( + final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + return Log4jExtension.getRequiredLoggerContext(extensionContext).getConfiguration(); + } +} + +/** + * Resolves parameters that implement {@link Appender} and have a {@link org.apache.logging.log4j.plugins.Named} + * value of the name of the appender. + */ +class AppenderResolver implements ParameterResolver { + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final Parameter parameter = parameterContext.getParameter(); + return Appender.class.isAssignableFrom(parameter.getType()) && Keys.hasName(parameter); + } + + @Override + public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final LoggerContext loggerContext = Log4jExtension.getRequiredLoggerContext(extensionContext); + final String name = Keys.getName(parameterContext.getParameter()); + if (name.isEmpty()) { + throw new ParameterResolutionException("No named annotation present after checking earlier"); + } + final Appender appender = loggerContext.getConfiguration().getAppender(name); + if (appender == null) { + throw new ParameterResolutionException("No appender named " + name); + } + return appender; + } +} + +/** + * Resolves parameters that extend {@link AbstractManager} and have a {@link org.apache.logging.log4j.plugins.Named} + * parameter of the corresponding appender that uses the manager. + */ +class AppenderManagerResolver implements ParameterResolver { + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final Parameter parameter = parameterContext.getParameter(); + return AbstractManager.class.isAssignableFrom(parameter.getType()) && Keys.hasName(parameter); + } + + @Override + public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final Configuration configuration = + Log4jExtension.getRequiredLoggerContext(extensionContext).getConfiguration(); + final Parameter parameter = parameterContext.getParameter(); + final String name = Keys.getName(parameter); + final Appender appender = configuration.getAppender(name); + if (appender == null) { + throw new ParameterResolutionException("No appender named " + name); + } + final Class appenderClass = appender.getClass(); + final Object manager = ReflectionSupport.findMethod(appenderClass, "getManager") + .map(method -> ReflectionSupport.invokeMethod(method, appender)) + .orElseThrow(() -> + new ParameterResolutionException("Cannot find getManager() on appender " + appenderClass)); + final Class parameterType = parameter.getType(); + if (!parameterType.isInstance(manager)) { + throw new ParameterResolutionException( + "Expected type " + parameterType + " but got type " + manager.getClass()); + } + return manager; + } +} + +/** + * Handles parameter resolution for named {@link Logger} instances. These parameters must be either {@link Logger} or + * a supertype. Parameters must have a {@linkplain org.apache.logging.log4j.plugins.Named naming annotation}. + */ +class LoggerResolver implements ParameterResolver { + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.getParameter().getType().isAssignableFrom(Logger.class); + } + + @Override + public Logger resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final Parameter parameter = parameterContext.getParameter(); + final String loggerName = Keys.hasName(parameter) + ? Keys.getName(parameter) + : extensionContext.getRequiredTestClass().getCanonicalName(); + return Log4jExtension.getRequiredLoggerContext(extensionContext).getLogger(loggerName); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/TestBinding.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/TestBinding.java new file mode 100644 index 00000000000..ef644c2ad26 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/TestBinding.java @@ -0,0 +1,66 @@ +/* + * 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.test.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies one or more pairs of {@linkplain org.apache.logging.log4j.plugins.di.Key binding keys} with + * injectable classes to bind in a test. The binding key corresponds to {@link #api()} and the factory will + * use the provided {@link #implementation()} class (or {@link #implementationClassName()} to load reflectively). + * + * @see ConfigurationFactoryType + * @see ContextSelectorType + */ +@Repeatable(TestBinding.Group.class) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Log4jTest +public @interface TestBinding { + /** + * Specifies the {@linkplain org.apache.logging.log4j.plugins.di.Key#forClass(Class) class to use as a key} for this binding. + */ + Class api(); + + /** + * Specifies the implementation class to use as a binding. If left as the default value of {@code Object.class}, + * then the implementation class must be specified as a fully qualified class name in {@link #implementationClassName()}. + */ + Class implementation() default Object.class; + + /** + * Specifies the fully qualified class name to use as a binding. If left blank, then the implementation class must + * be specified in {@link #implementation()}. + */ + String implementationClassName() default ""; + + /** + * Annotation container for multiple {@link TestBinding} annotations. + */ + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface Group { + TestBinding[] value(); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java index 67f8a2b2123..7171c7c43cf 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java @@ -22,7 +22,7 @@ * @see org.apache.logging.log4j.core.test.junit.LoggerContextRule JUnit 4 test rule */ @Export -@Version("2.20.1") +@Version("3.0.0") package org.apache.logging.log4j.core.test.junit; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java index b5187691bb0..0799eeb2bbb 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java @@ -29,8 +29,9 @@ import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.TestBinding; import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Inject; import org.junit.jupiter.api.Test; /** @@ -39,6 +40,7 @@ public class LogEventFactoryTest { @Test + @TestBinding(api = LogEventFactory.class, implementation = TestLogEventFactory.class) @LoggerContextSource(value = "log4j2-config.xml") public void testEvent(@Named("List") final ListAppender app, final LoggerContext context) { final org.apache.logging.log4j.Logger logger = context.getLogger("org.apache.test.LogEventFactory"); @@ -53,6 +55,7 @@ public void testEvent(@Named("List") final ListAppender app, final LoggerContext public static class TestLogEventFactory implements LogEventFactory { private final ContextDataInjector injector; + @Inject public TestLogEventFactory(final ContextDataInjector injector) { this.injector = injector; } @@ -78,9 +81,4 @@ public LogEvent createEvent( .build(); } } - - @Factory - public LogEventFactory logEventFactory(final ContextDataInjector injector) { - return new TestLogEventFactory(injector); - } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java index 40292c924b4..3cbcdaf5f34 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.test.junit.TempLoggingDir; import org.apache.logging.log4j.test.junit.UsingStatusListener; @@ -35,13 +36,14 @@ @Tag("async") @UsingStatusListener +@ContextSelectorType(AsyncLoggerContextSelector.class) public class AsyncLoggerConfigWithAsyncEnabledTest { @TempLoggingDir private static Path loggingPath; @Test - @LoggerContextSource(selector = AsyncLoggerContextSelector.class) + @LoggerContextSource public void testParametersAreAvailableToLayout(final LoggerContext ctx) throws Exception { final File file = loggingPath.resolve("AsyncLoggerConfigTest4.log").toFile(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java index 5072216da38..8a86a4628fc 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java @@ -36,8 +36,6 @@ import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.util.NetUtils; -import org.apache.logging.log4j.plugins.di.Binding; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.spi.DefaultThreadContextMap; import org.apache.logging.log4j.spi.LoggingSystemProperty; @@ -134,11 +132,11 @@ static void doTestAsyncLogWritesToLog( throws Exception { final Path testLoggingPath = loggingPath.resolve(contextImpl.toString()).resolve(asyncMode.toString()); props.setProperty("logging.path", testLoggingPath.toString()); - final ConfigurableInstanceFactory instanceFactory = DI.createFactory(); - instanceFactory.registerBinding( - Binding.from(ContextSelector.KEY).to(instanceFactory.getFactory(asyncMode.contextSelectorType))); - DI.initializeFactory(instanceFactory); - final Log4jContextFactory factory = new Log4jContextFactory(instanceFactory); + final Log4jContextFactory factory = DI.builder() + .addInitialBindingFrom(ContextSelector.KEY) + .toFunction(instanceFactory -> instanceFactory.getFactory(asyncMode.contextSelectorType)) + .build() + .getInstance(Log4jContextFactory.class); final String fqcn = testClass.getName(); final ClassLoader classLoader = testClass.getClassLoader(); final String name = contextImpl.toString() + ' ' + asyncMode; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java index bc25ee72d5d..7f0a872616b 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.plugins.Named; import org.apache.logging.log4j.spi.ExtendedLogger; @@ -33,7 +34,8 @@ * Tests LOG4J2-1688 Multiple loggings of arguments are setting these arguments to null. */ @Tag("async") -@LoggerContextSource(value = "log4j-list.xml", selector = AsyncLoggerContextSelector.class) +@ContextSelectorType(AsyncLoggerContextSelector.class) +@LoggerContextSource("log4j-list.xml") public class Log4j2Jira1688AsyncTest { private static Object[] createArray(final int size) { diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java index 861f3e23b4c..fcdf94b44f1 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java @@ -29,7 +29,6 @@ import org.apache.logging.log4j.core.config.ConfigurationProcessor; import org.apache.logging.log4j.core.config.NullConfiguration; import org.apache.logging.log4j.plugins.Node; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.plugins.model.PluginNamespace; @@ -71,7 +70,7 @@ public void setUp() throws Exception { public void testDoesNotLog_NoParameterThatMatchesElement_message() { final StatusListener listener = mock(StatusListener.class); when(listener.getStatusLevel()).thenReturn(Level.WARN); - instanceFactory.registerBinding(Binding.from(Configuration.KEY).to(NullConfiguration::new)); + instanceFactory.registerBinding(Configuration.KEY, NullConfiguration::new); final StatusLogger logger = StatusLogger.getLogger(); logger.trace("Initializing"); logger.registerListener(listener); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java index c189c0165dc..1b8d0fbdb2d 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java @@ -16,7 +16,12 @@ */ package org.apache.logging.log4j.core.layout; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.util.ArrayList; import java.util.List; @@ -24,7 +29,12 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationProcessor; import org.apache.logging.log4j.core.config.DefaultConfiguration; @@ -40,12 +50,12 @@ import org.apache.logging.log4j.message.StructuredDataCollectionMessage; import org.apache.logging.log4j.message.StructuredDataMessage; import org.apache.logging.log4j.plugins.Node; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.plugins.model.PluginNamespace; import org.apache.logging.log4j.plugins.model.PluginType; import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.Lazy; import org.apache.logging.log4j.util.Strings; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -703,7 +713,7 @@ public void testLayoutBuilderDefaultValues() { node.getAttributes().put("name", "Rfc5242Layout"); final ConfigurableInstanceFactory factory = DI.createInitializedFactory(); - factory.registerBinding(Binding.from(Configuration.KEY).toInstance(configuration)); + factory.registerBinding(Configuration.KEY, Lazy.value(configuration)); final ConfigurationProcessor processor = new ConfigurationProcessor(factory); final Rfc5424Layout object = processor.processNodeTree(node); assertNotNull(object); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java index 4d3cc5a4bf5..c2a1e46fa16 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java @@ -18,71 +18,47 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.apache.logging.log4j.core.impl.Log4jPropertyKey; import org.apache.logging.log4j.core.time.internal.CachedClock; import org.apache.logging.log4j.core.time.internal.CoarseCachedClock; import org.apache.logging.log4j.core.time.internal.SystemClock; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetSystemProperty; public class ClockFactoryTest { - private final ConfigurableInstanceFactory instanceFactory = DI.createFactory(); + private final DI.FactoryBuilder builder = DI.builder(); @Test public void testDefaultIsSystemClock() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(SystemClock.class); + final Clock clock = builder.build().getInstance(Clock.KEY); + assertThat(clock).isInstanceOf(SystemClock.class); } @Test - @SetSystemProperty(key = Log4jPropertyKey.Constant.CONFIG_CLOCK, value = "SystemClock") public void testSpecifySystemClockShort() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(SystemClock.class); + final Clock clock = builder.addInitialBindingFrom(Clock.KEY) + .toSingleton(SystemClock::new) + .build() + .getInstance(Clock.KEY); + assertThat(clock).isInstanceOf(SystemClock.class); } @Test - @SetSystemProperty( - key = Log4jPropertyKey.Constant.CONFIG_CLOCK, - value = "org.apache.logging.log4j.core.time.internal.SystemClock") - public void testSpecifySystemClockLong() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(SystemClock.class); - } - - @Test - @SetSystemProperty(key = Log4jPropertyKey.Constant.CONFIG_CLOCK, value = "CachedClock") public void testSpecifyCachedClockShort() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(CachedClock.class); + final Clock clock = builder.addInitialBindingFrom(Clock.KEY) + .toSingleton(CachedClock::instance) + .build() + .getInstance(Clock.KEY); + assertThat(clock).isInstanceOf(CachedClock.class); } @Test - @SetSystemProperty( - key = Log4jPropertyKey.Constant.CONFIG_CLOCK, - value = "org.apache.logging.log4j.core.time.internal.CachedClock") - public void testSpecifyCachedClockLong() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(CachedClock.class); - } - - @Test - @SetSystemProperty(key = Log4jPropertyKey.Constant.CONFIG_CLOCK, value = "CoarseCachedClock") public void testSpecifyCoarseCachedClockShort() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(CoarseCachedClock.class); - } - - @Test - @SetSystemProperty( - key = Log4jPropertyKey.Constant.CONFIG_CLOCK, - value = "org.apache.logging.log4j.core.time.internal.CoarseCachedClock") - public void testSpecifyCoarseCachedClockLong() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(CoarseCachedClock.class); + final Clock clock = builder.addInitialBindingFrom(Clock.KEY) + .toSingleton(CoarseCachedClock::instance) + .build() + .getInstance(Clock.KEY); + assertThat(clock).isInstanceOf(CoarseCachedClock.class); } public static class MyClock implements Clock { @@ -93,11 +69,11 @@ public long currentTimeMillis() { } @Test - @SetSystemProperty( - key = Log4jPropertyKey.Constant.CONFIG_CLOCK, - value = "org.apache.logging.log4j.core.time.ClockFactoryTest$MyClock") public void testCustomClock() { - DI.initializeFactory(instanceFactory); - assertThat(instanceFactory.getInstance(Clock.class)).isInstanceOf(MyClock.class); + final Clock clock = builder.addInitialBindingFrom(Clock.KEY) + .toSingleton(MyClock::new) + .build() + .getInstance(Clock.KEY); + assertThat(clock).isInstanceOf(MyClock.class); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java index 806a22479fd..27d89ae410e 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java @@ -28,20 +28,16 @@ import org.apache.logging.log4j.core.impl.Log4jContextFactory; import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.TestBinding; import org.apache.logging.log4j.plugins.Singleton; -import org.apache.logging.log4j.plugins.SingletonFactory; import org.apache.logging.log4j.status.StatusLogger; import org.junit.jupiter.api.Test; +@TestBinding(api = ShutdownCallbackRegistry.class, implementation = ShutdownCallbackRegistryTest.Registry.class) +@LoggerContextSource public class ShutdownCallbackRegistryTest { - @SingletonFactory - ShutdownCallbackRegistry shutdownCallbackRegistry() { - return new Registry(); - } - @Test - @LoggerContextSource(value = "ShutdownCallbackRegistryTest.xml", bootstrap = true) public void testShutdownCallbackRegistry(final LoggerContext context) { assertTrue(context.isStarted(), "LoggerContext should be started"); assertThat(Registry.CALLBACKS, hasSize(1)); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java index 7a70e0a61c6..2a17fd76b51 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java @@ -48,7 +48,6 @@ import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.plugins.di.InstanceFactory; @@ -212,7 +211,7 @@ public LoggerContext( private void initializeInstanceFactory() { final var ref = Lazy.weak(this); - instanceFactory.registerBinding(Binding.from(KEY).to(ref)); + instanceFactory.registerBinding(KEY, ref); instanceFactory.registerInstancePostProcessor(new LoggerContextAwarePostProcessor(this)); } @@ -253,8 +252,8 @@ public List getListeners() { */ public static LoggerContext getContext() { final var context = LogManager.getContext(); - if (context instanceof LoggerContext) { - return (LoggerContext) context; + if (context instanceof LoggerContext ctx) { + return ctx; } throw new IllegalStateException( "Expected instance of " + LoggerContext.class + " but got " + context.getClass()); @@ -279,8 +278,8 @@ public static LoggerContext getContext() { */ public static LoggerContext getContext(final boolean currentContext) { final var context = LogManager.getContext(currentContext); - if (context instanceof LoggerContext) { - return (LoggerContext) context; + if (context instanceof LoggerContext ctx) { + return ctx; } throw new IllegalStateException( "Expected instance of " + LoggerContext.class + " but got " + context.getClass()); @@ -309,8 +308,8 @@ public static LoggerContext getContext(final boolean currentContext) { public static LoggerContext getContext( final ClassLoader loader, final boolean currentContext, final URI configLocation) { final var context = LogManager.getContext(loader, currentContext, configLocation); - if (context instanceof LoggerContext) { - return (LoggerContext) context; + if (context instanceof LoggerContext ctx) { + return ctx; } throw new IllegalStateException( "Expected instance of " + LoggerContext.class + " but got " + context.getClass()); @@ -439,7 +438,7 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { this.setStopping(); String name = getName(); try { - Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500 + Server.unregisterLoggerContext(name); // LOG4J2-406, LOG4J2-500 } catch (final LinkageError | Exception e) { // LOG4J2-1506 Hello Android, GAE LOGGER.error("Unable to unregister MBeans", e); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index 631a8eabf95..fbed8d5461c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -76,7 +76,6 @@ import org.apache.logging.log4j.plugins.Inject; import org.apache.logging.log4j.plugins.Namespace; import org.apache.logging.log4j.plugins.Node; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.plugins.di.Key; @@ -183,7 +182,7 @@ protected AbstractConfiguration(final LoggerContext loggerContext, final Configu } configurationProcessor = new ConfigurationProcessor(instanceFactory); final var ref = Lazy.weak(this); - instanceFactory.registerBinding(Binding.from(Configuration.KEY).to(ref)); + instanceFactory.registerBinding(Configuration.KEY, ref); instanceFactory.registerInstancePostProcessor(new ConfigurationAwarePostProcessor(ref)); componentMap.put(Configuration.CONTEXT_PROPERTIES, properties); interpolatorFactory = instanceFactory.getInstance(InterpolatorFactory.class); @@ -219,7 +218,7 @@ public ScriptManager getScriptManager() { @Inject // TODO(ms): consider injecting here public void setScriptManager(final ScriptManager scriptManager) { this.scriptManager = scriptManager; - instanceFactory.registerBinding(Binding.from(ScriptManager.KEY).to(this::getScriptManager)); + instanceFactory.registerBinding(ScriptManager.KEY, this::getScriptManager); } public PluginNamespace getCorePlugins() { @@ -229,7 +228,7 @@ public PluginNamespace getCorePlugins() { @Inject // TODO(ms): consider injecting here public void setCorePlugins(@Namespace(Node.CORE_NAMESPACE) final PluginNamespace corePlugins) { this.corePlugins = corePlugins; - instanceFactory.registerBinding(Binding.from(Core.PLUGIN_NAMESPACE_KEY).to(this::getCorePlugins)); + instanceFactory.registerBinding(Core.PLUGIN_NAMESPACE_KEY, this::getCorePlugins); } @Override @@ -666,11 +665,12 @@ protected List processSelect(final Node selectNode, final PluginType ty } protected void doConfigure() { - instanceFactory.registerBinding(Binding.from(StringValueResolver.KEY).toInstance(configurationStrSubstitutor)); + instanceFactory.registerBinding(StringValueResolver.KEY, this::getConfigurationStrSubstitutor); processConditionals(rootNode); preConfigure(rootNode); configurationScheduler.start(); - if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) { + if (rootNode.hasChildren() + && "Properties".equalsIgnoreCase(rootNode.getChildren().get(0).getName())) { final Node first = rootNode.getChildren().get(0); createConfiguration(first, null); if (first.getObject() != null) { @@ -690,7 +690,7 @@ protected void doConfigure() { boolean setLoggers = false; boolean setRoot = false; for (final Node child : rootNode.getChildren()) { - if (child.getName().equalsIgnoreCase("Properties")) { + if ("Properties".equalsIgnoreCase(child.getName())) { if (tempLookup == runtimeStrSubstitutor.getVariableResolver()) { LOGGER.error("Properties declaration must be the first element in the configuration"); } @@ -700,15 +700,15 @@ protected void doConfigure() { if (child.getObject() == null) { continue; } - if (child.getName().equalsIgnoreCase("Scripts")) { + if ("Scripts".equalsIgnoreCase(child.getName())) { if (scriptManager != null) { scriptManager.addScripts(child); } - } else if (child.getName().equalsIgnoreCase("Appenders")) { + } else if ("Appenders".equalsIgnoreCase(child.getName())) { appenders = child.getObject(); } else if (child.isInstanceOf(Filter.class)) { addFilter(child.getObject(Filter.class)); - } else if (child.getName().equalsIgnoreCase("Loggers")) { + } else if ("Loggers".equalsIgnoreCase(child.getName())) { final Loggers l = child.getObject(); loggerConfigs = l.getMap(); setLoggers = true; @@ -716,15 +716,24 @@ protected void doConfigure() { root = l.getRoot(); setRoot = true; } - } else if (child.getName().equalsIgnoreCase("CustomLevels")) { - customLevels = child.getObject(CustomLevels.class).getCustomLevels(); + } else if ("CustomLevels".equalsIgnoreCase(child.getName())) { + final CustomLevels levels = child.getObject(CustomLevels.class); + if (levels == null) { + LOGGER.error("Unable to load CustomLevels plugin"); + } else { + customLevels = levels.getCustomLevels(); + } } else if (child.isInstanceOf(CustomLevelConfig.class)) { final List copy = new ArrayList<>(customLevels); copy.add(child.getObject(CustomLevelConfig.class)); customLevels = copy; } else if (child.isInstanceOf(AsyncWaitStrategyFactoryConfig.class)) { AsyncWaitStrategyFactoryConfig awsfc = child.getObject(AsyncWaitStrategyFactoryConfig.class); - asyncWaitStrategyFactory = awsfc.createWaitStrategyFactory(); + if (awsfc == null) { + LOGGER.error("Unable to load AsyncWaitStrategyFactoryConfig"); + } else { + asyncWaitStrategyFactory = awsfc.createWaitStrategyFactory(); + } } else { final List expected = Arrays.asList( "\"Appenders\"", "\"Loggers\"", "\"Properties\"", "\"Scripts\"", "\"CustomLevels\""); @@ -775,12 +784,9 @@ protected void setToDefault() { addAppender(appender); final LoggerConfig rootLoggerConfig = getRootLogger(); rootLoggerConfig.addAppender(appender, null, null); - - final Level defaultLevel = Level.ERROR; - final String levelName = - contextProperties.getStringProperty(Log4jPropertyKey.CONFIG_DEFAULT_LEVEL, defaultLevel.name()); - final Level level = Level.valueOf(levelName); - rootLoggerConfig.setLevel(level != null ? level : defaultLevel); + final String defaultLevelName = contextProperties.getStringProperty(Log4jPropertyKey.CONFIG_DEFAULT_LEVEL); + final Level defaultLevel = Level.toLevel(defaultLevelName, Level.ERROR); + rootLoggerConfig.setLevel(defaultLevel); } /** @@ -1112,7 +1118,7 @@ public void createConfiguration(final Node node, final LogEvent event) { } else { stringSubstitutionStrategy = str -> runtimeStrSubstitutor.replace(event, str); } - instanceFactory.registerBinding(Binding.from(StringValueResolver.KEY).toInstance(stringSubstitutionStrategy)); + instanceFactory.registerBinding(StringValueResolver.KEY, () -> stringSubstitutionStrategy); try { configurationProcessor.processNodeTree(node); } finally { @@ -1127,8 +1133,7 @@ public void createConfiguration(final Node node, final LogEvent event) { */ public Object createPluginObject(final Node node) { if (this.getState().equals(State.INITIALIZING)) { - instanceFactory.registerBinding( - Binding.from(StringValueResolver.KEY).toInstance(configurationStrSubstitutor)); + instanceFactory.registerBinding(StringValueResolver.KEY, this::getConfigurationStrSubstitutor); try { return configurationProcessor.processNodeTree(node); } finally { @@ -1166,6 +1171,6 @@ public NanoClock getNanoClock() { @Override // TODO(ms): consider injecting here public void setNanoClock(final NanoClock nanoClock) { - instanceFactory.registerBinding(Binding.from(NanoClock.KEY).toInstance(nanoClock)); + instanceFactory.registerBinding(NanoClock.KEY, () -> nanoClock); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationProcessor.java index f384c724903..42ebd312388 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationProcessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationProcessor.java @@ -24,7 +24,6 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Node; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.plugins.model.PluginType; @@ -45,7 +44,7 @@ public class ConfigurationProcessor { private final ThreadLocal currentNode = new ThreadLocal<>(); public ConfigurationProcessor(final ConfigurableInstanceFactory instanceFactory) { - instanceFactory.registerBinding(Binding.from(Node.CURRENT_NODE).to(currentNode::get)); + instanceFactory.registerBinding(Node.CURRENT_NODE, currentNode::get); this.instanceFactory = instanceFactory; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java index e805c843f02..f7f0d311447 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java @@ -16,7 +16,6 @@ */ package org.apache.logging.log4j.core.impl; -import static java.util.Objects.requireNonNull; import static org.apache.logging.log4j.util.Constants.isWebApp; import java.net.URI; @@ -34,9 +33,9 @@ import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.util.Cancellable; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; +import org.apache.logging.log4j.lang.NullMarked; import org.apache.logging.log4j.plugins.Inject; import org.apache.logging.log4j.plugins.Singleton; -import org.apache.logging.log4j.plugins.di.Binding; import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; import org.apache.logging.log4j.spi.LoggerContextFactory; @@ -68,9 +67,12 @@ public Log4jContextFactory() { * Initializes this factory's ContextSelector with the specified selector. * @param selector the selector to use */ + @NullMarked public Log4jContextFactory(final ContextSelector selector) { - this(DI.createInitializedFactory( - Binding.from(ContextSelector.KEY).toInstance(requireNonNull(selector, "No ContextSelector provided")))); + this(DI.builder() + .addInitialBindingFrom(ContextSelector.KEY) + .toInstance(selector) + .build()); } /** @@ -80,9 +82,12 @@ public Log4jContextFactory(final ContextSelector selector) { * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use * @since 2.1 */ + @NullMarked public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) { - this(DI.createInitializedFactory(Binding.from(ShutdownCallbackRegistry.KEY) - .toInstance(requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided")))); + this(DI.builder() + .addInitialBindingFrom(ShutdownCallbackRegistry.KEY) + .toInstance(shutdownCallbackRegistry) + .build()); } /** @@ -92,15 +97,19 @@ public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegist * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use * @since 2.1 */ + @NullMarked public Log4jContextFactory( final ContextSelector selector, final ShutdownCallbackRegistry shutdownCallbackRegistry) { - this(DI.createInitializedFactory( - Binding.from(ContextSelector.KEY).toInstance(requireNonNull(selector, "No ContextSelector provided")), - Binding.from(ShutdownCallbackRegistry.KEY) - .toInstance(requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided")))); + this(DI.builder() + .addInitialBindingFrom(ContextSelector.KEY) + .toInstance(selector) + .addInitialBindingFrom(ShutdownCallbackRegistry.KEY) + .toInstance(shutdownCallbackRegistry) + .build()); } @Inject + @NullMarked public Log4jContextFactory( final ConfigurableInstanceFactory instanceFactory, final ContextSelector selector, @@ -111,7 +120,8 @@ public Log4jContextFactory( initializeShutdownCallbackRegistry(); } - public Log4jContextFactory(final ConfigurableInstanceFactory instanceFactory) { + @NullMarked + private Log4jContextFactory(final ConfigurableInstanceFactory instanceFactory) { selector = instanceFactory.getInstance(ContextSelector.KEY); shutdownCallbackRegistry = instanceFactory.getInstance(ShutdownCallbackRegistry.KEY); LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java index 3edf466dcc9..c316bda2014 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java @@ -24,9 +24,7 @@ import org.apache.logging.log4j.core.time.internal.SystemMillisClock; import org.apache.logging.log4j.plugins.SingletonFactory; import org.apache.logging.log4j.plugins.condition.ConditionalOnMissingBinding; -import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory; import org.apache.logging.log4j.plugins.di.DI; -import org.apache.logging.log4j.plugins.di.InstanceFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Lazy; import org.apache.logging.log4j.util.PropertyEnvironment; @@ -39,11 +37,8 @@ public final class ClockFactory { private static final Logger LOGGER = StatusLogger.getLogger(); - private static final Lazy FALLBACK = Lazy.lazy(() -> { - ConfigurableInstanceFactory factory = DI.createFactory(); - factory.registerBundle(new ClockFactory()); - return factory.getInstance(Clock.KEY); - }); + private static final Lazy FALLBACK = + Lazy.lazy(() -> DI.builder().build().getInstance(Clock.KEY)); /** * Returns a {@code Clock} instance depending on the value of system @@ -75,33 +70,21 @@ public static Clock getClock() { @ConditionalOnMissingBinding @SingletonFactory - public Clock clock( - final PropertyEnvironment environment, final InstanceFactory instanceFactory, final ClassLoader classLoader) - throws ClassNotFoundException { + @Deprecated(forRemoval = true) + public Clock clock(final PropertyEnvironment environment) { final String customClock = environment.getStringProperty(Log4jPropertyKey.CONFIG_CLOCK); if (customClock == null) { return logSupportedPrecision(new SystemClock()); } - switch (customClock) { - case "SystemClock": - return logSupportedPrecision(new SystemClock()); - - case "SystemMillisClock": - return logSupportedPrecision(new SystemMillisClock()); - - case "CachedClock": - case "org.apache.logging.log4j.core.time.internal.CachedClock": - return logSupportedPrecision(CachedClock.instance()); - - case "CoarseCachedClock": - case "org.apache.logging.log4j.core.time.internal.CoarseCachedClock": - return logSupportedPrecision(CoarseCachedClock.instance()); - - default: - final Class clockClass = - classLoader.loadClass(customClock).asSubclass(Clock.class); - return logSupportedPrecision(instanceFactory.getInstance(clockClass)); - } + return switch (customClock) { + case "SystemMillisClock" -> logSupportedPrecision(new SystemMillisClock()); + case "CachedClock", "org.apache.logging.log4j.core.time.internal.CachedClock" -> logSupportedPrecision( + CachedClock.instance()); + case "CoarseCachedClock", + "org.apache.logging.log4j.core.time.internal.CoarseCachedClock" -> logSupportedPrecision( + CoarseCachedClock.instance()); + default -> logSupportedPrecision(new SystemClock()); + }; } private static Clock logSupportedPrecision(final Clock clock) { diff --git a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactoryTest.java b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactoryTest.java index c9f107b9ff8..7647c914e43 100644 --- a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactoryTest.java +++ b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactoryTest.java @@ -121,8 +121,10 @@ void setMethodInjectedSupplier(final Supplier supplier) { @Test void testDeferredSupplierNotInvokedUntilInitiallyProvided() { final AtomicInteger counter = new AtomicInteger(); - final DeferredSupplierBean bean = DI.createInitializedFactory( - Binding.from(int.class).to(counter::incrementAndGet)) + final DeferredSupplierBean bean = DI.builder() + .addInitialBindingFrom(int.class) + .toUnscoped(counter::incrementAndGet) + .build() .getInstance(DeferredSupplierBean.class); assertThat(counter.get()).isEqualTo(0); assertThat(bean.singletonSupplier.get().id).isEqualTo(1); @@ -177,9 +179,8 @@ void setMethodInjected(@Named({"baz", "bar"}) final String methodInjected) { @Test void supplierAliases() { - final var instanceFactory = DI.createInitializedFactory(); - instanceFactory.registerBundle(AliasBundle.class); - final Aliases aliases = instanceFactory.getInstance(Aliases.class); + final Aliases aliases = + DI.builder().addBundle(AliasBundle.class).build().getInstance(Aliases.class); assertThat(List.of(aliases.foo, aliases.bar, aliases.baz, aliases.constructed, aliases.methodInjected)) .allMatch("bar"::equals); } @@ -206,17 +207,22 @@ void injectionPointValidation() { @Test void injectionPointValidationPartial() { - final var instanceFactory = DI.createInitializedFactory( - Binding.from(new @Named("foo") Key() {}).toInstance("hello")); + final ConfigurableInstanceFactory instanceFactory = DI.builder() + .addInitialBindingFrom(new @Named("foo") Key() {}) + .toInstance("hello") + .build(); assertThatThrownBy(() -> instanceFactory.getInstance(ValidatedInjectionPoints.class)) .isInstanceOf(NoQualifiedBindingException.class); } @Test void injectionPointValidationFull() { - final var instanceFactory = DI.createInitializedFactory( - Binding.from(new @Named("foo") Key() {}).toInstance("hello"), - Binding.from(new @Named("bar") Key() {}).toInstance("world")); + final ConfigurableInstanceFactory instanceFactory = DI.builder() + .addInitialBindingFrom(new @Named("foo") Key() {}) + .toInstance("hello") + .addInitialBindingFrom(new @Named("bar") Key() {}) + .toInstance("world") + .build(); final ValidatedInjectionPoints instance = instanceFactory.getInstance(ValidatedInjectionPoints.class); assertThat(instance.foo).isEqualTo("hello"); assertThat(instance.bar).isEqualTo("world"); diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java deleted file mode 100644 index 1607812cafc..00000000000 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Binding.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.plugins.di; - -import java.util.function.Supplier; -import org.apache.logging.log4j.util.Lazy; - -/** - * Bindings combine a {@link Key} with a factory {@link Supplier} to get instances of the key's type. - * - * @param type of key to bind instance factory - */ -public final class Binding implements Supplier { - private final Key key; - private final Supplier factory; - - private Binding(final Key key, final Supplier factory) { - this.key = key; - this.factory = factory; - } - - public Key getKey() { - return key; - } - - @Override - public T get() { - return factory.get(); - } - - public static DSL from(final Key key) { - return new DSL<>(key); - } - - public static DSL from(final Class type) { - return new DSL<>(Key.forClass(type)); - } - - public static final class DSL { - private final Key key; - - private DSL(final Key key) { - this.key = key; - } - - public Binding to(final Supplier factory) { - return new Binding<>(key, factory); - } - - public Binding toSingleton(final Supplier factory) { - return new Binding<>(key, Lazy.lazy(factory)); - } - - public Binding toInstance(final T instance) { - return new Binding<>(key, Lazy.value(instance)); - } - } -} diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactory.java index ac0ad7975cc..a1e3079a79c 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactory.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/ConfigurableInstanceFactory.java @@ -18,6 +18,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.util.function.Supplier; +import org.apache.logging.log4j.lang.Nullable; import org.apache.logging.log4j.plugins.FactoryType; import org.apache.logging.log4j.plugins.Ordered; import org.apache.logging.log4j.plugins.di.spi.FactoryResolver; @@ -44,6 +46,7 @@ public interface ConfigurableInstanceFactory extends InstanceFactory { * @param scopeType scope annotation type * @return the registered scope instance for the provided scope type */ + @Nullable Scope getRegisteredScope(final Class scopeType); /** @@ -79,28 +82,20 @@ default void registerBundles(final Object... bundles) { /** * Registers a binding between a key and factory. This overwrites any existing binding the key may have had. * - * @param binding binding to register + * @param key key for binding + * @param factory factory for value to bind + * @param type of value returned by factory */ - void registerBinding(final Binding binding); - - /** - * Registers multiple bindings. - * - * @param bindings bindings to register - * @see #registerBinding(Binding) - */ - default void registerBindings(final Binding... bindings) { - for (final Binding binding : bindings) { - registerBinding(binding); - } - } + void registerBinding(final Key key, final Supplier factory); /** * Registers a binding between a key and factory only if no binding exists for that key. * - * @param binding binding to register + * @param key key for binding + * @param factory factory for value to bind + * @param type of value returned by factory */ - void registerBindingIfAbsent(final Binding binding); + void registerBindingIfAbsent(final Key key, final Supplier factory); /** * Removes any existing binding for the provided key. diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DI.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DI.java index 39e37631d6d..fcd009621f0 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DI.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DI.java @@ -16,14 +16,24 @@ */ package org.apache.logging.log4j.plugins.di; +import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.ServiceLoader; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import org.apache.logging.log4j.plugins.di.spi.ConfigurableInstanceFactoryPostProcessor; +import org.apache.logging.log4j.plugins.di.spi.Scope; import org.apache.logging.log4j.plugins.util.OrderedComparator; +import org.apache.logging.log4j.util.Lazy; import org.apache.logging.log4j.util.ServiceLoaderUtil; /** - * Factory for {@linkplain InstanceFactory instance factories}. + * Factory for {@linkplain InstanceFactory instance factories}. This provides a builder DSL for setting up a + * {@link ConfigurableInstanceFactory} using bindings registered before and after standard + * {@link ConfigurableInstanceFactoryPostProcessor} service provider classes are invoked. */ public final class DI { private DI() { @@ -31,53 +41,264 @@ private DI() { } /** - * Creates a new {@linkplain #initializeFactory(ConfigurableInstanceFactory) initialized} instance factory. + * Creates a new {@linkplain ConfigurableInstanceFactory initialized} instance factory. * - * @return the initialized instance factory + * @return an initialized instance factory */ public static ConfigurableInstanceFactory createInitializedFactory() { - final var factory = createFactory(); - initializeFactory(factory); - return factory; + return builder().build(); } /** - * Creates a new instance factory with the provided initial bindings and subsequently - * {@linkplain #initializeFactory(ConfigurableInstanceFactory) initializes} it. + * Creates a new builder for customizing a {@link ConfigurableInstanceFactory}. * - * @param bindings the bindings to register before initializing the factory - * @return the initialized instance factory + * @return new builder */ - public static ConfigurableInstanceFactory createInitializedFactory(final Binding... bindings) { - final var factory = createFactory(); - for (final Binding binding : bindings) { - factory.registerBinding(binding); + public static FactoryBuilder builder() { + return new FactoryBuilder(); + } + + /** + * Builder DSL for configuring a {@link ConfigurableInstanceFactory} using + * {@link ConfigurableInstanceFactoryPostProcessor} instances. This DSL is used for adding bindings to be + * registered before or after standard post-processor service classes have been invoked. + */ + public static class FactoryBuilder { + private final Supplier provider; + private final List preInitializationBindings = new ArrayList<>(); + private final ConfigurableInstanceFactoryPostProcessor initializer; + private final List postInitializationBindings = new ArrayList<>(); + + /** + * Constructs a fresh builder DSL using {@link DefaultInstanceFactory} and all available + * {@link ConfigurableInstanceFactoryPostProcessor} service provider classes in {@linkplain OrderedComparator sorted order}. + */ + public FactoryBuilder() { + provider = DefaultInstanceFactory::new; + initializer = factory -> ServiceLoaderUtil.safeStream(ServiceLoader.load( + ConfigurableInstanceFactoryPostProcessor.class, DI.class.getClassLoader())) + .sorted(Comparator.comparing( + ConfigurableInstanceFactoryPostProcessor::getClass, OrderedComparator.INSTANCE)) + .forEachOrdered(processor -> processor.postProcessFactory(factory)); + } + + private FactoryBuilder(final FactoryBuilder copy) { + this.provider = copy.provider; + this.preInitializationBindings.addAll(copy.preInitializationBindings); + this.initializer = copy.initializer; + this.postInitializationBindings.addAll(copy.postInitializationBindings); + } + + /** + * Begins to define a binding that will be registered before the {@link ConfigurableInstanceFactory} + * is initialized with service provider classes. + * + * @param key binding key for which a binding will be registered + * @return builder DSL to specify the binding to register + * @param type of binding result being registered + */ + public UnscopedBindingBuilder addInitialBindingFrom(final Key key) { + return new UnscopedBindingBuilder<>(key, this, preInitializationBindings::add); + } + + /** + * Begins to define a binding that will be registered before the {@link ConfigurableInstanceFactory} + * is initialized with service provider classes. + * + * @param type class for which a binding will be registered + * @return builder DSL to specify the binding to register + * @param type of binding result being registered + */ + public UnscopedBindingBuilder addInitialBindingFrom(final Class type) { + return addInitialBindingFrom(Key.forClass(type)); + } + + /** + * Begins to define a binding that will be registered after the {@link ConfigurableInstanceFactory} + * is initialized with service provider classes. + * + * @param key binding key for which a binding will be registered + * @return builder DSL to specify the binding to register + * @param type of binding result being registered + */ + public UnscopedBindingBuilder addBindingFrom(final Key key) { + return new UnscopedBindingBuilder<>(key, this, postInitializationBindings::add); + } + + /** + * Begins to define a binding that will be registered after the {@link ConfigurableInstanceFactory} + * is initialized with service provider classes. + * + * @param type class for which a binding will be registered + * @return builder DSL to specify the binding to register + * @param type of binding result being registered + */ + public UnscopedBindingBuilder addBindingFrom(final Class type) { + return addBindingFrom(Key.forClass(type)); + } + + /** + * Adds a bundle class or instance to be registered before the {@link ConfigurableInstanceFactory} + * is initialized with service provider classes. + * + * @param bundle bundle class or instance to register + * @return this builder DSL + */ + public FactoryBuilder addInitialBundle(final Object bundle) { + preInitializationBindings.add(factory -> factory.registerBundle(bundle)); + return this; + } + + /** + * Adds a bundle class or instance to be registered after the {@link ConfigurableInstanceFactory} + * is initialized with service provider classes. + * + * @param bundle bundle class or instance to register + * @return this builder DSL + */ + public FactoryBuilder addBundle(final Object bundle) { + postInitializationBindings.add(factory -> factory.registerBundle(bundle)); + return this; + } + + /** + * Constructs and configures a {@link ConfigurableInstanceFactory} using the configured bindings before + * and after service provider initialization. All initial bindings are registered before invoking the + * {@link ConfigurableInstanceFactoryPostProcessor} service providers, and then the remaining bindings + * are registered. + * + * @return the initialized ConfigurableInstanceFactory + */ + public ConfigurableInstanceFactory build() { + final ConfigurableInstanceFactory factory = provider.get(); + preInitializationBindings.forEach(binding -> binding.postProcessFactory(factory)); + initializer.postProcessFactory(factory); + postInitializationBindings.forEach(binding -> binding.postProcessFactory(factory)); + return factory; + } + + /** + * Creates a new builder DSL with a copy of the configured bindings from this builder. + * + * @return copy of this builder + */ + public FactoryBuilder copy() { + return new FactoryBuilder(this); } - initializeFactory(factory); - return factory; } /** - * Creates a new instance factory. This should be {@linkplain #initializeFactory(ConfigurableInstanceFactory) - * initialized} after setup. + * Builder DSL for configuring a binding to be registered with a {@link ConfigurableInstanceFactory}. * - * @return a new instance factory + * @param type of binding result being registered */ - public static ConfigurableInstanceFactory createFactory() { - return new DefaultInstanceFactory(); + public static class UnscopedBindingBuilder { + final Key key; + final FactoryBuilder builder; + final Consumer addBinding; + + private UnscopedBindingBuilder( + final Key key, + final FactoryBuilder builder, + final Consumer addBinding) { + this.key = key; + this.builder = builder; + this.addBinding = addBinding; + } + + private UnscopedBindingBuilder(final UnscopedBindingBuilder copy) { + this(copy.key, copy.builder, copy.addBinding); + } + + /** + * Continues defining a binding in a particular {@linkplain org.apache.logging.log4j.plugins.ScopeType scope}. + * + * @param scopeType annotation of the scope to use for this binding + * @return builder DSL to specify the scoped binding + */ + public ScopedBindingBuilder inScope(final Class scopeType) { + return new ScopedBindingBuilder<>(this, scopeType); + } + + /** + * Adds the given instance as a binding to the configured key. + * + * @param instance instance to bind to the key + * @return builder DSL for ConfigurableInstanceFactory + */ + public FactoryBuilder toInstance(final T instance) { + addBinding.accept(factory -> factory.registerBinding(key, Lazy.value(instance))); + return builder; + } + + /** + * Adds a singleton binding for the given supplier function. + * + * @param provider function for creating the initial binding value + * @return builder DSL for ConfigurableInstanceFactory + */ + public FactoryBuilder toSingleton(final Supplier provider) { + addBinding.accept(factory -> factory.registerBinding(key, Lazy.lazy(provider))); + return builder; + } + + /** + * Adds an unscoped binding for the given supplier function. This function will be invoked every time an + * instance of this binding is requested. + * + * @param provider unscoped function for creating the binding value + * @return builder DSL for ConfigurableInstanceFactory + */ + public FactoryBuilder toUnscoped(final Supplier provider) { + addBinding.accept(factory -> factory.registerBinding(key, provider)); + return builder; + } + + /** + * Adds a binding using a function that depends on {@link InstanceFactory}. This is useful for registering + * dependent bindings or more complex bindings in general. + * + * @param function unscoped function for creating the binding value from an InstanceFactory + * @return builder DSL for ConfigurableInstanceFactory + */ + public FactoryBuilder toFunction(final Function> function) { + addBinding.accept(factory -> factory.registerBinding(key, function.apply(factory))); + return builder; + } } /** - * Initializes the given instance factory with all registered {@link ConfigurableInstanceFactoryPostProcessor} - * services. + * Builder DSL for configuring a scoped binding to be registered with a {@link ConfigurableInstanceFactory}. * - * @param factory the instance factory to initialize + * @param type of binding result being registered */ - public static void initializeFactory(final ConfigurableInstanceFactory factory) { - ServiceLoaderUtil.safeStream( - ServiceLoader.load(ConfigurableInstanceFactoryPostProcessor.class, DI.class.getClassLoader())) - .sorted(Comparator.comparing( - ConfigurableInstanceFactoryPostProcessor::getClass, OrderedComparator.INSTANCE)) - .forEachOrdered(processor -> processor.postProcessFactory(factory)); + public static class ScopedBindingBuilder extends UnscopedBindingBuilder { + private final Class scopeType; + + private ScopedBindingBuilder( + final UnscopedBindingBuilder builder, final Class scopeType) { + super(builder); + this.scopeType = scopeType; + } + + /** + * Adds a scoped binding to the given provider function. If no {@link Scope} can be found corresponding to + * the requested scope annotation type, then this provider will be registered as an unscoped binding. + * + * @param provider unscoped function for creating the binding value + * @return builder DSL for ConfigurableInstanceFactory + */ + public FactoryBuilder toProvider(final Supplier provider) { + addBinding.accept(factory -> { + final Scope scope = factory.getRegisteredScope(scopeType); + if (scope != null) { + final Supplier scoped = scope.get(key, provider::get); + factory.registerBinding(key, scoped); + } else { + factory.registerBinding(key, provider); + } + }); + return builder; + } } } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInstanceFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInstanceFactory.java index cb20fb6d129..25c6490b2bd 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInstanceFactory.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInstanceFactory.java @@ -34,6 +34,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.lang.NullMarked; +import org.apache.logging.log4j.lang.Nullable; import org.apache.logging.log4j.plugins.FactoryType; import org.apache.logging.log4j.plugins.ScopeType; import org.apache.logging.log4j.plugins.condition.Condition; @@ -92,26 +94,25 @@ private DefaultInstanceFactory( this.scopes = scopes; this.factoryResolvers = factoryResolvers; this.instancePostProcessors.addAll(instancePostProcessors); - this.bindings.put(Binding.from(InjectionPoint.CURRENT_INJECTION_POINT).to(currentInjectionPoint::get)); - this.bindings.put(Binding.from(ConfigurableInstanceFactory.class).toInstance(this)); - this.bindings.put(Binding.from(InstanceFactory.class).toInstance(this)); - this.bindings.put(Binding.from(PropertyEnvironment.class).to(PropertiesUtil::getProperties)); - this.bindings.put(Binding.from(ClassLoader.class).to(LoaderUtil::getClassLoader)); + this.bindings.put(InjectionPoint.CURRENT_INJECTION_POINT, currentInjectionPoint::get); + this.bindings.put(Key.forClass(ConfigurableInstanceFactory.class), () -> this); + this.bindings.put(Key.forClass(InstanceFactory.class), () -> this); + this.bindings.put(Key.forClass(PropertyEnvironment.class), PropertiesUtil::getProperties); + this.bindings.put(Key.forClass(ClassLoader.class), LoaderUtil::getClassLoader); } @Override public Supplier getFactory(final ResolvableKey resolvableKey) { final Key key = resolvableKey.getKey(); - final Binding existingBinding = bindings.get(key, resolvableKey.getAliases()); + final Supplier existingBinding = bindings.get(key, resolvableKey.getAliases()); if (existingBinding != null) { return existingBinding; } final Supplier unscoped = resolveKey(resolvableKey).orElseGet(() -> createDefaultFactory(resolvableKey)); final Scope scope = getScopeForType(key.getRawType()); final Supplier scoped = scope.get(key, unscoped); - final Binding binding = Binding.from(key).to(scoped); - registerBinding(binding); - return binding; + registerBinding(key, scoped); + return scoped; } protected Optional> resolveKey(final ResolvableKey resolvableKey) { @@ -216,7 +217,7 @@ public boolean hasBinding(final Key key) { } @Override - public Scope getRegisteredScope(final Class scopeType) { + public @Nullable Scope getRegisteredScope(final Class scopeType) { return scopes.get(scopeType); } @@ -290,23 +291,21 @@ protected void registerBundleMethod(final Object bundleInstance, final Metho return postProcessAfterInitialization(resolvableKey, instance); }; final Supplier scoped = getScopeForMethod(method).get(primaryKey, unscoped); - registerBinding(Binding.from(primaryKey).to(scoped)); + registerBinding(primaryKey, scoped); for (final String alias : Keys.getAliases(method)) { final Key aliasKey = primaryKey.withName(alias); - if (!hasBinding(aliasKey)) { - registerBinding(Binding.from(aliasKey).to(scoped)); - } + registerBindingIfAbsent(aliasKey, scoped); } } @Override - public void registerBinding(final Binding binding) { - bindings.put(binding); + public void registerBinding(final Key key, final Supplier factory) { + bindings.put(key, factory); } @Override - public void registerBindingIfAbsent(final Binding binding) { - bindings.putIfAbsent(binding); + public void registerBindingIfAbsent(final Key key, final Supplier factory) { + bindings.putIfAbsent(key, factory); } @Override @@ -380,6 +379,7 @@ protected void injectField(final Field field, final Object instance) { } } + @NullMarked enum DefaultScope implements Scope { INSTANCE; diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java index 7820fa66f3b..6c39d42ff47 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Key.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.plugins.di; +import com.google.errorprone.annotations.concurrent.LazyInit; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedType; @@ -28,6 +29,7 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.function.Supplier; +import org.apache.logging.log4j.lang.Nullable; import org.apache.logging.log4j.plugins.Ordered; import org.apache.logging.log4j.plugins.QualifierType; import org.apache.logging.log4j.plugins.di.spi.InjectionPoint; @@ -41,7 +43,7 @@ /** * Keys represent a reified type with an optional {@link QualifierType} type, name, and namespace. * A key is used in two related contexts: describing an {@link InjectionPoint} requesting a dependency - * for injection and describing a {@link Binding} where a {@linkplain Scope scoped} factory is + * for injection and describing a binding where a {@linkplain Scope scoped} factory is * registered for providing dependencies. * * @param type of key @@ -53,12 +55,14 @@ public class Key implements StringBuilderFormattable, Comparable> { private final Type type; private final Class rawType; - private final Class qualifierType; + private final @Nullable Class qualifierType; private final String name; private final String namespace; private final OptionalInt order; private final int hashCode; - private String toString; + + @LazyInit + private @Nullable String toString; /** * Anonymous subclasses override this constructor to instantiate this Key based on the type given. @@ -87,7 +91,7 @@ protected Key() { private Key( final Type type, final Class rawType, - final Class qualifierType, + final @Nullable Class qualifierType, final String name, final String namespace, final OptionalInt order) { @@ -141,7 +145,7 @@ public final String getNamespace() { * Returns the qualifier type of this key. If this key has no qualifier type defined, then this returns * {@code null}. */ - public final Class getQualifierType() { + public final @Nullable Class getQualifierType() { return qualifierType; } @@ -198,7 +202,7 @@ public final Key withQualifierType(final Class qualifie * @return a new instance from this using the supplied type or {@code null} if this key did not contain a * supplier type */ - public final

Key

getSuppliedType() { + public final

@Nullable Key

getSuppliedType() { if (type instanceof ParameterizedType && Supplier.class.isAssignableFrom(rawType)) { final Type typeArgument = ((ParameterizedType) type).getActualTypeArguments()[0]; return withType(typeArgument); @@ -217,7 +221,7 @@ public final

Key

getSuppliedType() { * @throws IndexOutOfBoundsException if {@code arg} is negative or otherwise outside the bounds of the actual * number of type arguments of this key's type */ - public final

Key

getParameterizedTypeArgument(final int arg) { + public final

@Nullable Key

getParameterizedTypeArgument(final int arg) { if (arg < 0) { throw new IndexOutOfBoundsException(arg); } @@ -369,7 +373,7 @@ public static Builder builder(final Key original) { return new Builder<>(original); } - private static Class getQualifierType(final AnnotatedElement element) { + private static @Nullable Class getQualifierType(final AnnotatedElement element) { return AnnotationUtil.findAnnotatedAnnotations(element, QualifierType.class) .map(annotatedAnnotation -> annotatedAnnotation.getAnnotation().annotationType()) .findFirst() @@ -384,9 +388,9 @@ private static Class getQualifierType(final AnnotatedEleme public static class Builder implements Supplier> { private final Type type; private final Class rawType; - private Class qualifierType; - private String name; - private String namespace; + private @Nullable Class qualifierType; + private @Nullable String name; + private @Nullable String namespace; private OptionalInt order = OptionalInt.empty(); private Builder(final Type type) { @@ -412,7 +416,7 @@ private Builder(final Key original) { * Specifies a qualifier annotation type. Qualifiers are optional and are used for an additional comparison * property for keys. */ - public Builder setQualifierType(final Class qualifierType) { + public Builder setQualifierType(final @Nullable Class qualifierType) { this.qualifierType = qualifierType; return this; } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/package-info.java index 39d07efb356..c7df9d52a81 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/package-info.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/package-info.java @@ -20,7 +20,9 @@ */ @Export @Version("1.0.0") +@NullMarked package org.apache.logging.log4j.plugins.di; +import org.apache.logging.log4j.lang.NullMarked; import org.osgi.annotation.bundle.Export; import org.osgi.annotation.versioning.Version; diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BindingMap.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BindingMap.java index b9a8e28fda9..92be810e36c 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BindingMap.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/BindingMap.java @@ -16,121 +16,26 @@ */ package org.apache.logging.log4j.plugins.internal.util; -import java.util.Collection; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.apache.logging.log4j.plugins.di.Binding; +import java.util.function.Supplier; +import org.apache.logging.log4j.lang.Nullable; import org.apache.logging.log4j.plugins.di.Key; -import org.apache.logging.log4j.util.Cast; -import org.apache.logging.log4j.util.InternalApi; -@InternalApi -public class BindingMap { - private final HierarchicalMap, Binding> bindings; - private final ReadWriteLock lock = new ReentrantReadWriteLock(); +public interface BindingMap { + @Nullable Supplier get(final Key key, final Iterable aliases); - private BindingMap(final HierarchicalMap, Binding> bindings) { - this.bindings = bindings; - } - - public Binding get(final Key key, final Collection aliases) { - lock.readLock().lock(); - try { - final Binding existing = get(key); - if (existing != null) { - return existing; - } - for (final String alias : aliases) { - final Binding existingAlias = get(key.withName(alias)); - if (existingAlias != null) { - return existingAlias; - } - } - return null; - } finally { - lock.readLock().unlock(); - } - } - - Binding get(final Key key) { - return Cast.cast(bindings.get(key)); - } - - public void put(final Binding binding) { - lock.writeLock().lock(); - try { - bindings.put(binding.getKey(), binding); - } finally { - lock.writeLock().unlock(); - } - } + void put(final Key key, final Supplier factory); - public Binding putIfAbsent(final Binding binding) { - lock.readLock().lock(); - try { - final Binding existing = bindings.get(binding.getKey()); - if (existing != null) { - return existing; - } - } finally { - lock.readLock().unlock(); - } - lock.writeLock().lock(); - try { - return bindings.putIfAbsent(binding.getKey(), binding); - } finally { - lock.writeLock().unlock(); - } - } + void putIfAbsent(final Key key, final Supplier factory); - public Binding merge(final Binding binding) { - final Key key = binding.getKey(); - lock.writeLock().lock(); - try { - final Binding merged = bindings.merge(key, binding, (existingBinding, ignored) -> { - final Binding existing = Cast.cast(existingBinding); - final Key existingKey = existing.getKey(); - final int compared = existingKey.compareTo(key); - return compared > 0 ? binding : existing; - }); - return Cast.cast(merged); - } finally { - lock.writeLock().unlock(); - } - } + void remove(final Key key); - public void remove(final Key key) { - lock.writeLock().lock(); - try { - bindings.remove(key); - } finally { - lock.writeLock().unlock(); - } - } + boolean containsKey(final Key key); - public boolean containsKey(final Key key) { - lock.readLock().lock(); - try { - return bindings.containsKey(key); - } finally { - lock.readLock().unlock(); - } - } + boolean containsLocalKey(final Key key); - public boolean containsLocalKey(final Key key) { - lock.readLock().lock(); - try { - return bindings.containsLocalKey(key); - } finally { - lock.readLock().unlock(); - } - } - - public BindingMap newChildMap() { - return new BindingMap(bindings.newChildMap()); - } + BindingMap newChildMap(); - public static BindingMap newRootMap() { - return new BindingMap(HierarchicalMap.newRootMap()); + static BindingMap newRootMap() { + return new DefaultBindingMap(HierarchicalMap.newRootMap()); } } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/DefaultBindingMap.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/DefaultBindingMap.java new file mode 100644 index 00000000000..ecbb5e5c901 --- /dev/null +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/internal/util/DefaultBindingMap.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.plugins.internal.util; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; +import org.apache.logging.log4j.lang.Nullable; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.util.Cast; +import org.apache.logging.log4j.util.InternalApi; + +@InternalApi +public class DefaultBindingMap implements BindingMap { + private final HierarchicalMap, Supplier> bindings; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + DefaultBindingMap(final HierarchicalMap, Supplier> bindings) { + this.bindings = bindings; + } + + @Override + public @Nullable Supplier get(final Key key, final Iterable aliases) { + lock.readLock().lock(); + try { + final Supplier existing = get(key); + if (existing != null) { + return existing; + } + for (final String alias : aliases) { + final Supplier existingAlias = get(key.withName(alias)); + if (existingAlias != null) { + return existingAlias; + } + } + return null; + } finally { + lock.readLock().unlock(); + } + } + + private @Nullable Supplier get(final Key key) { + return Cast.cast(bindings.get(key)); + } + + @Override + public void put(final Key key, final Supplier factory) { + lock.writeLock().lock(); + try { + bindings.put(key, factory); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void putIfAbsent(final Key key, final Supplier factory) { + lock.writeLock().lock(); + try { + bindings.putIfAbsent(key, factory); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void remove(final Key key) { + lock.writeLock().lock(); + try { + bindings.remove(key); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public boolean containsKey(final Key key) { + lock.readLock().lock(); + try { + return bindings.containsKey(key); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public boolean containsLocalKey(final Key key) { + lock.readLock().lock(); + try { + return bindings.containsLocalKey(key); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public BindingMap newChildMap() { + return new DefaultBindingMap(bindings.newChildMap()); + } +} diff --git a/src/changelog/.3.x.x/2147_add_instance_factory_dsls.xml b/src/changelog/.3.x.x/2147_add_instance_factory_dsls.xml new file mode 100644 index 00000000000..976a5c07a5c --- /dev/null +++ b/src/changelog/.3.x.x/2147_add_instance_factory_dsls.xml @@ -0,0 +1,10 @@ + + + + + Add and update DSLs for setting up dependency injection for test and non-test code. + + diff --git a/src/site/_release-notes/_3.x.x.adoc b/src/site/_release-notes/_3.x.x.adoc index 6b270cf527c..218ddaf2603 100644 --- a/src/site/_release-notes/_3.x.x.adoc +++ b/src/site/_release-notes/_3.x.x.adoc @@ -20,6 +20,11 @@ This release contains... +[#release-notes-3-x-x-added] +=== Added + +* Add and update DSLs for setting up dependency injection for test and non-test code. (https://github.com/apache/logging-log4j2/issues/2147[2147]) + [#release-notes-3-x-x-changed] === Changed