Skip to content

Commit

Permalink
Feat/#309 read config from arbitrary directories (#386)
Browse files Browse the repository at this point in the history
* - Allowed exclusion list of files which should not check/allow values injection

* - Rename the exclusions.yml to config.yml

* - Remove the extensions from config.yml

* Reducing some duplication in Config.java for potential future changes and adding a couple test cases to the CentralizedManagement class

* - Added typeCast method to cast environment variables and default value
- trim the default value
- Added tests for type cast
- Added test for exclusion injection
- Since the mergeMap is merge the map in place, I deleted the return

* merge different with develop

* - Added comments

* - Change comments

* - Supported absolute path for the config file in Config module

* - Supported reading configurations from arbitrary directories by overloading method

* - Added comments

* - Set same property for all test to eliminate conflicts caused by singleton

* - use reflection to inject field in ConfigPropertyPathTest

* - Improved the boolean casting
  • Loading branch information
jiachen1120 authored and stevehu committed Feb 14, 2019
1 parent aa2a3a9 commit d13f126
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 34 deletions.
92 changes: 64 additions & 28 deletions config/src/main/java/com/networknt/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.networknt.config;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
Expand Down Expand Up @@ -49,16 +48,25 @@ protected Config() {
}

// abstract methods that need be implemented by all implementations

public abstract Map<String, Object> getJsonMapConfig(String configName);

public abstract Map<String, Object> getJsonMapConfig(String configName, String path);

public abstract Map<String, Object> getJsonMapConfigNoCache(String configName);

//public abstract JsonNode getJsonNodeConfig(String configName);
public abstract Map<String, Object> getJsonMapConfigNoCache(String configName, String path);

// public abstract JsonNode getJsonNodeConfig(String configName);

public abstract Object getJsonObjectConfig(String configName, Class clazz);

public abstract Object getJsonObjectConfig(String configName, Class clazz, String path);

public abstract String getStringFromFile(String filename);

public abstract String getStringFromFile(String filename, String path);

public abstract InputStream getInputStreamFromFile(String filename);

public abstract ObjectMapper getMapper();
Expand Down Expand Up @@ -121,35 +129,40 @@ public void clear() {
}

