/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program 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
* of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.common.impl.util.config;
import ch.entwine.weblounge.common.Times;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Module;
import ch.entwine.weblounge.common.site.Site;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class used to handle parameters from configuration files.
*/
public final class ConfigurationUtils {
/**
* This class is not intended to be instantiated.
*/
private ConfigurationUtils() {
// Nothing to be done here
}
/**
* Returns <code>object</code> if it is not <code>null</code>,
* <code>defaultObject</code> otherwise.
* <p>
* Note that this method will try to process templates that are contained in
* either one of <code>object</code> or <code>defaultObject</code>.
*
* @param object
* the object to use if not <code>null</code>
* @param defaultObject
* the default object to use if <code>object</code> is
* <code>null</code>
* @return <code>object</code> if it is not <code>null</code>,
* <code>defaultObject</code> otherwise
*/
public static Object getValue(Object object, Object defaultObject) {
return getValue(object, defaultObject, true);
}
/**
* Returns <code>object</code> if it is not <code>null</code>,
* <code>defaultObject</code> otherwise.
*
* @param object
* the object to use if not <code>null</code>
* @param defaultObject
* the default object to use if <code>object</code> is
* <code>null</code>
* @param processTemplates
* <code>true</code> to process templates
* @return <code>object</code> if it is not <code>null</code>,
* <code>defaultObject</code> otherwise
*/
public static Object getValue(Object object, Object defaultObject,
boolean processTemplates) {
Object o = object != null ? object : defaultObject;
if (processTemplates && o instanceof String)
return processTemplate((String) o);
else
return o;
}
/**
* Returns the <code>int</code> value or <code>defaultValue</code> if
* <code>value</code> is either <code>null</code> or blank.
*
* @param value
* the value as a string
* @param defaultValue
* the default value
* @return the value
* @throws NumberFormatException
* if <code>value</code> can't be parsed into an <code>int</code>
*/
public static int getValue(String value, int defaultValue)
throws NumberFormatException {
if (StringUtils.isBlank(value))
return defaultValue;
return Integer.parseInt(value);
}
/**
* Returns the <code>long</code> value or <code>defaultValue</code> if
* <code>value</code> is either <code>null</code> or blank.
*
* @param value
* the value as a string
* @param defaultValue
* the default value
* @return the value
* @throws NumberFormatException
* if <code>value</code> can't be parsed into a <code>long</code>
*/
public static long getValue(String value, long defaultValue)
throws NumberFormatException {
if (StringUtils.isBlank(value))
return defaultValue;
return Long.parseLong(value);
}
/**
* Returns the single option values as a <code>String[]</code> array. The
* values are expected to be separated by either comma, semicolon or space
* characters.
*
* @param optionValue
* the option value
* @return the values
*/
public static String[] getMultiOptionValues(String optionValue) {
if (optionValue == null) {
return new String[] {};
}
List<String> values = new ArrayList<String>();
StringTokenizer tok = new StringTokenizer(optionValue, " ,;");
while (tok.hasMoreTokens()) {
values.add(tok.nextToken());
}
return values.toArray(new String[values.size()]);
}
/**
* Returns <code>true</code> if the node contains an attribute named
* <tt>default</tt> with a value that corresponds to any of:
* <ul>
* <li>true</li>
* <li>on</li>
* <li>yes</li>
* </ul>
*
* @param value
* the value to test
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isDefault(Node node) {
if (node == null)
return false;
Node defaultAttribute = node.getAttributes().getNamedItem("default");
if (defaultAttribute == null || defaultAttribute.getNodeValue() == null)
return false;
return isTrue(defaultAttribute.getNodeValue());
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null</code> and corresponds to any of:
* <ul>
* <li>true</li>
* <li>on</li>
* <li>yes</li>
* </ul>
*
* @param value
* the value to test
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isTrue(Node node) {
if (node == null || node.getNodeValue() == null)
return false;
return isTrue(node.getNodeValue());
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null</code> and corresponds to any of:
* <ul>
* <li>true</li>
* <li>on</li>
* <li>yes</li>
* </ul>
*
* @param value
* the value to test
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isTrue(String value) {
return isTrue(value, false);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null</code> and corresponds to any of:
* <ul>
* <li>true</li>
* <li>on</li>
* <li>yes</li>
* </ul>
* If <code>value</code> is either <code>null</code> or blank,
* <code>defaultValue</code> is returned.
*
* @param value
* the value to test
* @param defaultValue
* the default value if <code>value</code> is blank
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isTrue(String value, boolean defaultValue) {
if (StringUtils.isBlank(value))
return defaultValue;
value = value.trim().toLowerCase();
return "true".equals(value) || "on".equals(value) || "yes".equals(value);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null<code> and corresponds to any of:
* <ul>
* <li>true</li>
* <li>on</li>
* <li>yes</li>
* </ul>
*
* @param value
* the value to test
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isFalse(String value) {
return isFalse(value, false);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null<code> and corresponds to any of:
* <ul>
* <li>true</li>
* <li>on</li>
* <li>yes</li>
* </ul>
* If <code>value</code> is either <code>null</code> or blank,
* <code>defaultValue</code> is returned.
*
* @param value
* the value to test
* @param defaultValue
* the default value if <code>value</code> is blank
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isFalse(String value, boolean defaultValue) {
if (value == null)
return defaultValue;
value = value.trim().toLowerCase();
return "false".equals(value) || "off".equals(value) || "no".equals(value);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null</code> and corresponds to any of:
* <ul>
* <li>active</li>
* <li>enabled</li>
* <li>on</li>
* </ul>
*
* @param value
* the value to test
* @return <code>true</code> if the value can be interpreted as
* <code>enabled</code>
*/
public static boolean isEnabled(String value) {
return isEnabled(value, false);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null</code> and corresponds to any of:
* <ul>
* <li>active</li>
* <li>enabled</li>
* <li>on</li>
* </ul>
* If <code>value</code> is either <code>null</code> or blank,
* <code>defaultValue</code> is returned.
*
* @param value
* the value to test
* @param defaultValue
* the default value if <code>value</code> is blank
* @return <code>true</code> if the value can be interpreted as
* <code>enabled</code>
*/
public static boolean isEnabled(String value, boolean defaultValue) {
if (value == null)
return defaultValue;
value = value.trim().toLowerCase();
return "active".equals(value) || "enabled".equals(value) || "on".equals(value);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null<code> and corresponds to any of:
* <ul>
* <li>inactive</li>
* <li>disabled</li>
* <li>off</li>
* </ul>
*
* @param value
* the value to test
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isDisabled(String value) {
return isDisabled(value, false);
}
/**
* Returns <code>true</code> if the lowercase and trimmed value is not
* <code>null<code> and corresponds to any of:
* <ul>
* <li>inactive</li>
* <li>disabled</li>
* <li>off</li>
* </ul>
* If <code>value</code> is either <code>null</code> or blank,
* <code>defaultValue</code> is returned.
*
* @param value
* the value to test
* @param defaultValue
* the default value if <code>value</code> is blank
* @return <code>true</code> if the value can be interpreted as
* <code>true</code>
*/
public static boolean isDisabled(String value, boolean defaultValue) {
if (value == null)
return defaultValue;
value = value.trim().toLowerCase();
return "inactive".equals(value) || "disabled".equals(value) || "off".equals(value);
}
/**
* Returns the string representation of the given duration in milliseconds.
* The string follows the pattern <code>ymwdHMS</code>, with the following
* meanings:
* <ul>
* <li><b>y</b> - years</li>
* <li><b>m</b> - months</li>
* <li><b>w</b> - weeks</li>
* <li><b>d</b> - days</li>
* <li><b>H</b> - hours</li>
* <li><b>M</b> - minutes</li>
* <li><b>S</b> - seconds</li>
* </ul>
* Therefore, an example representing 1 week, 3 days and 25 minutes would
* result in <code>1w3d25M</code>.
*
* @param millis
* the duration in milliseconds
* @return the duration as a human readable string
*/
public static String toHumanReadableDuration(long millis) {
return toDuration(millis, true);
}
/**
* Returns the string representation of the given duration in milliseconds.
* The string follows the pattern <code>ymwdHMS</code>, with the following
* meanings:
* <ul>
* <li><b>y</b> - years</li>
* <li><b>m</b> - months</li>
* <li><b>w</b> - weeks</li>
* <li><b>d</b> - days</li>
* <li><b>H</b> - hours</li>
* <li><b>M</b> - minutes</li>
* <li><b>S</b> - seconds</li>
* </ul>
* Therefore, an example representing 1 week, 3 days and 25 minutes would
* result in <code>1w3d25M</code>.
*
* @param millis
* the duration in milliseconds
* @return the duration as a human readable string
*/
public static String toDuration(long millis) {
return toDuration(millis, false);
}
/**
* Returns the string representation of the given duration in milliseconds.
* The string follows the pattern <code>ymwdHMS</code>, with the following
* meanings:
* <ul>
* <li><b>y</b> - years</li>
* <li><b>m</b> - months</li>
* <li><b>w</b> - weeks</li>
* <li><b>d</b> - days</li>
* <li><b>H</b> - hours</li>
* <li><b>M</b> - minutes</li>
* <li><b>S</b> - seconds</li>
* </ul>
* Therefore, an example representing 1 week, 3 days and 25 minutes would
* result in <code>1w3d25M</code>.
*
* @param millis
* the duration in milliseconds
* @param humanReadable
* <code>true</code> to generate human readable output
* @return the duration as a human readable string
*/
private static String toDuration(long millis, boolean humanReadable) {
StringBuffer result = new StringBuffer();
long v = 0;
// Years
if (millis >= Times.MS_PER_YEAR) {
v = millis / Times.MS_PER_YEAR;
millis -= v * Times.MS_PER_YEAR;
result.append(v).append(humanReadable ? " years " : "y");
}
// Months
if (millis >= Times.MS_PER_MONTH) {
v = millis / Times.MS_PER_MONTH;
millis -= v * Times.MS_PER_MONTH;
result.append(v).append(humanReadable ? " months " : "m");
}
// Weeks
if (millis >= Times.MS_PER_WEEK) {
v = millis / Times.MS_PER_WEEK;
millis -= v * Times.MS_PER_WEEK;
result.append(v).append(humanReadable ? " weeks " : "w");
}
// Days
if (millis >= Times.MS_PER_DAY) {
v = millis / Times.MS_PER_DAY;
millis -= v * Times.MS_PER_DAY;
result.append(v).append(humanReadable ? " days " : "d");
}
// Hours
if (millis >= Times.MS_PER_HOUR) {
v = millis / Times.MS_PER_HOUR;
millis -= v * Times.MS_PER_HOUR;
result.append(v).append(humanReadable ? " hours " : "H");
}
// Minutes
if (millis >= Times.MS_PER_MIN) {
v = millis / Times.MS_PER_MIN;
millis -= v * Times.MS_PER_MIN;
result.append(v).append(humanReadable ? " minutes " : "M");
}
// Seconds
if (millis >= Times.MS_PER_SECOND) {
v = millis / Times.MS_PER_SECOND;
millis -= v * Times.MS_PER_SECOND;
result.append(v).append(humanReadable ? " seconds " : "S");
}
// Cleanup
if (millis > 0) {
result.append(millis);
result.append(v).append(humanReadable ? " milliseconds" : "");
} else if (result.length() == 0)
result.append("0");
return result.toString().trim();
}
/**
* Parses <code>duration</code> to determine the number of milliseconds that
* it represents. <code>duration</code> may either be a <code>Long</code>
* value or a duration encoded using the following characters:
* <ul>
* <li><b>y</b> - years</li>
* <li><b>m</b> - months</li>
* <li><b>w</b> - weeks</li>
* <li><b>d</b> - days</li>
* <li><b>H</b> - hours</li>
* <li><b>M</b> - minutes</li>
* <li><b>S</b> - seconds</li>
* </ul>
* Therefore, an example representing 1 week, 3 days and 25 minutes would
* result in <code>1w3d25m</code>.
*
* @param duration
* the duration either in milliseconds or encoded
* @return the duration in milliseconds
* @throws IllegalArgumentException
* if the duration cannot be parsed
*/
public static long parseDuration(String duration)
throws IllegalArgumentException {
if (duration == null)
return 0;
long millis = 0;
try {
return Long.parseLong(duration);
} catch (NumberFormatException e) {
Pattern p = Pattern.compile("^([\\d]+y)?([\\d]+m)?([\\d]+w)?([\\d]+d)?([\\d]+H)?([\\d]+M)?([\\d]+S)?$");
Matcher m = p.matcher(duration);
if (m.matches()) {
for (int i = 1; i <= m.groupCount(); i++) {
String match = m.group(i);
if (match == null)
continue;
if (match.endsWith("y"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_YEAR;
if (match.endsWith("m"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_MONTH;
if (match.endsWith("w"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_WEEK;
if (match.endsWith("d"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_DAY;
if (match.endsWith("H"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_HOUR;
if (match.endsWith("M"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_MIN;
if (match.endsWith("S"))
millis += Long.parseLong(match.substring(0, match.length() - 1)) * Times.MS_PER_SECOND;
}
} else {
throw new IllegalArgumentException("Unknown duration format: " + duration);
}
}
return millis;
}
/**
* Processes the given text by replacing placeholders in the form of
* <code>${key}</code> with their actual values as found in the system
* properties, e. g.
* <ul>
* <li><code>${java.io.tmpdir}</code> becomes <code>/tmp</li>
* </ul>
*
* @param text
* the text to process
* @return the processed text
*/
public static String processTemplate(String text) {
if (text.indexOf("${") >= 0 && text.indexOf("}") > 2) {
Map<Object, Object> systemProperties = new HashMap<Object, Object>(System.getProperties());
for (Entry<Object, Object> entry : systemProperties.entrySet()) {
String variable = "\\$\\{" + (String) entry.getKey() + "\\}";
String replacement = (String) entry.getValue();
text = text.replaceAll(variable, replacement);
}
Map<String, String> systemEnv = new HashMap<String, String>(System.getenv());
for (Entry<String, String> entry : systemEnv.entrySet()) {
String variable = "\\$\\{" + entry.getKey() + "\\}";
String replacement = entry.getValue();
text = text.replaceAll(variable, replacement);
}
}
return text;
}
/**
* Processes the given text by replacing these placeholders with their actual
* values:
* <ul>
* <li><code>file://${site.root}</code> with
* <code>http://<servername>/weblounge-sites/<sitegt;</li>
* </ul>
*
* @param text
* the text to process
* @param variables
* keys and values to replace with
* @return the processed text
*/
public static String processTemplate(String text,
Map<String, String> variables) {
text = processTemplate(text);
for (Map.Entry<String, String> entry : variables.entrySet()) {
String find = "\\$\\{" + entry.getKey() + "\\}";
String replace = entry.getValue();
text = text.replaceAll(find, replace);
}
return text;
}
/**
* Processes the given text by replacing these placeholders with their actual
* values:
* <ul>
* <li><code>file://${site.root}</code> with
* <code>http://<servername>/weblounge-sites/<sitegt;</li>
* <li><code>file://${module.root}</code> with
* <code>http://<servername>/weblounge-sites/<sitegt;/modules/<module></code>
* </li>
* </ul>
*
* @param text
* the text to process
* @param module
* the module
* @param environment
* the environment
* @return the processed text
*/
public static String processTemplate(String text, Module module,
Environment environment) {
text = processTemplate(text);
Map<String, String> replacements = new HashMap<String, String>();
Site site = module.getSite();
// ${site.root}
StringBuffer siteRootReplacement = new StringBuffer();
siteRootReplacement.append(site.getHostname(environment).toExternalForm());
siteRootReplacement.append("/weblounge-sites/").append(site.getIdentifier());
replacements.put("file://\\$\\{site.root\\}", siteRootReplacement.toString());
// ${module.root}
StringBuffer moduleRootReplacement = new StringBuffer(siteRootReplacement);
moduleRootReplacement.append("/modules/").append(module.getIdentifier());
replacements.put("file://\\$\\{module.root\\}", moduleRootReplacement.toString());
// Site options
for (String option : site.getOptionNames()) {
String value = site.getOptionValue(option);
replacements.put("\\$\\{" + option + "\\}", value);
}
// Replace with whatever is available
for (Map.Entry<String, String> entry : replacements.entrySet()) {
text = text.replaceAll(entry.getKey(), entry.getValue());
}
return text;
}
/**
* Processes the given text by replacing these placeholders with their actual
* values:
* <ul>
* <li><code>file://${site.root}</code> with
* <code>http://<servername>/weblounge-sites/<sitegt;</li>
* </li>
* </ul>
*
* @param text
* the text to process
* @param site
* the site
* @param environment
* the environment
* @return the processed text
*/
public static String processTemplate(String text, Site site,
Environment environment) {
text = processTemplate(text);
Map<String, String> replacements = new HashMap<String, String>();
// ${site.root}
StringBuffer siteRootReplacement = new StringBuffer();
siteRootReplacement.append(site.getHostname(environment).toExternalForm());
siteRootReplacement.append("/weblounge-sites/").append(site.getIdentifier());
replacements.put("file://\\$\\{site.root\\}", siteRootReplacement.toString());
// Site options
for (String option : site.getOptionNames()) {
String value = site.getOptionValue(option);
replacements.put("\\$\\{" + option + "\\}", value);
}
// Replace with whatever is available
for (Map.Entry<String, String> entry : replacements.entrySet()) {
text = text.replaceAll(entry.getKey(), entry.getValue());
}
return text;
}
}