org.opencms.configuration.CmsParameterConfiguration Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software GmbH & Co. KG, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.configuration;
import org.opencms.i18n.CmsEncoder;
import org.opencms.util.CmsStringUtil;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import org.dom4j.Element;
/**
* Provides convenient access to configuration parameters.
*
* Usually the parameters are configured in some sort of String based file,
* either in an XML configuration, or in a .property file.
* This wrapper allows accessing such String values directly
* as int
, boolean
or other data types, without
* worrying about the type conversion.
*
* It can also read a configuration from a special property file format,
* which is explained here:
*
*
* -
* Each parameter in the file has the syntax
key = value
*
* -
* The key may use any character but the equal sign '='.
*
* -
* value may be separated on different lines if a backslash
* is placed at the end of the line that continues below.
*
* -
* If value is a list of strings, each token is separated
* by a comma ','.
*
* -
* Commas in each token are escaped placing a backslash right before
* the comma.
*
* -
* Backslashes are escaped by using two consecutive backslashes i.e. \\.
* Note: Unlike in regular Java properties files, you don't need to escape Backslashes.
*
* -
* If a key is used more than once, the values are appended
* as if they were on the same line separated with commas.
*
* -
* Blank lines and lines starting with character '#' are skipped.
*
*
*
* Here is an example of a valid parameter properties file:
*
*
* # lines starting with # are comments
*
* # This is the simplest property
* key = value
*
* # A long property may be separated on multiple lines
* longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
*
* # This is a property with many tokens
* tokens_on_a_line = first token, second token
*
* # This sequence generates exactly the same result
* tokens_on_multiple_lines = first token
* tokens_on_multiple_lines = second token
*
* # commas may be escaped in tokens
* commas.escaped = Hi\, what'up?
*
*/
public class CmsParameterConfiguration extends AbstractMap implements Serializable {
/**
* Used to read parameter lines from a property file.
*
* The lines do not terminate with new-line chars but rather when there is no
* backslash sign a the end of the line. This is used to
* concatenate multiple lines for readability in the input file.
*/
protected static class ParameterReader extends LineNumberReader {
/**
* Constructor.
*
* @param reader a reader
*/
public ParameterReader(Reader reader) {
super(reader);
}
/**
* Reads a parameter line.
*
* @return the parameter line read
*
* @throws IOException in case of IO errors
*/
public String readParameter() throws IOException {
StringBuffer buffer = new StringBuffer();
String line = readLine();
while (line != null) {
line = line.trim();
if ((line.length() != 0) && (line.charAt(0) != '#')) {
if (endsWithSlash(line)) {
line = line.substring(0, line.length() - 1);
buffer.append(line);
} else {
buffer.append(line);
return buffer.toString(); // normal method end
}
}
line = readLine();
}
return null; // EOF reached
}
}
/**
* This class divides property value into tokens separated by ",".
*
* Commas in the property value that are wanted
* can be escaped using the backslash in front like this "\,".
*/
protected static class ParameterTokenizer extends StringTokenizer {
/** The property delimiter used while parsing (a comma). */
static final String COMMA = ",";
/**
* Constructor.
*
* @param string the String to break into tokens
*/
public ParameterTokenizer(String string) {
super(string, COMMA);
}
/**
* Returns the next token.
*
* @return the next token
*/
@Override
public String nextToken() {
StringBuffer buffer = new StringBuffer();
while (hasMoreTokens()) {
String token = super.nextToken();
if (endsWithSlash(token)) {
buffer.append(token.substring(0, token.length() - 1));
buffer.append(COMMA);
} else {
buffer.append(token);
break;
}
}
return buffer.toString().trim();
}
}
/**
* An empty, immutable parameter configuration.
*/
public static final CmsParameterConfiguration EMPTY_PARAMETERS = new CmsParameterConfiguration(
Collections. emptyMap(),
Collections. emptyMap());
/** The serial version id. */
private static final long serialVersionUID = 294679648036460877L;
/** The parsed map of parameters where the Strings may have become Objects. */
private transient Map m_configurationObjects;
/** The original map of parameters that contains only String values. */
private Map m_configurationStrings;
/**
* Creates an empty parameter configuration.
*/
public CmsParameterConfiguration() {
this(new TreeMap(), new TreeMap());
}
/**
* Creates a parameter configuration from an input stream.
*
* @param in the input stream to create the parameter configuration from
*
* @throws IOException in case of errors loading the parameters from the input stream
*/
public CmsParameterConfiguration(InputStream in)
throws IOException {
this();
load(in);
}
/**
* Creates a parameter configuration from a Map of Strings.
*
* @param configuration the map of Strings to create the parameter configuration from
*/
public CmsParameterConfiguration(Map configuration) {
this();
for (String key : configuration.keySet()) {
String value = configuration.get(key);
add(key, value);
}
}
/**
* Creates a parameter wrapper by loading the parameters from the specified property file.
*
* @param file the path of the file to load
*
* @throws IOException in case of errors loading the parameters from the specified property file
*/
public CmsParameterConfiguration(String file)
throws IOException {
this();
FileInputStream in = null;
try {
in = new FileInputStream(file);
load(in);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
// ignore error on close() only
}
}
}
/**
* Creates a parameter configuration from the given maps.
*
* @param strings the String map
* @param objects the object map
*/
private CmsParameterConfiguration(Map strings, Map objects) {
m_configurationStrings = strings;
m_configurationObjects = objects;
}
/**
* Returns an unmodifiable version of this parameter configuration.
*
* @param original the configuration to make unmodifiable
*
* @return an unmodifiable version of this parameter configuration
*/
public static CmsParameterConfiguration unmodifiableVersion(CmsParameterConfiguration original) {
return new CmsParameterConfiguration(
Collections.unmodifiableMap(original.m_configurationStrings),
original.m_configurationObjects);
}
/**
* Counts the number of successive times 'ch' appears in the
* 'line' before the position indicated by the 'index'.
*
* @param line the line to count
* @param index the index position to start
* @param ch the character to count
*
* @return the number of successive times 'ch' appears in the 'line'
* before the position indicated by the 'index'
*/
protected static int countPreceding(String line, int index, char ch) {
int i;
for (i = index - 1; i >= 0; i--) {
if (line.charAt(i) != ch) {
break;
}
}
return index - 1 - i;
}
/**
* Checks if the line ends with odd number of backslashes.
*
* @param line the line to check
*
* @return true
if the line ends with odd number of backslashes
*/
protected static boolean endsWithSlash(String line) {
if (!line.endsWith("\\")) {
return false;
}
return ((countPreceding(line, line.length() - 1, '\\') % 2) == 0);
}
/**
* Replaces escaped char sequences in the input value.
*
* @param value the value to unescape
*
* @return the unescaped String
*/
protected static String unescape(String value) {
value = CmsStringUtil.substitute(value, "\\,", ",");
value = CmsStringUtil.substitute(value, "\\=", "=");
value = CmsStringUtil.substitute(value, "\\\\", "\\");
return value;
}
/**
* Add a parameter to this configuration.
*
* If the parameter already exists then the value will be added
* to the existing configuration entry and a List will be created for the values.
*
* String values separated by a comma "," will NOT be tokenized when this
* method is used. To create a List of String values for a parameter, call this method
* multiple times with the same parameter name.
*
* @param key the parameter to add
* @param value the value to add
*/
public void add(String key, String value) {
add(key, value, false);
}
/**
* Serializes this parameter configuration for the OpenCms XML configuration.
*
* For each parameter, a XML node like this
*
* <param name="theName">theValue</param>
*
* is generated and appended to the provided parent node.
*
* @param parentNode the parent node where the parameter nodes are appended to
*
* @return the parent node
*/
public Element appendToXml(Element parentNode) {
return appendToXml(parentNode, null);
}
/**
* Serializes this parameter configuration for the OpenCms XML configuration.
*
* For each parameter, a XML node like this
*
* <param name="theName">theValue</param>
*
* is generated and appended to the provided parent node.
*
* @param parentNode the parent node where the parameter nodes are appended to
* @param parametersToIgnore if not null
,
* all parameters in this list are not written to the XML
*
* @return the parent node
*/
public Element appendToXml(Element parentNode, List parametersToIgnore) {
for (Map.Entry entry : m_configurationObjects.entrySet()) {
String name = entry.getKey();
// check if the parameter should be ignored
if ((parametersToIgnore == null) || !parametersToIgnore.contains(name)) {
// now serialize the parameter name and value
Object value = entry.getValue();
if (value instanceof List) {
@SuppressWarnings("unchecked")
List values = (List)value;
for (String strValue : values) {
// use the original String as value
Element paramNode = parentNode.addElement(I_CmsXmlConfiguration.N_PARAM);
// set the name attribute
paramNode.addAttribute(I_CmsXmlConfiguration.A_NAME, name);
// set the text of node
paramNode.addText(strValue);
}
} else {
// use the original String as value
String strValue = get(name);
Element paramNode = parentNode.addElement(I_CmsXmlConfiguration.N_PARAM);
// set the name attribute
paramNode.addAttribute(I_CmsXmlConfiguration.A_NAME, name);
// set the text of node
paramNode.addText(strValue);
}
}
}
return parentNode;
}
/**
* @see java.util.Map#clear()
*/
@Override
public void clear() {
m_configurationStrings.clear();
m_configurationObjects.clear();
}
/**
* @see java.util.Map#containsKey(java.lang.Object)
*/
@Override
public boolean containsKey(Object key) {
return m_configurationStrings.containsKey(key);
}
/**
* @see java.util.Map#containsValue(java.lang.Object)
*/
@Override
public boolean containsValue(Object value) {
return m_configurationStrings.containsValue(value) || m_configurationObjects.containsValue(value);
}
/**
* @see java.util.Map#entrySet()
*/
@Override
public Set> entrySet() {
return m_configurationStrings.entrySet();
}
/**
* Returns the String associated with the given parameter.
*
* @param key the parameter to look up the value for
*
* @return the String associated with the given parameter
*/
@Override
public String get(Object key) {
return m_configurationStrings.get(key);
}
/**
* Returns the boolean associated with the given parameter,
* or the default value in case there is no boolean value for this parameter.
*
* @param key the parameter to look up the value for
* @param defaultValue the default value
*
* @return the boolean associated with the given parameter,
* or the default value in case there is no boolean value for this parameter
*/
public boolean getBoolean(String key, boolean defaultValue) {
Object value = m_configurationObjects.get(key);
if (value instanceof Boolean) {
return ((Boolean)value).booleanValue();
} else if (value instanceof String) {
Boolean b = Boolean.valueOf((String)value);
m_configurationObjects.put(key, b);
return b.booleanValue();
} else {
return defaultValue;
}
}
/**
* Returns the integer associated with the given parameter,
* or the default value in case there is no integer value for this parameter.
*
* @param key the parameter to look up the value for
* @param defaultValue the default value
*
* @return the integer associated with the given parameter,
* or the default value in case there is no integer value for this parameter
*/
public int getInteger(String key, int defaultValue) {
Object value = m_configurationObjects.get(key);
if (value instanceof Integer) {
return ((Integer)value).intValue();
} else if (value instanceof String) {
Integer i = new Integer((String)value);
m_configurationObjects.put(key, i);
return i.intValue();
} else {
return defaultValue;
}
}
/**
* Returns the List of Strings associated with the given parameter,
* or an empty List in case there is no List of Strings for this parameter.
*
* The list returned is a copy of the internal data of this object, and as
* such you may alter it freely.
*
* @param key the parameter to look up the value for
*
* @return the List of Strings associated with the given parameter,
* or an empty List in case there is no List of Strings for this parameter
*/
public List getList(String key) {
return getList(key, null);
}
/**
* Returns the List of Strings associated with the given parameter,
* or the default value in case there is no List of Strings for this parameter.
*
* The list returned is a copy of the internal data of this object, and as
* such you may alter it freely.
*
* @param key the parameter to look up the value for
* @param defaultValue the default value
*
* @return the List of Strings associated with the given parameter,
* or the default value in case there is no List of Strings for this parameter
*/
public List getList(String key, List defaultValue) {
Object value = m_configurationObjects.get(key);
if (value instanceof List) {
@SuppressWarnings("unchecked")
List result = (List)value;
return new ArrayList(result);
} else if (value instanceof String) {
ArrayList values = new ArrayList(1);
values.add((String)value);
m_configurationObjects.put(key, values);
return values;
} else {
if (defaultValue == null) {
return new ArrayList();
} else {
return defaultValue;
}
}
}
/**
* Returns the raw Object associated with the given parameter,
* or null
in case there is no Object for this parameter.
*
* @param key the parameter to look up the value for
*
* @return the raw Object associated with the given parameter,
* or null
in case there is no Object for this parameter.
*/
public Object getObject(String key) {
return m_configurationObjects.get(key);
}
/**
* Creates a new Properties object from the existing configuration
* extracting all key-value pars whose key are prefixed
* with keyPrefix.
*
* For this example config:
*
*
* # lines starting with # are comments
* db.pool.default.jdbcDriver=net.bull.javamelody.JdbcDriver
* db.pool.default.connectionProperties.driver=com.mysql.cj.jdbc.Driver
*
*
* getPrefixedProperties("db.pool.default.connectionProperties")
* will return a Properties object with one single entry:
*
* key:"driver", value:"com.mysql.cj.jdbc.Driver"
*
*
* @param keyPrefix prefix to match. If it isn't already, it will be
* terminated with a dot. If null, it will return
* an empty Properties instance
* @return a new Properties object with all the entries from this
* configuration whose keys math the prefix
*/
public Properties getPrefixedProperties(String keyPrefix) {
Properties props = new Properties();
if (null == keyPrefix) {
return props;
}
String dotTerminatedKeyPrefix = keyPrefix + (keyPrefix.endsWith(".") ? "" : ".");
for (Map.Entry e : entrySet()) {
String key = e.getKey();
if ((null != key) && key.startsWith(dotTerminatedKeyPrefix)) {
String subKey = key.substring(dotTerminatedKeyPrefix.length());
props.put(subKey, e.getValue());
}
}
return props;
}
/**
* Returns the String associated with the given parameter,
* or the given default value in case there is no value for this parameter.
*
* @param key the parameter to look up the value for
* @param defaultValue the default value
*
* @return the String associated with the given parameter,
* or the given default value in case there is no value for this parameter.
*/
public String getString(String key, String defaultValue) {
String result = get(key);
return result == null ? defaultValue : result;
}
/**
* @see java.util.Map#hashCode()
*/
@Override
public int hashCode() {
return m_configurationStrings.hashCode();
}
/**
* @see java.util.Map#keySet()
*/
@Override
public Set keySet() {
return m_configurationStrings.keySet();
}
/**
* Load the parameters from the given input stream, which must be in property file format.
*
* @param input the stream to load the input from
*
* @throws IOException in case of IO errors reading from the stream
*/
public void load(InputStream input) throws IOException {
ParameterReader reader = null;
try {
reader = new ParameterReader(new InputStreamReader(input, CmsEncoder.ENCODING_ISO_8859_1));
} catch (UnsupportedEncodingException ex) {
reader = new ParameterReader(new InputStreamReader(input));
}
while (true) {
String line = reader.readParameter();
if (line == null) {
return; // EOF
}
int equalSign = line.indexOf('=');
if (equalSign > 0) {
String key = line.substring(0, equalSign).trim();
String value = line.substring(equalSign + 1).trim();
if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
continue;
}
add(key, value, true);
}
}
}
/**
* Set a parameter for this configuration.
*
* If the parameter already exists then the existing value will be replaced.
*
* @param key the parameter to set
* @param value the value to set
*
* @return the previous String value from the parameter map
*/
@Override
public String put(String key, String value) {
String result = remove(key);
add(key, value, false);
return result;
}
/**
* Merges this parameter configuration with the provided other parameter configuration.
*
* The difference form a simple Map<String, String>
is that for the parameter
* configuration, the values of the keys in both maps are merged and kept in the Object store
* as a List.
*
* As result, this
configuration will be altered, the other configuration will
* stay unchanged.
*
* @param other the other parameter configuration to merge this configuration with
*/
@Override
public void putAll(Map extends String, ? extends String> other) {
for (String key : other.keySet()) {
boolean tokenize = false;
if (other instanceof CmsParameterConfiguration) {
Object o = ((CmsParameterConfiguration)other).getObject(key);
if (o instanceof List) {
tokenize = true;
}
}
add(key, other.get(key), tokenize);
}
}
/**
* Removes a parameter from this configuration.
*
* @param key the parameter to remove
*/
@Override
public String remove(Object key) {
String result = m_configurationStrings.remove(key);
m_configurationObjects.remove(key);
return result;
}
/**
* @see java.util.Map#toString()
*/
@Override
public String toString() {
return m_configurationStrings.toString();
}
/**
* @see java.util.Map#values()
*/
@Override
public Collection values() {
return m_configurationStrings.values();
}
/**
* Add a parameter to this configuration.
*
* If the parameter already exists then the value will be added
* to the existing configuration entry and a List will be created for the values.
*
* @param key the parameter to add
* @param value the value to add
* @param tokenize decides if a String value should be tokenized or nor
*/
private void add(String key, String value, boolean tokenize) {
if (tokenize && (value.indexOf(ParameterTokenizer.COMMA) > 0)) {
// token contains commas, so must be split apart then added
ParameterTokenizer tokenizer = new ParameterTokenizer(value);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
addInternal(key, unescape(token));
}
} else if (tokenize) {
addInternal(key, unescape(value));
} else {
// token contains no commas, so can be simply added
addInternal(key, value);
}
}
/**
* Adds a parameter, parsing the value if required.
*
* @param key the parameter to add
* @param value the value of the parameter
*/
private void addInternal(String key, String value) {
Object currentObj = m_configurationObjects.get(key);
String currentStr = get(key);
if (currentObj instanceof String) {
// one object already in map - convert it to a list
ArrayList values = new ArrayList(2);
values.add(currentStr);
values.add(value);
m_configurationObjects.put(key, values);
m_configurationStrings.put(key, currentStr + ParameterTokenizer.COMMA + value);
} else if (currentObj instanceof List) {
// already a list - just add the new token
@SuppressWarnings("unchecked")
List list = (List)currentObj;
list.add(value);
m_configurationStrings.put(key, currentStr + ParameterTokenizer.COMMA + value);
} else {
m_configurationObjects.put(key, value);
m_configurationStrings.put(key, value);
}
}
}