-
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.
Support loading additional configuration locations.
- Loading branch information
Showing
20 changed files
with
1,044 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
258 changes: 258 additions & 0 deletions
258
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,258 @@ | ||
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.URISyntaxException; | ||
import java.net.URL; | ||
import java.nio.file.DirectoryStream; | ||
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 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}. The configuration support multiple | ||
* locations separated by a comma and each much represent a valid {@link URI}. | ||
* <p> | ||
* | ||
* The locations comprise a list of URIs which are loaded in order. The following URI schemes are supported: | ||
* | ||
* <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 only loaded if the unprofiled resource is available in the same | ||
* location. This is to keep a consistent loading order and matching 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<URI> resources = newCollectionConverter(new URIConverter(), ArrayList::new).convert(value.getValue()); | ||
if (resources != null) { | ||
for (URI uri : resources) { | ||
if (uri.getScheme() == null || uri.getScheme().equals("file")) { | ||
tryFileSystem(uri, context, configSources); | ||
tryClassPath(uri, context, configSources); | ||
} else if (uri.getScheme().equals("jar")) { | ||
tryJar(uri, context, configSources); | ||
} else if (uri.getScheme().startsWith("http")) { | ||
tryHttpResource(uri, context, configSources); | ||
} else { | ||
throw ConfigMessages.msg.schemeNotSupported(uri.getScheme()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return configSources; | ||
} | ||
|
||
private void tryFileSystem(final URI uri, final ConfigSourceContext context, final List<ConfigSource> configSources) { | ||
final Path urlPath = Paths.get(uri.getPath()); | ||
if (Files.isRegularFile(urlPath)) { | ||
consumeAsPath(toURL(urlPath.toUri()), new ConfigSourcePathConsumer(context, configSources)); | ||
} else if (Files.isDirectory(urlPath)) { | ||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(urlPath, this::validExtension)) { | ||
for (Path path : paths) { | ||
addConfigSource(path.toUri(), configSources); | ||
} | ||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} | ||
} | ||
} | ||
|
||
private void tryClassPath(final URI uri, final ConfigSourceContext context, final List<ConfigSource> configSources) { | ||
try { | ||
consumeAsPaths(uri.getPath(), new ConfigSourcePathConsumer(context, configSources)); | ||
} catch (IOException e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} catch (IllegalArgumentException e) { | ||
fallbackToUnknownProtocol(uri, configSources); | ||
} | ||
} | ||
|
||
private void tryJar(final URI uri, final ConfigSourceContext context, final List<ConfigSource> configSources) { | ||
try { | ||
consumeAsPath(toURL(uri), new ConfigSourcePathConsumer(context, configSources)); | ||
} catch (Exception e) { | ||
throw ConfigMessages.msg.failedToLoadResource(e); | ||
} | ||
} | ||
|
||
private void fallbackToUnknownProtocol(final URI uri, final List<ConfigSource> configSources) { | ||
final ClassLoader classLoader = SecuritySupport.getContextClassLoader(); | ||
try { | ||
Enumeration<URL> resources = classLoader.getResources(uri.toString()); | ||
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 URI uri, final ConfigSourceContext context, final List<ConfigSource> configSources) { | ||
if (validExtension(uri.getPath())) { | ||
addConfigSource(uri, configSources); | ||
tryProfiles(uri, context, configSources); | ||
} | ||
} | ||
|
||
private void tryProfiles(final URI uri, final ConfigSourceContext context, final List<ConfigSource> configSources) { | ||
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 = toURL(addProfileToFileName(uri, profiles.get(i))); | ||
consumeAsPath(profileToFileName, path -> addProfileConfigSource(profileToFileName, 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 URI uri, final List<ConfigSource> configSources) { | ||
addConfigSource(toURL(uri), configSources); | ||
} | ||
|
||
private void addConfigSource(final URL url, final List<ConfigSource> configSources) { | ||
try { | ||
final ConfigSource configSource = loadConfigSource(url); | ||
if (!configSource.getPropertyNames().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.getPropertyNames().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 Path fileName) { | ||
return validExtension(fileName.getFileName().toString()); | ||
} | ||
|
||
private boolean validExtension(final String resourceName) { | ||
for (String s : getFileExtensions()) { | ||
if (resourceName.endsWith(s)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private static URI addProfileToFileName(final URI uri, final String profile) { | ||
if (uri.getScheme().equals("jar")) { | ||
return URI.create("jar:" + addProfileToFileName(URI.create(uri.getSchemeSpecificPart()), profile)); | ||
} | ||
|
||
final String fileName = uri.getPath(); | ||
assert fileName != null; | ||
|
||
final int dot = fileName.lastIndexOf("."); | ||
final String fileNameProfile; | ||
if (dot != -1) { | ||
fileNameProfile = fileName.substring(0, dot) + "-" + profile + fileName.substring(dot); | ||
} else { | ||
fileNameProfile = fileName + "-" + profile; | ||
} | ||
|
||
try { | ||
return new URI(uri.getScheme(), | ||
uri.getAuthority(), | ||
uri.getHost(), | ||
uri.getPort(), | ||
fileNameProfile, | ||
uri.getQuery(), | ||
uri.getFragment()); | ||
} catch (URISyntaxException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
|
||
private static class URIConverter implements Converter<URI> { | ||
private static final long serialVersionUID = -4852082279190307320L; | ||
|
||
@Override | ||
public URI convert(final String value) { | ||
try { | ||
return new URI(value); | ||
} catch (URISyntaxException e) { | ||
throw ConfigMessages.msg.uriSyntaxInvalid(e, value); | ||
} | ||
} | ||
} | ||
|
||
private class ConfigSourcePathConsumer implements Consumer<Path> { | ||
private final ConfigSourceContext context; | ||
private final List<ConfigSource> configSources; | ||
|
||
public ConfigSourcePathConsumer(final ConfigSourceContext context, final List<ConfigSource> configSources) { | ||
this.context = context; | ||
this.configSources = configSources; | ||
} | ||
|
||
@Override | ||
public void accept(final Path path) { | ||
final AbstractLocationConfigSourceFactory factory = AbstractLocationConfigSourceFactory.this; | ||
if (factory.validExtension(path.getFileName().toString())) { | ||
factory.addConfigSource(path.toUri(), configSources); | ||
factory.tryProfiles(path.toUri(), context, configSources); | ||
} | ||
} | ||
} | ||
} |
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.