/*
* 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.Customizable;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.site.Environment;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
/**
* This class may be used as a utility to hold configuration data of the
* following kind:
*
* <pre>
* <options>
* <option>
* <name>name</name>
* <value>value</value>
* </option>
* </options>
* </pre>
*/
public final class OptionsHelper implements Customizable {
/** The current environment */
protected Environment environment = Environment.Any;
/** Options */
private Map<String, Map<Environment, List<String>>> options = new HashMap<String, Map<Environment, List<String>>>();
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.Customizable#setOption(java.lang.String,
* java.lang.String)
*/
public void setOption(String name, String value) {
setOption(name, value, Environment.Any);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.Customizable#setOption(java.lang.String,
* java.lang.String, ch.entwine.weblounge.common.site.Environment)
*/
public void setOption(String name, String value, Environment environment) {
if (name == null)
throw new IllegalArgumentException("Option name must not be null");
if (value == null)
removeOption(name);
// Does this key exist?
Map<Environment, List<String>> environments = options.get(name);
if (environments == null) {
environments = new HashMap<Environment, List<String>>();
options.put(name, environments);
}
// Is there a list of values for the current environment
List<String> values = environments.get(environment);
if (values == null) {
values = new ArrayList<String>();
environments.put(environment, values);
}
// Add the value
if (!values.contains(value)) {
values.add(value);
Collections.sort(values);
}
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.Customizable#removeOption(java.lang.String)
*/
public void removeOption(String name) {
if (name == null)
throw new IllegalArgumentException("Option name must not be null");
options.remove(name);
}
/**
* Returns <code>true</code> if the the option with name <code>name</code> has
* been configured.
*
* @param name
* the option name
* @return <code>true</code> if an option with that name exists
* @see #getOptions()
* @see #getOptionValue(java.lang.String)
* @see #getOptionValue(java.lang.String, java.lang.String)
*/
public boolean hasOption(String name) {
Map<Environment, List<String>> values = options.get(name);
if (values == null)
return false;
Collection<Environment> environments = values.keySet();
return environments.contains(environment) || environments.contains(Environment.Any);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.Customizable#getOptionNames()
*/
public String[] getOptionNames() {
List<String> keys = new ArrayList<String>();
for (Map.Entry<String, Map<Environment, List<String>>> entry : options.entrySet()) {
String key = entry.getKey();
Map<Environment, List<String>> environments = entry.getValue();
if (environments.get(environment) != null) {
keys.add(key);
} else if (environments.keySet().size() == 1 && environments.containsKey(Environment.Any)) {
keys.add(key);
}
}
return keys.toArray(new String[keys.size()]);
}
/**
* Returns the option value for option <code>name</code> if it has been
* configured, <code>null</code> otherwise.
* <p>
* If the option is a multi value option (that is, if the option has been
* configured multiple times), this method returns the first value only. Use
* {@link #getOptionValues(java.lang.String)} to get all option values.
*
* @param name
* the option name
* @return the option value
* @see #getOptions()
* @see #hasOption(java.lang.String)
* @see #getOptionValue(java.lang.String, java.lang.String)
*/
public String getOptionValue(String name) {
// Does this key exist?
Map<Environment, List<String>> environments = options.get(name);
if (environments == null) {
environments = new HashMap<Environment, List<String>>();
options.put(name, environments);
}
// Is there a value for the current environment?
List<String> optionList = environments.get(environment);
if (optionList != null && optionList.size() > 0) {
return optionList.get(0);
}
// Is there a default value?
optionList = environments.get(Environment.Any);
if (optionList != null && optionList.size() > 0) {
return optionList.get(0);
}
return null;
}
/**
* Returns the option value for option <code>name</code> if it has been
* configured, <code>defaultValue</code> otherwise.
*
* @param name
* the option name
* @param defaultValue
* the default value
* @return the option value
* @see #getOptions()
* @see #hasOption(java.lang.String)
* @see #getOptionValue(java.lang.String)
*/
public String getOptionValue(String name, String defaultValue) {
String value = getOptionValue(name);
return (value != null) ? value : defaultValue;
}
/**
* Returns the option values for option <code>name</code> if it has been
* configured, or an empty array otherwise.
*
* @param name
* the option name
* @return the option values
* @see #getOptions()
* @see #hasOption(java.lang.String)
* @see #getOptionValue(java.lang.String)
*/
public String[] getOptionValues(String name) {
// Does this key exist?
Map<Environment, List<String>> environments = options.get(name);
if (environments == null) {
environments = new HashMap<Environment, List<String>>();
options.put(name, environments);
}
// Is there a value for the current environment?
List<String> optionList = environments.get(environment);
if (optionList != null && optionList.size() > 0) {
return optionList.toArray(new String[optionList.size()]);
}
// Is there a default value?
optionList = environments.get(Environment.Any);
if (optionList != null && optionList.size() > 0) {
return optionList.toArray(new String[optionList.size()]);
}
return new String[] {};
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.Customizable#getOptions()
*/
public Map<String, Map<Environment, List<String>>> getOptions() {
return options;
}
/**
* Initializes the options from an XML node that was generated using
* {@link #toXml()}.
* <p>
* To speed things up, you might consider using the second signature that uses
* an existing <code>XPath</code> instance instead of creating a new one.
*
* @param config
* the options node
* @throws IllegalStateException
* if the options cannot be parsed
* @see #fromXml(Node, XPath)
* @see #toXml()
*/
public static OptionsHelper fromXml(Node config) throws IllegalStateException {
XPath xpath = XPathFactory.newInstance().newXPath();
return fromXml(config, xpath);
}
/**
* Switches the current set of options to the given environment.
*
* @param environment
* the environment
*/
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* Initializes the options from an XML node that was generated using
* {@link #toXml()}. This method expects a <code><properties></code>
* node as the input to <code>config</code>.
*
* @param config
* the options node
* @param xpathProcessor
* xpath processor to use
* @throws IllegalStateException
* if the options cannot be parsed
* @see #toXml()
*/
public static OptionsHelper fromXml(Node config, XPath xpathProcessor)
throws IllegalStateException {
OptionsHelper options = new OptionsHelper();
fromXml(config, options, xpathProcessor);
return options;
}
/**
* Initializes the options from an XML node that was generated using
* {@link #toXml()}. This method expects a <code><properties></code>
* node as the input to <code>config</code>.
*
* @param config
* the options node
* @param customizable
* the customizable object
* @param xpathProcessor
* xpath processor to use
* @throws IllegalStateException
* if the options cannot be parsed
* @see #toXml()
*/
public static void fromXml(Node config, Customizable customizable,
XPath xpathProcessor) throws IllegalStateException {
if (config == null)
return;
// Read the options
NodeList nodes = XPathHelper.selectList(config, "ns:option", xpathProcessor);
for (int i = 0; i < nodes.getLength(); i++) {
Node option = nodes.item(i);
String name = XPathHelper.valueOf(option, "ns:name", xpathProcessor);
NodeList valueNodes = XPathHelper.selectList(option, "ns:value", xpathProcessor);
for (int j = 0; j < valueNodes.getLength(); j++) {
String env = XPathHelper.valueOf(valueNodes.item(j), "@environment");
Environment environment = Environment.Any;
if (StringUtils.isNotBlank(env)) {
try {
environment = Environment.valueOf(StringUtils.capitalize(env));
} catch (Throwable t) {
throw new IllegalStateException("Environment '" + env + "' is unknown");
}
}
String value = valueNodes.item(j).getFirstChild().getNodeValue();
customizable.setOption(name, value, environment);
}
}
}
/**
* Returns an <code>XML</code> representation of the options, that will look
* similar to the following example:
*
* <pre>
* <options>
* <option>
* <name>key</name>
* <value>value</value>
* </option>
* <option>
* <name>multikey</name>
* <value>value</value>
* <value>othervalue</value>
* </option>
* </options>
* </pre>
*
* Use {@link #fromXml(Node))} or {@link #fromXml(Node, XPath)} to create a
* <code>ActionConfiguration</code> from the serialized output of this method.
*
* @return the <code>XML</code> representation of the options
* @see #fromXml(Node)
* @see #fromXml(Node, XPath)
*/
public String toXml() {
StringBuffer b = new StringBuffer();
if (options.size() == 0)
return b.toString();
b.append("<options>");
List<String> keys = new ArrayList<String>();
keys.addAll(options.keySet());
Collections.sort(keys);
for (String key : keys) {
b.append("<option>");
b.append("<name>").append(key).append("</name>");
Map<Environment, List<String>> valuesByEnvironment = options.get(key);
List<Environment> environments = new ArrayList<Environment>();
environments.addAll(valuesByEnvironment.keySet());
Collections.sort(environments, new Comparator<Environment>() {
public int compare(Environment envA, Environment envB) {
return envA.toString().compareTo(envB.toString());
}
});
for (Environment environment : environments) {
List<String> values = valuesByEnvironment.get(environment);
Collections.sort(values);
for (String value : values) {
b.append("<value");
if (!environment.equals(Environment.Any)) {
b.append(" environment=\"").append(environment.toString().toLowerCase()).append("\"");
}
b.append(">");
b.append("<![CDATA[").append(value).append("]]></value>");
}
}
b.append("</option>");
}
b.append("</options>");
return b.toString();
}
}