-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prototype to support loading additional configuration locations.
- Loading branch information
Showing
20 changed files
with
1,017 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
237 changes: 237 additions & 0 deletions
237
implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package io.smallrye.config; | ||
|
||
import static io.smallrye.common.classloader.ClassPathUtils.consumeAsPath; | ||
import static io.smallrye.common.classloader.ClassPathUtils.consumeAsPaths; | ||
import static io.smallrye.config.Converters.newCollectionConverter; | ||
|
||
import java.io.FileNotFoundException; | ||
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.Path; | ||
import java.nio.file.Paths; | ||
import java.util.ArrayList; | ||
import java.util.Enumeration; | ||
import java.util.List; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Stream; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
import org.eclipse.microprofile.config.spi.Converter; | ||
|
||
import io.smallrye.common.annotation.Experimental; | ||
|
||
/** | ||
* This {@code AbstractLocationConfigSourceFactory} allows to initialize additional config locations with the | ||
* configuration {@link AbstractLocationConfigSourceFactory#SMALLRYE_LOCATIONS}. | ||
* <p> | ||
* | ||
* Locations set in {@link AbstractLocationConfigSourceFactory#SMALLRYE_LOCATIONS} are loaded in order and from the | ||
* following resources: | ||
* | ||
* <ol> | ||
* <li>file or directory</li> | ||
* <li>classpath resource</li> | ||
* <li>jar resource</li> | ||
* <li>http resource</li> | ||
* </ol> | ||
* <p> | ||
* | ||
* If a profile is active, the profile resource is loaded if available and the unprofiled resource is also | ||
* available. This is to keep a consistent order with the unprofiled resource. Profiles are not taken into account if | ||
* the location is a directory. | ||
*/ | ||
@Experimental("Load additional config locations") | ||
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, final int ordinal) throws IOException; | ||
|
||
protected ConfigSource loadConfigSource(final URL url) throws IOException { | ||
return this.loadConfigSource(url, ConfigSource.DEFAULT_ORDINAL); | ||
} | ||
|
||
@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()); | ||
if (resources != null) { | ||
for (URL url : resources) { | ||
tryFileSystem(url, configSources, context); | ||
tryClassPath(url, configSources, context); | ||
tryHttpResource(url, configSources, context); | ||
} | ||
} | ||
} | ||
|
||
return configSources; | ||
} | ||
|
||
private void tryFileSystem(final URL url, final List<ConfigSource> configSources, final ConfigSourceContext context) { | ||
if (url.getProtocol().startsWith("file")) { | ||
// Load single file and profiles | ||
final Path urlPath = Paths.get(url.getFile()).toAbsolutePath().normalize(); | ||
if (Files.isRegularFile(urlPath)) { | ||
consumeAsPath(toURL(urlPath.toUri()), new ConfigSourcePathConsumer(this, configSources, context)); | ||
} else if (Files.isDirectory(urlPath)) { // Load directory contents | ||
try (Stream<Path> paths = Files.walk(urlPath, 1)) { | ||
paths | ||
.filter(Files::isRegularFile) | ||
.filter(path -> validExtension(path.getFileName().toString())) | ||
.forEach(path -> addConfigSource(toURL(path.toUri()), configSources)); | ||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void tryClassPath(final URL url, final List<ConfigSource> configSources, final ConfigSourceContext context) { | ||
if (url.getProtocol().startsWith("file")) { | ||
try { | ||
consumeAsPaths(url.getFile(), new ConfigSourcePathConsumer(this, configSources, context)); | ||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} catch (IllegalArgumentException e) { | ||
fallbackToUnknownProtocol(url, configSources); | ||
} | ||
} | ||
|
||
if (url.getProtocol().startsWith("jar")) { | ||
consumeAsPath(url, new ConfigSourcePathConsumer(this, configSources, context)); | ||
} | ||
} | ||
|
||
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()) { | ||
final URL resourceUrl = resources.nextElement(); | ||
if (validExtension(resourceUrl.getFile())) { | ||
addConfigSource(resourceUrl, configSources); | ||
} | ||
} | ||
|
||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} | ||
} | ||
|
||
private void tryHttpResource(final URL url, final List<ConfigSource> configSources, final ConfigSourceContext context) { | ||
if (url.getProtocol().startsWith("http")) { | ||
if (validExtension(url.toString())) { | ||
addConfigSource(url, configSources); | ||
tryProfiles(url, configSources, context); | ||
} | ||
} | ||
} | ||
|
||
private void tryProfiles(final URL url, final List<ConfigSource> configSources, final ConfigSourceContext context) { | ||
final List<String> profiles = context.getProfiles(); | ||
for (int i = profiles.size() - 1; i >= 0; i--) { | ||
final int lastOrdinal = configSources.get(configSources.size() - 1).getOrdinal(); | ||
final URL profileToFileName = addProfileToFileName(url, profiles.get(i)); | ||
consumeAsPath(profileToFileName, | ||
path -> addProfileConfigSource(toURL(path.toUri()), lastOrdinal + 1, 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) { | ||
try { | ||
final ConfigSource configSource = loadConfigSource(url); | ||
if (!configSource.getProperties().isEmpty()) { | ||
configSources.add(configSource); | ||
} | ||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} | ||
} | ||
|
||
private void addProfileConfigSource(final URL url, final int ordinal, final List<ConfigSource> configSources) { | ||
try { | ||
final ConfigSource configSource = loadConfigSource(url, ordinal); | ||
if (!configSource.getProperties().isEmpty()) { | ||
configSources.add(configSource); | ||
} | ||
} catch (FileNotFoundException e) { | ||
// It is ok to not find the resource here, because it is an optional profile resource. | ||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} | ||
} | ||
|
||
private boolean validExtension(final String resourceName) { | ||
for (String s : getFileExtensions()) { | ||
if (resourceName.endsWith(s)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private static URL addProfileToFileName(final URL fileName, final String profile) { | ||
final int dot = fileName.toString().lastIndexOf("."); | ||
try { | ||
if (dot != -1) { | ||
return new URL(fileName.toString().substring(0, dot) + "-" + profile + fileName.toString().substring(dot)); | ||
} else { | ||
return new URL(fileName + "-" + profile); | ||
} | ||
} catch (MalformedURLException e) { | ||
// The original URL was already validated, so this shouldn't happen. | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
|
||
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); | ||
} | ||
} | ||
} | ||
|
||
private static class ConfigSourcePathConsumer implements Consumer<Path> { | ||
private final AbstractLocationConfigSourceFactory factory; | ||
private final List<ConfigSource> configSources; | ||
private final ConfigSourceContext context; | ||
|
||
public ConfigSourcePathConsumer( | ||
final AbstractLocationConfigSourceFactory factory, | ||
final List<ConfigSource> configSources, final ConfigSourceContext context) { | ||
this.factory = factory; | ||
this.configSources = configSources; | ||
this.context = context; | ||
} | ||
|
||
@Override | ||
public void accept(final Path path) { | ||
if (factory.validExtension(path.getFileName().toString())) { | ||
final URL pathUrl = toURL(path.toUri()); | ||
factory.addConfigSource(pathUrl, configSources); | ||
factory.tryProfiles(pathUrl, configSources, context); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
implementation/src/main/java/io/smallrye/config/PropertiesLocationConfigSourceFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
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 | ||
protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { | ||
return new PropertiesConfigSource(url, ordinal); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
implementation/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceFactory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.smallrye.config.PropertiesLocationConfigSourceFactory |
Oops, something went wrong.