@Override
public String getStringFromFile(String filename) {
public String getStringFromFile(String filename, String path) {
checkCacheExpiration();
String content = (String) configCache.get(filename);
if (content == null) {
synchronized (FileConfigImpl.class) {
content = (String) configCache.get(filename);
if (content == null) {
content = loadStringFromFile(filename);
content = loadStringFromFile(filename, path);
if (content != null) configCache.put(filename, content);
}
}
}
return content;
}

@Override
public String getStringFromFile(String filename) {
return getStringFromFile(filename, "");
}

@Override
public InputStream getInputStreamFromFile(String filename) {
return getConfigStream(filename);
return getConfigStream(filename, "");
}

@Override
public Object getJsonObjectConfig(String configName, Class clazz) {
public Object getJsonObjectConfig(String configName, Class clazz, String path) {
checkCacheExpiration();
Object config = configCache.get(configName);
if (config == null) {
synchronized (FileConfigImpl.class) {
config = configCache.get(configName);
if (config == null) {
config = loadObjectConfig(configName, clazz);
config = loadObjectConfig(configName, clazz, path);
if (config != null) configCache.put(configName, config);
}
}
Expand All @@ -158,31 +171,46 @@ public Object getJsonObjectConfig(String configName, Class clazz) {
}

@Override
public Map<String, Object> getJsonMapConfig(String configName) {
public Object getJsonObjectConfig(String configName, Class clazz) {
return getJsonObjectConfig(configName, clazz, "");
}

@Override
public Map<String, Object> getJsonMapConfig(String configName, String path) {
checkCacheExpiration();
Map<String, Object> config = (Map<String, Object>) configCache.get(configName);
if (config == null) {
synchronized (FileConfigImpl.class) {
config = (Map<String, Object>) configCache.get(configName);
if (config == null) {
config = loadMapConfig(configName);
config = loadMapConfig(configName, path);
if (config != null) configCache.put(configName, config);
}
}
}
return config;
}

@Override
public Map<String, Object> getJsonMapConfig(String configName) {
return getJsonMapConfig(configName, "");
}

@Override
public Map<String, Object> getJsonMapConfigNoCache(String configName, String path) {
return loadMapConfig(configName, path);
}

@Override
public Map<String, Object> getJsonMapConfigNoCache(String configName) {
return loadMapConfig(configName);
return getJsonMapConfigNoCache(configName, "");
}

private String loadStringFromFile(String filename) {
private String loadStringFromFile(String filename, String path) {
String content = null;
InputStream inStream = null;
try {
inStream = getConfigStream(filename);
inStream = getConfigStream(filename, path);
if (inStream != null) {
content = convertStreamToString(inStream);
}
Expand All @@ -202,16 +230,17 @@ private String loadStringFromFile(String filename) {

/**
* Helper method to reduce duplication of loading a given file as a given Object.
* @param configName The name of the config file, without an extension
* @param configName The name of the config file, without an extension
* @param fileExtension The extension (with a leading .)
* @param clazz The class that the object will be deserialized into.
* @param <T> The type of the class file should be the type of the object returned.
* @param clazz The class that the object will be deserialized into.
* @param <T> The type of the class file should be the type of the object returned.
* @param path The relative directory that config will be loaded from
* @return An instance of the object if possible, null otherwise. IOExceptions smothered.
*/
private <T> Object loadSpecificConfigFileAsObject(String configName, String fileExtension, Class<T> clazz) {
private <T> Object loadSpecificConfigFileAsObject(String configName, String fileExtension, Class<T> clazz, String path) {
Object config = null;
String fileName = configName + fileExtension;
try (InputStream inStream = getConfigStream(fileName)) {
try (InputStream inStream = getConfigStream(fileName, path)) {
if (inStream != null) {
// The config file specified in the config.yml shouldn't be injected
if (ConfigInjection.isExclusionConfigFile(configName)) {
Expand All @@ -228,25 +257,26 @@ private <T> Object loadSpecificConfigFileAsObject(String configName, String file
return config;
}

private <T> Object loadObjectConfig(String configName, Class<T> clazz) {
private <T> Object loadObjectConfig(String configName, Class<T> clazz, String path) {
Object config;
for (String extension : configExtensionsOrdered) {
config = loadSpecificConfigFileAsObject(configName, extension, clazz);
config = loadSpecificConfigFileAsObject(configName, extension, clazz, path);
if (config != null) return config;
}
return null;
}

/**
* Helper method to reduce duplication of loading a given config file as a Map.
* @param configName The name of the config file, without an extension
* @param configName The name of the config file, without an extension
* @param fileExtension The extension (with a leading .)
* @param path The relative directory that config will be loaded from
* @return A map of the config fields if possible, null otherwise. IOExceptions smothered.
*/
private Map<String, Object> loadSpecificConfigFileAsMap(String configName, String fileExtension) {
private Map<String, Object> loadSpecificConfigFileAsMap(String configName, String fileExtension, String path) {
Map<String, Object> config = null;
String ymlFilename = configName + fileExtension;
try (InputStream inStream = getConfigStream(ymlFilename)) {
try (InputStream inStream = getConfigStream(ymlFilename, path)) {
if (inStream != null) {
config = yaml.load(inStream);
if (!ConfigInjection.isExclusionConfigFile(configName)) {
Expand All @@ -259,28 +289,29 @@ private Map<String, Object> loadSpecificConfigFileAsMap(String configName, Strin
return config;
}

private Map<String, Object> loadMapConfig(String configName) {
private Map<String, Object> loadMapConfig(String configName, String path) {
Map<String, Object> config;
for (String extension : configExtensionsOrdered) {
config = loadSpecificConfigFileAsMap(configName, extension);
config = loadSpecificConfigFileAsMap(configName, extension, path);
if (config != null) return config;
}
return null;
}

private InputStream getConfigStream(String configFilename) {
private InputStream getConfigStream(String configFilename, String path) {

InputStream inStream = null;
String absolutePath = getAbsolutePath(path);
try {
inStream = new FileInputStream(EXTERNALIZED_PROPERTY_DIR + "/" + configFilename);
inStream = new FileInputStream(absolutePath + "/" + configFilename);
} catch (FileNotFoundException ex) {
if (logger.isInfoEnabled()) {
logger.info("Unable to load config from externalized folder for " + Encode.forJava(configFilename + " in " + EXTERNALIZED_PROPERTY_DIR));
logger.info("Unable to load config from externalized folder for " + Encode.forJava(configFilename + " in " + absolutePath));
}
}
if (inStream != null) {
if (logger.isInfoEnabled()) {
logger.info("Config loaded from externalized folder for " + Encode.forJava(configFilename + " in " + EXTERNALIZED_PROPERTY_DIR));
logger.info("Config loaded from externalized folder for " + Encode.forJava(configFilename + " in " + absolutePath));
}
return inStream;
}
Expand Down Expand Up @@ -328,6 +359,11 @@ private void checkCacheExpiration() {
cacheExpirationTime = getNextMidNightTime();
}
}

// private method used to get absolute directory based on relative directory
private String getAbsolutePath(String path) {
return path.equals("") ? EXTERNALIZED_PROPERTY_DIR : EXTERNALIZED_PROPERTY_DIR + "/" + path;
}
}

static String convertStreamToString(java.io.InputStream is) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public class ConfigClassPathTest extends TestCase {
@Override
public void setUp() throws Exception {
super.setUp();

config = Config.getInstance();

// write a config file into the user home directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,64 @@
import org.junit.Assert;

import java.io.File;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class ConfigPropertyPathTest extends TestCase {

private Config config = null;

final String homeDir = System.getProperty("user.home");

@Override
public void setUp() throws Exception {
super.setUp();
System.setProperty("light-4j-config-dir", homeDir);

Config config = Config.getInstance();
// the instance would already be created by other classes since the config is singleton, so need to using
// reflection to inject field.
config = Config.getInstance();
Field f1 = config.getClass().getDeclaredField("EXTERNALIZED_PROPERTY_DIR");
f1.setAccessible(true);
f1.set(config, homeDir);

// write a config file
Map<String, Object> map = new HashMap<>();
map.put("value", "default config");
config.getMapper().writeValue(new File(homeDir + "/test.json"), map);
// write another config file with the same name but in different path
Map<String, Object> map2 = new HashMap<>();
map2.put("value", "another config");
new File(homeDir + "/src").mkdirs();
config.getMapper().writeValue(new File(homeDir + "/src/test.json"), map2);
}

@Override
public void tearDown() throws Exception {
File test = new File(homeDir + "/test.json");
test.delete();
File test1 = new File(homeDir + "/test.json");
File test2 = new File(homeDir + "/src/test.json");
test1.delete();
test2.delete();
}

// test getting config from light-4j-config-dir
public void testGetConfig() throws Exception {
Config config = Config.getInstance();
config.clear();
Map<String, Object> configMap = config.getJsonMapConfig("test");
Assert.assertEquals("default config", configMap.get("value"));
}

// test getting map config from a relative path "src"
public void testGetConfigFromRelPath() {
config.clear();
Map<String, Object> configMap = config.getJsonMapConfig("test", "src");
Assert.assertEquals("another config", configMap.get("value"));
}

// test getting object config from a relative path "src"
public void testGetObjectConfigFromRelPath() {
config.clear();
TestConfig configObject = (TestConfig) config.getJsonObjectConfig("test", TestConfig.class, "src");
Assert.assertEquals("another config", configObject.getValue());
}
}

0 comments on commit d13f126

Please sign in to comment.