Merge branch 'feature/properties_files' of Locusworks/crypto into develop
This commit is contained in:
@ -0,0 +1,6 @@
|
||||
package net.locusworks.crypto.configuration;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ConfigurationCallback {
|
||||
void results(String msg);
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package net.locusworks.crypto.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import net.locusworks.crypto.AES;
|
||||
|
||||
public class ConfigurationManager {
|
||||
|
||||
private Properties configuration;
|
||||
private Properties defaults = null;
|
||||
private Path conf = null;
|
||||
|
||||
protected AES aes;
|
||||
|
||||
private Consumer<String> callback;
|
||||
|
||||
protected void init(String baseDir, String propertiesFile, Consumer<String> callback) throws IOException {
|
||||
init(baseDir, propertiesFile, this.getClass().getName().getBytes(StandardCharsets.UTF_8), callback);
|
||||
}
|
||||
|
||||
protected void init(String baseDir, String propertiesFile, byte[] aesKey, Consumer<String> callback) throws IOException {
|
||||
aes = aesKey.length > 0 ? AES.createInstance(aesKey) : AES.createInstance();
|
||||
this.callback = callback;
|
||||
try {
|
||||
defaults = PropertiesManager.loadConfiguration(this.getClass(), propertiesFile);
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
// create patchrepoConf File object
|
||||
conf = Paths.get(baseDir).resolve(propertiesFile);
|
||||
|
||||
loadConfiguration();
|
||||
}
|
||||
|
||||
private void loadConfiguration() {
|
||||
// load the active config file
|
||||
// ignore read error, we can continue with an empty configuration map
|
||||
// and all default items will be added below and the file created
|
||||
|
||||
callbackMessage("Loading config file: " + conf);
|
||||
try {
|
||||
configuration = PropertiesManager.loadConfiguration(conf);
|
||||
} catch (Exception e) {
|
||||
callbackMessage("Config file: " + conf + " will be created from template");
|
||||
configuration = new Properties();
|
||||
}
|
||||
|
||||
Map<String, String> results = PropertiesManager.addConfiguration(configuration, defaults);
|
||||
boolean changed = !results.isEmpty();
|
||||
if (!results.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder("Added new configuration items:\n");
|
||||
for (Entry<String, String> entry : results.entrySet()) {
|
||||
sb.append(String.format("%s=%s\n", entry.getKey(), entry.getValue()));
|
||||
}
|
||||
callbackMessage(sb.toString());
|
||||
}
|
||||
|
||||
results = PropertiesManager.removeConfiguration(configuration, defaults);
|
||||
changed |= !results.isEmpty();
|
||||
if (!results.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder("Added new configuration items:\n");
|
||||
for (Entry<String, String> entry : results.entrySet()) {
|
||||
sb.append(String.format("%s=%s\n", entry.getKey(), entry.getValue()));
|
||||
}
|
||||
callbackMessage(sb.toString());
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
PropertiesManager.saveConfiguration(configuration, conf, "Patch Repository properties file");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the configuration values to file
|
||||
* @param confs Configuration property values to save
|
||||
* @throws Exception general exception
|
||||
*/
|
||||
public void saveToConf(Properties confs) throws Exception {
|
||||
PropertiesManager.saveConfiguration(confs, conf, conf.getFileName().toString());
|
||||
callbackMessage("Saved config file: " + conf + ", " + confs.size() + " entries");
|
||||
loadConfiguration();
|
||||
}
|
||||
|
||||
public String getPropertyValue(String key) {
|
||||
return getPropertyValue(key, null);
|
||||
}
|
||||
public String getPropertyValue(String key, String defaultValue) {
|
||||
return configuration.containsKey(key) ? configuration.getProperty(key) : defaultValue;
|
||||
}
|
||||
|
||||
public Properties getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void saveConfiguration(PersistableRequest request, Set<String> fieldsToSave, Set<String> excryptedFields) throws IOException {
|
||||
if (fieldsToSave == null || fieldsToSave.isEmpty()) {
|
||||
throw new IOException("No fields to save were defined");
|
||||
}
|
||||
if (excryptedFields == null) {
|
||||
excryptedFields = new HashSet<>();
|
||||
}
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
|
||||
//copy what is current in the configuration settings into the new properties file
|
||||
configuration.entrySet().forEach(item -> props.setProperty(String.valueOf(item.getKey()), String.valueOf(item.getValue())));
|
||||
boolean changed = false;
|
||||
for (Field f : request.getClass().getDeclaredFields()) {
|
||||
f.setAccessible(true);
|
||||
String fieldName = f.getName();
|
||||
String fieldValue = String.valueOf(f.get(request));
|
||||
|
||||
//Ensures we are only saving values that are already configured
|
||||
if (!fieldsToSave.contains(fieldName)) continue;
|
||||
|
||||
//Check to see if the old value changed
|
||||
String oldValue = props.getProperty(fieldName);
|
||||
if (StringUtils.isAnyBlank(oldValue, fieldValue) || oldValue.equals(fieldValue)) continue;
|
||||
|
||||
changed = true;
|
||||
|
||||
fieldValue = excryptedFields.contains(fieldName) ? aes.encrypt(fieldValue) : fieldValue;
|
||||
|
||||
props.setProperty(fieldName, fieldValue);
|
||||
}
|
||||
if (changed) {
|
||||
saveToConf(props);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void callbackMessage(String msg) {
|
||||
if (callback != null) callback.accept(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package net.locusworks.crypto.configuration;
|
||||
|
||||
public interface PersistableRequest {
|
||||
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package net.locusworks.crypto.configuration;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
/**
|
||||
* Properties manager class to help load and read properties
|
||||
* @author Isaac Parenteau
|
||||
* @version 1.0.0
|
||||
* @date 02/15/2018
|
||||
*/
|
||||
public class PropertiesManager {
|
||||
/**
|
||||
* Load a configuration from resource
|
||||
* @param clazz class loader
|
||||
* @param src source of the file
|
||||
* @return properties
|
||||
* @throws IOException Exception thrown the file can't be read
|
||||
*/
|
||||
public static Properties loadConfiguration(Class<?> clazz, String src) throws IOException {
|
||||
InputStream is = clazz.getResourceAsStream(src);
|
||||
if (is == null) {
|
||||
is = clazz.getClassLoader().getResourceAsStream(src);
|
||||
}
|
||||
if (is == null) {
|
||||
return null;
|
||||
}
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||
return loadConfiguration(br);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from a file. This method has been deprecated and may be removed in
|
||||
* future released. Please use java.nio.Path
|
||||
* @param file File to load
|
||||
* @return properties
|
||||
* @throws IOException Exception thrown the file can't be read
|
||||
*/
|
||||
@Deprecated
|
||||
public static Properties loadConfiguration(File file) throws IOException {
|
||||
return loadConfiguration(file.toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from a file.
|
||||
* @param path the path to the file
|
||||
* @return properties
|
||||
* @throws IOException Exception thrown the file can't be read
|
||||
*/
|
||||
public static Properties loadConfiguration(Path path) throws IOException {
|
||||
if (Files.notExists(path)) {
|
||||
return new Properties();
|
||||
}
|
||||
|
||||
try(BufferedReader br = Files.newBufferedReader(path)) {
|
||||
return loadConfiguration(br);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from a buffered reader
|
||||
* @param reader Buffered reader to read the properties values from
|
||||
* @return properties
|
||||
* @throws IOException Exception thrown the file can't be read
|
||||
*/
|
||||
public static Properties loadConfiguration(BufferedReader reader) throws IOException {
|
||||
Properties config = new Properties();
|
||||
config.load(reader);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add configurations from one properties file to another
|
||||
* @param to Properties file to copy values to
|
||||
* @param from Properties file to copy values from
|
||||
* @return a map containing the results of the values added
|
||||
*/
|
||||
public static Map<String, String> addConfiguration(Properties to, Properties from) {
|
||||
Map<String, String> results = from.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !to.containsKey(entry.getKey()))
|
||||
.map(entry -> {
|
||||
String key = entry.getKey().toString();
|
||||
String value = entry.getValue().toString();
|
||||
to.put(key, value);
|
||||
return new ImmutablePair<String, String>(key, value);
|
||||
})
|
||||
.collect(Collectors.toMap(key -> key.getLeft(), value -> value.getRight()));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes configuration values that are not present in the comparedTo
|
||||
* @param from Properties file to remove values from
|
||||
* @param comparedTo Properties file to compare to
|
||||
* @return a map containing the results of the values removed
|
||||
*/
|
||||
public static Map<String, String> removeConfiguration(Properties from, Properties comparedTo) {
|
||||
Map<String, String> results = from.keySet()
|
||||
.stream()
|
||||
.filter(key -> !comparedTo.containsKey(key)) //only get the items that are not in the comparedTo properties
|
||||
.map(key -> new ImmutablePair<String, String>(String.valueOf(key), String.valueOf(from.get(key))))
|
||||
.collect(Collectors.toList()) //Create a list of paired items (key value) of the items that were filtered
|
||||
.stream()
|
||||
.map(pair -> { //remove those pairs from the from properties
|
||||
from.remove(pair.getLeft());
|
||||
return pair;
|
||||
})
|
||||
.collect(Collectors.toMap(key -> key.getLeft(), value -> value.getRight())); //create a map of what was removed
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Save the properties file to disk.</p>
|
||||
* <p><b>This method has been depecreated and could be removed in future release.<br/>Please use java.nio.Path</b></p>
|
||||
* @param props Properties file to save
|
||||
* @param fileToSave File to save to
|
||||
* @param comment Any comments to add
|
||||
*/
|
||||
@Deprecated
|
||||
public static void saveConfiguration(Properties props, File fileToSave, String comment) {
|
||||
saveConfiguration(props, fileToSave.toPath(), comment);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Save the properties file to disk.</p>
|
||||
* @param props Properties file to save
|
||||
* @param fileToSave File to save to
|
||||
* @param comment Any comments to add
|
||||
*/
|
||||
public static void saveConfiguration(Properties props, Path fileToSave, String comment) {
|
||||
try(OutputStream fos = Files.newOutputStream(fileToSave)) {
|
||||
props.store(fos, comment == null ? "" : comment);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package net.locusworks.crypto.tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.locusworks.crypto.configuration.PropertiesManager;
|
||||
|
||||
/**
|
||||
* Test cases for the properties manager class
|
||||
* @author Isaac Parenteau
|
||||
* @since 1.0.0-RELEASE
|
||||
*
|
||||
*/
|
||||
public class PropertiesManagerTest {
|
||||
|
||||
private static final String PROPERTIES_FILE = "test.properties";
|
||||
private static final String TMP_PROPS = "temp.properties";
|
||||
private static final int ENTRY_SIZE = 4;
|
||||
|
||||
public static enum Configuration {
|
||||
DB_HOST("dbHost"),
|
||||
DB_PORT("dbPort"),
|
||||
USER_EXPIRATION_DAYS("userExpirationDays"),
|
||||
LOG_LEVEL("logLevel");
|
||||
|
||||
private String value;
|
||||
|
||||
private Configuration(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current value of the enumeration
|
||||
* @return value
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void removeSavedProps() {
|
||||
File tmp = new File(TMP_PROPS);
|
||||
if (tmp.exists()) {
|
||||
tmp.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesLoad() {
|
||||
try {
|
||||
Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE);
|
||||
assertTrue(props != null);
|
||||
assertTrue(props.containsKey(Configuration.USER_EXPIRATION_DAYS.toString()));
|
||||
assertTrue(props.containsKey(Configuration.DB_HOST.toString()));
|
||||
assertTrue(props.containsKey(Configuration.DB_PORT.toString()));
|
||||
assertTrue(props.containsKey(Configuration.LOG_LEVEL.toString()));
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddConfiguration() {
|
||||
try {
|
||||
Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE);
|
||||
Properties tmp = new Properties();
|
||||
assertTrue(tmp.keySet().size() == 0);
|
||||
PropertiesManager.addConfiguration(tmp, props);
|
||||
assertTrue(tmp.keySet().size() == ENTRY_SIZE);
|
||||
assertTrue(tmp.containsKey(Configuration.USER_EXPIRATION_DAYS.toString()));
|
||||
assertTrue(tmp.containsKey(Configuration.DB_HOST.toString()));
|
||||
assertTrue(tmp.containsKey(Configuration.DB_PORT.toString()));
|
||||
assertTrue(tmp.containsKey(Configuration.LOG_LEVEL.toString()));
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveConfiguration() {
|
||||
try {
|
||||
Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE);
|
||||
Properties tmp = new Properties();
|
||||
assertTrue(props.keySet().size() == ENTRY_SIZE);
|
||||
assertTrue(tmp.keySet().size() == 0);
|
||||
PropertiesManager.removeConfiguration(props, tmp);
|
||||
assertTrue(props.keySet().size() == 0);
|
||||
assertTrue(tmp.keySet().size() == 0);
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveConfiguration() {
|
||||
try {
|
||||
Properties props = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE);
|
||||
Path tmpFile = Paths.get(TMP_PROPS);
|
||||
PropertiesManager.saveConfiguration(props, tmpFile, "test propertis");
|
||||
Properties tmp = PropertiesManager.loadConfiguration(tmpFile);
|
||||
assertTrue(tmp.keySet().size() == ENTRY_SIZE);
|
||||
assertTrue(tmp.containsKey(Configuration.USER_EXPIRATION_DAYS.toString()));
|
||||
assertTrue(tmp.containsKey(Configuration.DB_HOST.toString()));
|
||||
assertTrue(tmp.containsKey(Configuration.DB_PORT.toString()));
|
||||
assertTrue(tmp.containsKey(Configuration.LOG_LEVEL.toString()));
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
13
src/test/resources/log4j2-test.xml
Normal file
13
src/test/resources/log4j2-test.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<Console name="ConsoleAppender" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{dd-MMM-yyyy HH:mm:ss.SSS} [%-5p] %m%n" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="TRACE">
|
||||
<AppenderRef ref="ConsoleAppender" level="INFO"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
4
src/test/resources/test.properties
Normal file
4
src/test/resources/test.properties
Normal file
@ -0,0 +1,4 @@
|
||||
userExpirationDays=3650
|
||||
dbHost=localhost
|
||||
dbPort=3306
|
||||
logLevel=INFO
|
Reference in New Issue
Block a user