package mil.nga.giat.geowave.core.cli.operations.config.options;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.Parameter;
import mil.nga.giat.geowave.core.cli.Constants;
import mil.nga.giat.geowave.core.cli.VersionUtils;
import mil.nga.giat.geowave.core.cli.api.OperationParams;
import mil.nga.giat.geowave.core.cli.operations.config.security.utils.SecurityUtils;
import mil.nga.giat.geowave.core.cli.utils.JCommanderParameterUtils;
/**
* Config options allows the user to override the default location for
* configuration options, and also allows commands to load the properties needed
* for running the program.
*/
public class ConfigOptions
{
public static final String CHARSET = "ISO-8859-1";
private final static Logger LOGGER = LoggerFactory.getLogger(ConfigOptions.class);
public final static String PROPERTIES_FILE_CONTEXT = "properties-file";
public final static String GEOWAVE_CACHE_PATH = ".geowave";
public final static String GEOWAVE_CACHE_FILE = "config.properties";
/**
* Allow the user to override the config file location
*/
@Parameter(names = {
"-cf",
"--config-file"
}, description = "Override configuration file (default is <home>/.geowave/config.properties)")
private String configFile;
public ConfigOptions() {
}
public String getConfigFile() {
return configFile;
}
public void setConfigFile(
final String configFilePath ) {
configFile = configFilePath;
}
/**
* The default property file is in the user's home directory, in the
* .geowave folder.
*
* @return
*/
public static File getDefaultPropertyPath() {
// File location
// HP Fortify "Path Manipulation" false positive
// What Fortify considers "user input" comes only
// from users with OS-level access anyway
final String cachePath = String.format(
"%s%s%s",
System.getProperty("user.home"),
File.separator,
GEOWAVE_CACHE_PATH);
return new File(
cachePath);
}
/**
* The default property file is in the user's home directory, in the
* .geowave folder. If the version can not be found the first available
* property file in the folder is used
*
* @return Default Property File
*/
public static File getDefaultPropertyFile() {
// HP Fortify "Path Manipulation" false positive
// What Fortify considers "user input" comes only
// from users with OS-level access anyway
final File defaultPath = getDefaultPropertyPath();
final String version = VersionUtils.getVersion();
if (version != null) {
return formatConfigFile(
version,
defaultPath);
}
else {
final String[] configFiles = defaultPath.list(new FilenameFilter() {
@Override
public boolean accept(
File dir,
String name ) {
return name.endsWith("-config.properties");
}
});
if (configFiles != null && configFiles.length > 0) {
final String backupVersion = configFiles[0].substring(
0,
configFiles[0].length() - 18);
return formatConfigFile(
backupVersion,
defaultPath);
}
else {
return formatConfigFile(
"unknownversion",
defaultPath);
}
}
}
/**
* Configures a File based on a given path name and version
*
* @param version
* @param defaultPath
* @return Configured File
*/
public static File formatConfigFile(
String version,
File defaultPath ) {
// HP Fortify "Path Manipulation" false positive
// What Fortify considers "user input" comes only
// from users with OS-level access anyway
final String configFile = String.format(
"%s%s%s%s%s",
defaultPath.getAbsolutePath(),
File.separator,
version,
"-",
GEOWAVE_CACHE_FILE);
return new File(
configFile);
}
public static boolean writeProperties(
final File configFile,
final Properties properties,
Class<?> clazz,
String namespacePrefix ) {
try {
Properties tmp = new Properties() {
private static final long serialVersionUID = 1L;
@Override
public Set<Object> keySet() {
return Collections.unmodifiableSet(new TreeSet<Object>(
super.keySet()));
}
@Override
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<Object>(
super.keySet()));
}
};
// check if encryption is enabled - it is by default and would need
// to be explicitly disabled
if (Boolean.parseBoolean(properties.getProperty(
Constants.ENCRYPTION_ENABLED_KEY,
"true"))) {
// check if any values exist that need to be encrypted before
// written to properties
if (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
for (Annotation annotation : field.getAnnotations()) {
if (annotation.annotationType() == Parameter.class) {
Parameter parameter = (Parameter) annotation;
if (JCommanderParameterUtils.isPassword(parameter)) {
String storeFieldName = (namespacePrefix != null && !"".equals(namespacePrefix
.trim())) ? namespacePrefix + "." + field.getName() : field.getName();
if (properties.containsKey(storeFieldName)) {
String value = properties.getProperty(storeFieldName);
String encryptedValue = value;
try {
File tokenFile = SecurityUtils
.getFormattedTokenKeyFileForConfig(configFile);
encryptedValue = SecurityUtils.encryptAndHexEncodeValue(
value,
tokenFile.getAbsolutePath());
}
catch (Exception e) {
LOGGER.error(
"An error occurred encrypting specified password value: "
+ e.getLocalizedMessage(),
e);
encryptedValue = value;
}
properties.setProperty(
storeFieldName,
encryptedValue);
}
}
}
}
}
}
}
tmp.putAll(properties);
try (FileOutputStream str = new FileOutputStream(
configFile)) {
tmp.store(
str,
null);
}
}
catch (FileNotFoundException e) {
LOGGER.error(
"Could not find the property file.",
e);
return false;
}
catch (IOException e) {
LOGGER.error(
"Exception writing property file.",
e);
return false;
}
return true;
}
/**
* Write the given properties to the file, and log an error if an exception
* occurs.
*
* @return true if success, false if failure
*/
public static boolean writeProperties(
final File configFile,
final Properties properties ) {
return writeProperties(
configFile,
properties,
null,
null);
}
/**
* This helper function will load the properties file, or return null if it
* can't. It's designed to be used by other commands.
*
* @param delimiter
*/
public static Properties loadProperties(
final File configFile,
final String pattern ) {
Pattern p = null;
if (pattern != null) {
p = Pattern.compile(pattern);
}
// Load the properties file.
final Properties properties = new Properties();
InputStream is = null;
try {
if (p != null) {
try (FileInputStream input = new FileInputStream(
configFile); Scanner s = new Scanner(
input,
CHARSET)) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final PrintWriter writer = new PrintWriter(
new OutputStreamWriter(
out,
CHARSET));
while (s.hasNext()) {
final String line = s.nextLine();
if (p.matcher(
line).find()) {
writer.println(line);
}
}
writer.flush();
is = new ByteArrayInputStream(
out.toByteArray());
}
}
else {
is = new FileInputStream(
configFile);
}
properties.load(is);
}
catch (final IOException e) {
LOGGER.error(
"Could not find property cache file: " + configFile,
e);
return null;
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException e) {
LOGGER.error(
e.getMessage(),
e);
}
}
}
return properties;
}
/**
* Load the properties file into the input params.
*
* @param inputParams
*/
public void prepare(
OperationParams inputParams ) {
File propertyFile = null;
if (getConfigFile() != null) {
propertyFile = new File(
getConfigFile());
}
else {
propertyFile = getDefaultPropertyFile();
}
// Set the properties on the context.
inputParams.getContext().put(
PROPERTIES_FILE_CONTEXT,
propertyFile);
}
}