Skip to content

Commit

Permalink
Support .env sources. (#500)
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez authored Feb 4, 2021
1 parent 28ebb3d commit 20fa186
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ protected List<ConfigSource> tryProfiles(final URI uri, final ConfigSource mainS
configSources.add(new ConfigurableConfigSource((ProfileConfigSourceFactory) profiles -> {
final List<ConfigSource> profileSources = new ArrayList<>();
for (int i = profiles.size() - 1; i >= 0; i--) {
final int ordinal = mainSource.getOrdinal() + profiles.size() - i + 1;
final int ordinal = mainSource.getOrdinal() + profiles.size() - i;
final URI profileUri = addProfileName(uri, profiles.get(i));
addProfileConfigSource(toURL(profileUri), ordinal, profileSources);
}
Expand Down Expand Up @@ -244,7 +244,7 @@ private static URI addProfileName(final URI uri, final String profile) {

final int dot = fileName.lastIndexOf(".");
final String fileNameProfile;
if (dot != -1) {
if (dot != -1 && dot != 0 && fileName.charAt(dot - 1) != '/') {
fileNameProfile = fileName.substring(0, dot) + "-" + profile + fileName.substring(dot);
} else {
fileNameProfile = fileName + "-" + profile;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.smallrye.config;

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

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

import io.smallrye.config.common.utils.ConfigSourceUtil;

public class DotEnvConfigSourceProvider extends AbstractLocationConfigSourceLoader implements ConfigSourceProvider {
private final String location;

public DotEnvConfigSourceProvider() {
this(".env");
}

public DotEnvConfigSourceProvider(final String location) {
this.location = location;
}

@Override
protected String[] getFileExtensions() {
return new String[] { "" };
}

@Override
protected ConfigSource loadConfigSource(final URL url) throws IOException {
return loadConfigSource(url, 295);
}

@Override
protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException {
return new EnvConfigSource(ConfigSourceUtil.urlToMap(url), ordinal) {
@Override
public String getName() {
return super.getName() + "[source=" + url + "]";
}
};
}

@Override
public List<ConfigSource> getConfigSources(final ClassLoader forClassLoader) {
return loadConfigSources(location, forClassLoader);
}

public static List<ConfigSource> dotEnvSources(final ClassLoader classLoader) {
return new DotEnvConfigSourceProvider().getConfigSources(classLoader);
}

public static List<ConfigSource> dotEnvSources(final String location, final ClassLoader classLoader) {
return new DotEnvConfigSourceProvider(location).getConfigSources(classLoader);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.smallrye.config;

import static io.smallrye.config.common.utils.ConfigSourceUtil.CONFIG_ORDINAL_KEY;
import static java.security.AccessController.doPrivileged;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;

import java.io.Serializable;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import io.smallrye.config.common.AbstractConfigSource;
import io.smallrye.config.common.MapBackedConfigSource;

/**
* @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2017 Red Hat inc.
*/
public class EnvConfigSource extends AbstractConfigSource {
public class EnvConfigSource extends MapBackedConfigSource {
private static final long serialVersionUID = -4525015934376795496L;

private static final int DEFAULT_ORDINAL = 300;
Expand All @@ -41,21 +39,23 @@ public class EnvConfigSource extends AbstractConfigSource {
private final Map<String, Object> cache = new ConcurrentHashMap<>();

protected EnvConfigSource() {
super("EnvConfigSource", getEnvOrdinal());
this(DEFAULT_ORDINAL);
}

@Override
public Map<String, String> getProperties() {
return getEnvProperties();
protected EnvConfigSource(final int ordinal) {
this(getEnvProperties(), ordinal);
}

@Override
public Set<String> getPropertyNames() {
return unmodifiableSet(getProperties().keySet());
public EnvConfigSource(final Map<String, String> propertyMap, final int ordinal) {
super("EnvConfigSource", propertyMap, getEnvOrdinal(propertyMap, ordinal));
}

@Override
public String getValue(String name) {
public String getValue(final String propertyName) {
return getValue(propertyName, getProperties(), cache);
}

private static String getValue(final String name, final Map<String, String> properties, final Map<String, Object> cache) {
if (name == null) {
return null;
}
Expand All @@ -68,8 +68,6 @@ public String getValue(String name) {
return (String) cachedValue;
}

final Map<String, String> properties = getProperties();

// exact match
String value = properties.get(name);
if (value != null) {
Expand All @@ -79,7 +77,6 @@ public String getValue(String name) {

// replace non-alphanumeric characters by underscores
String sanitizedName = replaceNonAlphanumericByUnderscores(name);

value = properties.get(sanitizedName);
if (value != null) {
cache.put(name, value);
Expand Down Expand Up @@ -117,35 +114,16 @@ private static Map<String, String> getEnvProperties() {
return unmodifiableMap(doPrivileged((PrivilegedAction<Map<String, String>>) System::getenv));
}

/**
* TODO
* Ideally, this should use {@link EnvConfigSource#getValue(String)} directly, so we don't duplicate the property
* names logic, but we need this method to be static.
*
* We do require a bigger change to rewrite {@link EnvConfigSource#getValue(String)} as static and still cache
* values in each separate instance.
*
* @return the {@link EnvConfigSource} ordinal.
*/
private static int getEnvOrdinal() {
Map<String, String> envProperties = getEnvProperties();
String ordStr = envProperties.get(CONFIG_ORDINAL_KEY);
if (ordStr != null) {
return Converters.INTEGER_CONVERTER.convert(ordStr);
}

String sanitazedOrdinalKey = replaceNonAlphanumericByUnderscores(CONFIG_ORDINAL_KEY);
ordStr = envProperties.get(sanitazedOrdinalKey);
if (ordStr != null) {
return Converters.INTEGER_CONVERTER.convert(ordStr);
}
private static String getEnvProperty(final String name) {
return doPrivileged((PrivilegedAction<String>) () -> System.getenv(name));
}

ordStr = envProperties.get(sanitazedOrdinalKey.toUpperCase());
if (ordStr != null) {
return Converters.INTEGER_CONVERTER.convert(ordStr);
private static int getEnvOrdinal(final Map<String, String> properties, final int ordinal) {
final String value = getValue(CONFIG_ORDINAL_KEY, properties, new HashMap<>());
if (value != null) {
return Converters.INTEGER_CONVERTER.convert(value);
}

return DEFAULT_ORDINAL;
return ordinal;
}

Object writeReplace() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.smallrye.config;

import static io.smallrye.config.DotEnvConfigSourceProvider.dotEnvSources;
import static io.smallrye.config.SecuritySupport.getContextClassLoader;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.FileOutputStream;
import java.nio.file.Path;
import java.util.Properties;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

class DotEnvConfigSourceProviderTest {
@Test
void dotEnvSource(@TempDir Path tempDir) throws Exception {
Properties envProperties = new Properties();
envProperties.setProperty("MY_PROP", "1234");
try (FileOutputStream out = new FileOutputStream(tempDir.resolve(".env").toFile())) {
envProperties.store(out, null);
}

SmallRyeConfig config = new SmallRyeConfigBuilder()
.addDefaultSources()
.addDiscoveredSources()
.addDefaultInterceptors()
.withSources(dotEnvSources(tempDir.resolve(".env").toFile().toURI().toString(), getContextClassLoader()))
.build();

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

@Test
void dotEnvSourceProfiles(@TempDir Path tempDir) throws Exception {
Properties mainProperties = new Properties();
mainProperties.setProperty("MY_PROP_MAIN", "main");
mainProperties.setProperty("MY_PROP_COMMON", "main");
mainProperties.setProperty("MY_PROP_PROFILE", "main");
try (FileOutputStream out = new FileOutputStream(tempDir.resolve(".env").toFile())) {
mainProperties.store(out, null);
}

Properties commonProperties = new Properties();
commonProperties.setProperty("MY_PROP_COMMON", "common");
commonProperties.setProperty("MY_PROP_PROFILE", "common");
try (FileOutputStream out = new FileOutputStream(tempDir.resolve(".env-common").toFile())) {
commonProperties.store(out, null);
}

Properties devProperties = new Properties();
devProperties.setProperty("MY_PROP_DEV", "dev");
devProperties.setProperty("MY_PROP_PROFILE", "dev");
try (FileOutputStream out = new FileOutputStream(tempDir.resolve(".env-dev").toFile())) {
devProperties.store(out, null);
}

SmallRyeConfig config = new SmallRyeConfigBuilder()
.addDefaultSources()
.addDiscoveredSources()
.addDefaultInterceptors()
.withProfile("common,dev")
.withSources(dotEnvSources(tempDir.resolve(".env").toFile().toURI().toString(), getContextClassLoader()))
.build();

assertEquals("main", config.getRawValue("my.prop.main"));
assertEquals("main", config.getRawValue("MY_PROP_MAIN"));
assertEquals("common", config.getRawValue("my.prop.common"));
assertEquals("common", config.getRawValue("MY_PROP_COMMON"));
assertEquals("dev", config.getRawValue("my.prop.profile"));
assertEquals("dev", config.getRawValue("MY_PROP_PROFILE"));
}
}

0 comments on commit 20fa186

Please sign in to comment.