Skip to content

Commit a6cb7b7

Browse files
yybmionvy
andauthored
Add new ConfigurationFactory::getConfiguration accepting multiple URIs (#3921)
Co-authored-by: Volkan Yazıcı <volkan@yazi.ci>
1 parent d54bf0a commit a6cb7b7

File tree

8 files changed

+156
-5
lines changed

8 files changed

+156
-5
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationFactoryTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
import static org.junit.jupiter.api.Assertions.assertTrue;
2626

2727
import java.io.IOException;
28+
import java.net.URI;
2829
import java.nio.file.Files;
2930
import java.nio.file.Path;
31+
import java.util.Arrays;
32+
import java.util.Collections;
3033
import java.util.Iterator;
3134
import java.util.List;
3235
import java.util.Map;
@@ -36,12 +39,14 @@
3639
import org.apache.logging.log4j.core.Logger;
3740
import org.apache.logging.log4j.core.LoggerContext;
3841
import org.apache.logging.log4j.core.appender.ConsoleAppender;
42+
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
3943
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
4044
import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
4145
import org.apache.logging.log4j.test.junit.TempLoggingDir;
4246
import org.apache.logging.log4j.util.Strings;
4347
import org.junit.jupiter.api.Tag;
4448
import org.junit.jupiter.api.Test;
49+
import org.mockito.Mockito;
4550

4651
class ConfigurationFactoryTest {
4752

@@ -130,4 +135,56 @@ void properties(final LoggerContext context) throws IOException {
130135
final Path logFile = loggingPath.resolve("test-properties.log");
131136
checkFileLogger(context, logFile);
132137
}
138+
139+
@Test
140+
void testGetConfigurationWithNullUris() {
141+
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
142+
try (final LoggerContext context = new LoggerContext("test")) {
143+
assertThrows(NullPointerException.class, () -> factory.getConfiguration(context, "test", (List<URI>) null));
144+
}
145+
}
146+
147+
@Test
148+
void testGetConfigurationWithEmptyUris() {
149+
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
150+
try (final LoggerContext context = new LoggerContext("test")) {
151+
factory.getConfiguration(context, "test", Collections.emptyList());
152+
Mockito.verify(factory).getConfiguration(Mockito.same(context), Mockito.eq("test"), (URI) Mockito.isNull());
153+
}
154+
}
155+
156+
@Test
157+
void testGetConfigurationWithNullInList() {
158+
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
159+
try (final LoggerContext context = new LoggerContext("test")) {
160+
final List<URI> listWithNull = Arrays.asList(URI.create("path:://to/nowhere"), null);
161+
assertThrows(NullPointerException.class, () -> factory.getConfiguration(context, "test", listWithNull));
162+
}
163+
}
164+
165+
@Test
166+
void testGetConfigurationWithSingleUri() throws Exception {
167+
final ConfigurationFactory factory = Mockito.spy(ConfigurationFactory.getInstance());
168+
try (final LoggerContext context = new LoggerContext("test")) {
169+
final URI configLocation =
170+
getClass().getResource("/log4j-test1.xml").toURI();
171+
factory.getConfiguration(context, "test", Collections.singletonList(configLocation));
172+
Mockito.verify(factory)
173+
.getConfiguration(Mockito.same(context), Mockito.eq("test"), Mockito.eq(configLocation));
174+
}
175+
}
176+
177+
@Test
178+
void testGetConfigurationWithMultipleUris() throws Exception {
179+
final ConfigurationFactory factory = ConfigurationFactory.getInstance();
180+
try (final LoggerContext context = new LoggerContext("test")) {
181+
final URI configLocation1 =
182+
getClass().getResource("/log4j-test1.xml").toURI();
183+
final URI configLocation2 =
184+
getClass().getResource("/log4j-xinclude.xml").toURI();
185+
final List<URI> configLocations = Arrays.asList(configLocation1, configLocation2);
186+
final Configuration config = factory.getConfiguration(context, "test", configLocations);
187+
assertInstanceOf(CompositeConfiguration.class, config);
188+
}
189+
}
133190
}

log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomConfigurationFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ static Configuration addTestFixtures(final String name, final ConfigurationBuild
7272

7373
@Override
7474
public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
75-
return getConfiguration(loggerContext, source.toString(), null);
75+
return getConfiguration(loggerContext, source.toString(), (URI) null);
7676
}
7777

7878
@Override

log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import java.util.Collections;
2626
import java.util.List;
2727
import java.util.Map;
28+
import java.util.Objects;
2829
import java.util.concurrent.locks.Lock;
2930
import java.util.concurrent.locks.ReentrantLock;
31+
import java.util.stream.Collectors;
3032
import org.apache.logging.log4j.Level;
3133
import org.apache.logging.log4j.Logger;
3234
import org.apache.logging.log4j.core.LoggerContext;
@@ -333,6 +335,85 @@ public Configuration getConfiguration(
333335
return getConfiguration(loggerContext, name, configLocation);
334336
}
335337

338+
/**
339+
* {@return a {@link Configuration} created using provided configuration location {@link URI}s}
340+
* <p>
341+
* Configurations will be loaded and merged in the given order using the effective {@linkplain org.apache.logging.log4j.core.config.composite.MergeStrategy merge strategy}.
342+
* The default can be changed using the {@value org.apache.logging.log4j.core.config.composite.CompositeConfiguration#MERGE_STRATEGY_PROPERTY} system property.
343+
* <p>
344+
* If the provided list of {@code URI}s is empty, the configuration factory attempts to load an implementation-dependent set of default locations.
345+
* If no configuration can be found, a {@link ConfigurationException} is thrown.
346+
*
347+
* @param loggerContext a logger context, may be null
348+
* @param name a configuration name, may be null
349+
* @param configLocations configuration location {@code URI}s, may not contain or be null
350+
* @throws ConfigurationException if configuration could not be created
351+
* @throws NullPointerException if {@code configLocations} contains or is null
352+
*
353+
* @since 2.26.0
354+
*/
355+
public Configuration getConfiguration(
356+
final LoggerContext loggerContext, final String name, final List<URI> configLocations) {
357+
358+
// Sanitize URIs
359+
final int[] configLocationIndex = {0};
360+
final List<URI> distinctConfigLocations = Objects.requireNonNull(configLocations, "configLocations").stream()
361+
.peek(uri -> {
362+
if (uri == null) {
363+
final String message = String.format("configLocations[%d]", configLocationIndex[0]);
364+
throw new NullPointerException(message);
365+
}
366+
configLocationIndex[0]++;
367+
})
368+
.distinct()
369+
.collect(Collectors.toList());
370+
371+
// Short-circuit if provided URIs are null or empty
372+
if (distinctConfigLocations.isEmpty()) {
373+
final Configuration config = getConfiguration(loggerContext, name, (URI) null);
374+
if (config == null) {
375+
throw new ConfigurationException("Configuration could not be created");
376+
}
377+
return config;
378+
}
379+
380+
// Short-circuit if there is only a single URI
381+
if (distinctConfigLocations.size() == 1) {
382+
final URI configLocation = distinctConfigLocations.get(0);
383+
final Configuration config = getConfiguration(loggerContext, name, configLocation);
384+
if (config == null) {
385+
final String message =
386+
String.format("Configuration could not be created from location: `%s`", configLocation);
387+
throw new ConfigurationException(message);
388+
}
389+
return config;
390+
}
391+
392+
// Create individual configurations
393+
final List<AbstractConfiguration> configs = distinctConfigLocations.stream()
394+
.map(configLocation -> {
395+
final Configuration config = getConfiguration(loggerContext, name, configLocation);
396+
if (config == null) {
397+
final String message =
398+
String.format("Configuration could not be created from location: `%s`", configLocation);
399+
throw new ConfigurationException(message);
400+
}
401+
if (!(config instanceof AbstractConfiguration)) {
402+
final String message = String.format(
403+
"Configuration created from location `%s` was expected to be of type `%s`, found: `%s`",
404+
configLocation,
405+
AbstractConfiguration.class.getCanonicalName(),
406+
config.getClass().getCanonicalName());
407+
throw new ConfigurationException(message);
408+
}
409+
return (AbstractConfiguration) config;
410+
})
411+
.collect(Collectors.toList());
412+
413+
// Combine created configurations
414+
return new CompositeConfiguration(configs);
415+
}
416+
336417
static boolean isClassLoaderUri(final URI uri) {
337418
if (uri == null) {
338419
return false;

log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* Classes and interfaces supporting configuration of Log4j 2 with JSON.
1919
*/
2020
@Export
21-
@Version("2.20.1")
21+
@Version("2.26.0")
2222
package org.apache.logging.log4j.core.config.json;
2323

2424
import org.osgi.annotation.bundle.Export;

log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* Configuration using Properties files.
1919
*/
2020
@Export
21-
@Version("2.20.1")
21+
@Version("2.26.0")
2222
package org.apache.logging.log4j.core.config.properties;
2323

2424
import org.osgi.annotation.bundle.Export;

log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* Classes and interfaces supporting configuration of Log4j 2 with XML.
1919
*/
2020
@Export
21-
@Version("2.20.2")
21+
@Version("2.26.0")
2222
package org.apache.logging.log4j.core.config.xml;
2323

2424
import org.osgi.annotation.bundle.Export;

log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* Classes and interfaces supporting configuration of Log4j 2 with YAML.
1919
*/
2020
@Export
21-
@Version("2.20.1")
21+
@Version("2.26.0")
2222
package org.apache.logging.log4j.core.config.yaml;
2323

2424
import org.osgi.annotation.bundle.Export;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns="https://logging.apache.org/xml/ns"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="
5+
https://logging.apache.org/xml/ns
6+
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
7+
type="added">
8+
<issue id="3775" link="https://github.com/apache/logging-log4j2/issues/3775"/>
9+
<issue id="3921" link="https://github.com/apache/logging-log4j2/pull/3921"/>
10+
<description format="asciidoc">
11+
Add a new `ConfigurationFactory::getConfiguration` method accepting multiple `URI`s
12+
</description>
13+
</entry>

0 commit comments

Comments
 (0)