/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.component;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.opengamma.util.ArgumentChecker;
/**
* Map of config values, handling restricted values such as passwords.
* <p>
* This class maintains a map of key-value pairs from configuration.
* Restricted values, such as passwords, are traced to avoid logging sensitive data.
* <p>
* This class is mutable.
*/
public final class ConfigProperties {
/**
* Text used for hidden configuration.
*/
public static final String HIDDEN = "*** HIDDEN ***";
/**
* The key-value pairs.
*/
private final LinkedHashMap<String, ConfigProperty> _properties = new LinkedHashMap<>();
//-------------------------------------------------------------------------
/**
* Gets the size of the map.
*
* @return the number of configured properties
*/
public int size() {
return _properties.size();
}
/**
* Checks if the key is present.
*
* @param key the key to find, null returns false
* @return true if the key is present
*/
public boolean containsKey(String key) {
return key != null && _properties.containsKey(key);
}
/**
* Gets the property for the specified key.
*
* @param key the key to find, not null
* @return the property, null if not found
*/
public ConfigProperty get(String key) {
ArgumentChecker.notNull(key, "key");
return _properties.get(key);
}
/**
* Gets the value for the specified key.
*
* @param key the key to find, not null
* @return the value, null if not found
*/
public String getValue(String key) {
ConfigProperty cp = get(key);
return (cp != null ? cp.getValue() : null);
}
/**
* Gets the set of keys.
*
* @return the set of keys, not null
*/
public Set<String> keySet() {
return _properties.keySet();
}
/**
* Gets the set of config properties.
*
* @return the set of properties, not null
*/
public Set<ConfigProperty> values() {
return new HashSet<>(_properties.values());
}
/**
* Gets the map of key-value pairs.
*
* @return a copy of the internal map, not null
*/
public LinkedHashMap<String, String> toMap() {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (ConfigProperty cp : _properties.values()) {
map.put(cp.getKey(), cp.getValue());
}
return map;
}
//-------------------------------------------------------------------------
/**
* Adds a configured key-value pair replacing any existing value.
* <p>
* This is a standard {@code Map.put(key,value)} operation.
* Most properties should be resolved and added using
* {@link #resolveProperty} and {@link #addIfAbsent}.
*
* @param key the key to add, not null
* @param value the value, not null
*/
public void put(String key, String value) {
ArgumentChecker.notNull(key, "key");
ArgumentChecker.notNull(value, "value");
_properties.put(key, ConfigProperty.of(key, value, false));
}
/**
* Adds all the specified key-value pairs replacing any existing value.
* <p>
* This is a standard {@code Map.putAll(Map)} operation.
* Most properties should be resolved and added using
* {@link #resolveProperty} and {@link #addIfAbsent}.
*
* @param map the map to add, not null
*/
public void putAll(Map<String, String> map) {
ArgumentChecker.notNull(map, "map");
for (Entry<String, String> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
//-------------------------------------------------------------------------
/**
* Adds a resolved property to the map replacing any existing value.
* <p>
* Use of this method will propagate hiding of sensitive data.
*
* @param resolved the resolved property, not null
*/
public void add(ConfigProperty resolved) {
ArgumentChecker.notNull(resolved, "resolved");
_properties.put(resolved.getKey(), resolved);
}
/**
* Adds a resolved property to the map if not currently present.
* <p>
* Use of this method will propagate hiding of sensitive data.
*
* @param resolved the resolved property, not null
*/
public void addIfAbsent(ConfigProperty resolved) {
ArgumentChecker.notNull(resolved, "resolved");
if (_properties.containsKey(resolved.getKey()) == false) {
add(resolved);
}
}
/**
* Resolves any ${property} references in the value.
* <p>
* This returns a property object that encapsulates the key, value and whether
* the property contains sensitive information.
*
* @param key the key, not null
* @param value the value to resolve, not null
* @param lineNum the line number, for error messages
* @return the resolved property, null if an optional property was undefined
* @throws ComponentConfigException if a variable expansion is not found
*/
public ConfigProperty resolveProperty(String key, String value, int lineNum) {
// hide certain properties from peeking
boolean hidden = (key.contains("password") || key.startsWith("shiro."));
// find ${foo} reference
String variable = findVariable(value);
while (variable != null) {
// need actual start/end for correct interpolation
int start = value.lastIndexOf("${");
int end = value.indexOf("}", start) + 1;
// optional properties do not cause an error if property is missing
boolean isOptional = false;
String variableName = variable;
if (variable.endsWith("?")) {
isOptional = true;
variableName = variable.substring(0, variable.length() - 1);
}
// lookup and interpolate property
ConfigProperty variableProperty = _properties.get(variableName);
if (variableProperty != null) {
value = value.substring(0, start) + variableProperty.getValue() + value.substring(end);
hidden = hidden || variableProperty.isHidden();
} else if (isOptional) {
value = value.substring(0, start) + value.substring(end);
if (value.isEmpty()) {
return null;
}
} else {
throw new ComponentConfigException("Variable expansion not found: ${" + variableName + "}, line " + lineNum);
}
// find next ${foo} reference
variable = findVariable(value);
}
return ConfigProperty.of(key, value, hidden);
}
/**
* Finds a variable to replace.
*
* @param value the value to search, not null
* @return the variable, null if not found
*/
private String findVariable(String value) {
int start = value.lastIndexOf("${");
if (start >= 0) {
start += 2;
int end = value.indexOf("}", start);
if (end >= 0) {
return value.substring(start, end);
}
}
return null;
}
//-------------------------------------------------------------------------
/**
* Converts these properties to a loggable map.
* <p>
* Sensitive data will be hidden.
*
* @return the loggable map, not null
*/
public Map<String, String> loggableMap() {
TreeMap<String, String> map = new TreeMap<String, String>();
for (ConfigProperty cp : _properties.values()) {
map.put(cp.getKey(), cp.loggableValue());
}
return map;
}
/**
* Gets the loggable value for the specified key.
*
* @param key the key to find, not null
* @return the loggable value, null if not found
*/
public String loggableValue(String key) {
ArgumentChecker.notNull(key, "key");
ConfigProperty cp = _properties.get(key);
return (cp != null ? cp.loggableValue() : null);
}
//-------------------------------------------------------------------------
@Override
public String toString() {
return loggableMap().toString();
}
}