Skip to content

Commit

Permalink
Prototype to support loading additional configuration locations.
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Oct 29, 2020
1 parent 3d5ab3f commit 05c5070
Show file tree
Hide file tree
Showing 17 changed files with 584 additions and 6 deletions.
5 changes: 5 additions & 0 deletions implementation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
<groupId>io.smallrye.testing</groupId>
<artifactId>smallrye-testing-utilities</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap</groupId>
<artifactId>shrinkwrap-impl-base</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.smallrye.config;

import static io.smallrye.common.classloader.ClassPathUtils.consumeAsPaths;
import static io.smallrye.config.Converters.newCollectionConverter;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;

import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.Converter;

import io.smallrye.common.classloader.ClassPathUtils;

public abstract class AbstractLocationConfigSourceFactory implements ConfigSourceFactory {
public static final String SMALLRYE_LOCATIONS = "smallrye.config.locations";

protected abstract String[] getFileExtensions();

protected abstract ConfigSource loadConfigSource(final URL url);

@Override
public Iterable<ConfigSource> getConfigSources(final ConfigSourceContext context) {
final ConfigValue value = context.getValue(SMALLRYE_LOCATIONS);

final List<ConfigSource> configSources = new ArrayList<>();
if (value.getValue() != null) {
List<URL> resources = newCollectionConverter(new URLConverter(), ArrayList::new).convert(value.getValue());
resources.forEach(url -> {
tryFileSystem(url, configSources);
tryClassPath(url, configSources);
tryHttpResource(url, configSources);
});
}

return configSources;
}

private void tryFileSystem(final URL url, final List<ConfigSource> configSources) {
if (url.getProtocol().startsWith("file")) {
try {
Files.walk(Paths.get(url.getFile()))
.filter(Files::isRegularFile)
.filter(path -> validExtension(path.getFileName().toString()))
.forEach(path -> addConfigSource(toURL(path.toUri()), configSources));
} catch (IOException e) {
// Log
}
}
}

private void tryClassPath(final URL url, final List<ConfigSource> configSources) {
if (url.getProtocol().startsWith("file")) {
try {
consumeAsPaths(url.getFile(), path -> {
if (validExtension(path.getFileName().toString())) {
addConfigSource(toURL(path.toUri()), configSources);
}
});
} catch (IOException e) {
// Log
} catch (IllegalArgumentException e) {
fallbackToUnknownProtocol(url, configSources);
}
}

if (url.getProtocol().startsWith("jar")) {
ClassPathUtils.consumeAsPath(url, path -> addConfigSource(toURL(path.toUri()), configSources));
}
}

private void fallbackToUnknownProtocol(final URL url, final List<ConfigSource> configSources) {
final ClassLoader classLoader = SecuritySupport.getContextClassLoader();
try {
Enumeration<URL> resources = classLoader.getResources(url.getFile());
while (resources.hasMoreElements()) {
addConfigSource(resources.nextElement(), configSources);
}

} catch (IOException e) {
// Log
}
}

private void tryHttpResource(final URL url, final List<ConfigSource> configSources) {
if (url.getProtocol().startsWith("http")) {
if (validExtension(url.toString())) {
addConfigSource(url, configSources);
}
}
}

private static URL toURL(final URI uri) {
try {
return uri.toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}

private void addConfigSource(final URL url, final List<ConfigSource> configSources) {
final ConfigSource configSource = loadConfigSource(url);
if (!configSource.getProperties().isEmpty()) {
configSources.add(configSource);
}
}

private boolean validExtension(final String resourceName) {
return Arrays.stream(getFileExtensions()).anyMatch(resourceName::endsWith);
}

private static class URLConverter implements Converter<URL> {
private static final long serialVersionUID = -4852082279190307320L;

@Override
public URL convert(final String value) {
try {
return new URL(value);
} catch (MalformedURLException e) {
return convert("file:" + value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.smallrye.config;

import java.io.IOException;
import java.net.URL;

import org.eclipse.microprofile.config.spi.ConfigSource;

public class PropertiesLocationConfigSourceFactory extends AbstractLocationConfigSourceFactory {
@Override
public String[] getFileExtensions() {
return new String[] { "properties" };
}

@Override
public ConfigSource loadConfigSource(final URL url) {
try {
return new PropertiesConfigSource(url);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.smallrye.config.PropertiesLocationConfigSourceFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package io.smallrye.config;

import static io.smallrye.config.AbstractLocationConfigSourceFactory.SMALLRYE_LOCATIONS;
import static java.util.stream.StreamSupport.stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

class PropertiesLocationConfigSourceFactoryTest {
@Test
void systemFile() {
SmallRyeConfig config = buildConfig("./src/test/resources/additional.properties");

assertEquals("1234", config.getRawValue("my.prop"));
assertNull(config.getRawValue("more.prop"));
assertEquals(1, countSources(config));
}

@Test
void systemFolder() {
SmallRyeConfig config = buildConfig("./src/test/resources");

assertEquals("1234", config.getRawValue("my.prop"));
assertEquals("5678", config.getRawValue("more.prop"));
assertEquals(3, countSources(config));
}

@Test
void webResource() {
SmallRyeConfig config = buildConfig(
"https://raw.githubusercontent.com/smallrye/smallrye-config/master/implementation/src/test/resources/config-values.properties");

assertEquals("abc", config.getRawValue("my.prop"));
assertEquals(1, countSources(config));
}

@Test
void classpath() {
SmallRyeConfig config = buildConfig("additional.properties");

assertEquals("1234", config.getRawValue("my.prop"));
assertEquals(1, countSources(config));
}

@Test
void all() {
SmallRyeConfig config = buildConfig("./src/test/resources",
"https://raw.githubusercontent.com/smallrye/smallrye-config/master/implementation/src/test/resources/config-values.properties");

assertEquals("1234", config.getRawValue("my.prop"));
assertEquals("5678", config.getRawValue("more.prop"));
assertEquals(4, countSources(config));
}

@Test
void notFound() {
SmallRyeConfig config = buildConfig("not.found");

assertNull(config.getRawValue("my.prop"));
assertEquals(0, countSources(config));
}

@Test
void noPropertiesFile() {
SmallRyeConfig config = buildConfig("./src/test/resources/random.yml");

assertEquals(0, countSources(config));
}

@Test
void multipleResourcesInClassPath(@TempDir Path tempDir) throws Exception {
JavaArchive jarOne = ShrinkWrap
.create(JavaArchive.class, "resources-one.jar")
.addAsResource(new StringAsset("my.prop.one=1234\n"), "resources.properties");

Path filePathOne = tempDir.resolve("resources-one.jar");
jarOne.as(ZipExporter.class).exportTo(filePathOne.toFile());

JavaArchive jarTwo = ShrinkWrap
.create(JavaArchive.class, "resources-two.jar")
.addAsResource(new StringAsset("my.prop.two=5678\n"), "resources.properties");

Path filePathTwo = tempDir.resolve("resources-two.jar");
jarTwo.as(ZipExporter.class).exportTo(filePathTwo.toFile());

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {
new URL("jar:file:" + filePathOne.toString() + "!/"),
new URL("jar:file:" + filePathTwo.toString() + "!/")
}, contextClassLoader);
Thread.currentThread().setContextClassLoader(urlClassLoader);

SmallRyeConfig config = buildConfig("resources.properties");

assertEquals("1234", config.getRawValue("my.prop.one"));
assertEquals("5678", config.getRawValue("my.prop.two"));
assertEquals(2, countSources(config));

Thread.currentThread().setContextClassLoader(contextClassLoader);
}

@Test
void jar(@TempDir Path tempDir) throws Exception {
JavaArchive jarOne = ShrinkWrap
.create(JavaArchive.class, "resources-one.jar")
.addAsResource(new StringAsset("my.prop.one=1234\n"), "resources.properties");

Path filePathOne = tempDir.resolve("resources-one.jar");
jarOne.as(ZipExporter.class).exportTo(filePathOne.toFile());

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {
new URL("jar:file:" + filePathOne.toString() + "!/")
}, contextClassLoader);
Thread.currentThread().setContextClassLoader(urlClassLoader);

SmallRyeConfig config = buildConfig("jar:file:" + filePathOne.toString() + "!/resources.properties");

assertEquals("1234", config.getRawValue("my.prop.one"));
assertEquals(1, countSources(config));

Thread.currentThread().setContextClassLoader(contextClassLoader);
}

@Test
void invalidWebResource() {
assertThrows(IllegalStateException.class,
() -> buildConfig("https://raw.githubusercontent.com/smallrye/smallrye-config/notfound.properties"));
buildConfig("https://github.com/smallrye/smallrye-config/blob/3cc4809734d7fbd03852a20b5870ca743a2427bc/pom.xml");
}

private static SmallRyeConfig buildConfig(String... locations) {
return new SmallRyeConfigBuilder()
.addDiscoveredSources()
.addDefaultInterceptors()
.withDefaultValue(SMALLRYE_LOCATIONS, String.join(",", locations))
.build();
}

private static int countSources(SmallRyeConfig config) {
return (int) stream(config.getConfigSources().spliterator(), false).filter(PropertiesConfigSource.class::isInstance)
.count();
}
}
2 changes: 2 additions & 0 deletions implementation/src/test/resources/additional.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
config_ordinal=500
my.prop=1234
1 change: 1 addition & 0 deletions implementation/src/test/resources/more.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
more.prop=5678
15 changes: 15 additions & 0 deletions implementation/src/test/resources/random.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
admin:
users:
-
email: "[email protected]"
username: "joe"
password: "123456"
roles:
- "Moderator"
- "Admin"
-
email: "[email protected]"
username: "jack"
password: "654321"
roles:
- "Moderator"
12 changes: 8 additions & 4 deletions sources/yaml/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config-common</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-constraint</artifactId>
Expand All @@ -39,13 +43,13 @@
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config</artifactId>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<groupId>org.jboss.shrinkwrap</groupId>
<artifactId>shrinkwrap-impl-base</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Loading

0 comments on commit 05c5070

Please sign in to comment.