Merge branch 'feature/properties_files' of Locusworks/crypto into develop

This commit is contained in:
Isaac Parenteau
2019-10-05 23:11:31 -05:00
committed by Gitea
7 changed files with 454 additions and 0 deletions

View File

@ -0,0 +1,6 @@
package net.locusworks.crypto.configuration;
@FunctionalInterface
public interface ConfigurationCallback {
void results(String msg);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,5 @@
package net.locusworks.crypto.configuration;
public interface PersistableRequest {
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View 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>

View File

@ -0,0 +1,4 @@
userExpirationDays=3650
dbHost=localhost
dbPort=3306
logLevel=INFO