blob: c2bd780b1e46b94ff733eccada726263ad3a6bef [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The ConfigMap class is a helper implementation of Map that makes it easier
* to handle properties that can appear one or more times. If a property is set
* more than once, it is converted to a List and added as another property
* rather than replacing the existing property. It also provides utility APIs
* for getting properties from the Map, cast to a certain type and allows a
* default to be specified in the event that the property is missing.
*/
public class ConfigMap extends LinkedHashMap
{
/**
* This number was generated using the 'serialver' command line tool.
* This number should remain consistent with the version used by
* ColdFusion to communicate with the message broker over RMI.
*/
private static final long serialVersionUID = 8913604659150919550L;
/**
* This error is thrown when a property unexpectedly contains multiple values.
*/
private static final int UNEXPECTED_MULTIPLE_VALUES = 10169;
/**
* An *undocumented* system property can be used to revert to legacy config property handling:
* Legacy behavior - config property values were not trimmed, retaining leading/trailing whitespace which
* proved problematic for customers.
* New default behavior - config property values are trimmed.
*/
private static final String SYSPROPNAME_TRIM_CONFIG_PROPERTY_VALUES = "flex.trim-config-property-values";
private static final boolean TRIM_CONFIG_PROPERTY_VALUES = Boolean.valueOf(System.getProperty(SYSPROPNAME_TRIM_CONFIG_PROPERTY_VALUES, "true")).booleanValue();
/**
* Map to keep track of accessed properties.
*/
private HashSet accessedKeys = new HashSet();
/**
* Constructs an empty <code>ConfigMap</code> with the default initial
* capacity of 10.
*/
public ConfigMap()
{
super();
}
/**
* Constructs a new <code>ConfigMap</code> with the initial
* capacity specified.
*
* @param initialCapacity the initial capacity.
*/
public ConfigMap(int initialCapacity)
{
super(initialCapacity);
}
/**
* Constructs a new <code>ConfigMap</code> and copies the values
* from the supplied map to this map.
*
* @param m a <code>ConfigMap</code> whose properties are to be added to
* this <code>ConfigMap</code>.
*/
public ConfigMap(ConfigMap m)
{
this();
addProperties(m);
}
/**
* Adds all properties from a map to this map.
*
* @param p a <code>ConfigMap</code> whose properties are to be added to
* this <code>ConfigMap</code>.
*/
public void addProperties(ConfigMap p)
{
Iterator it = p.entrySet().iterator();
while (it.hasNext())
{
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
Object value = entry.getValue();
if (value instanceof ValueList)
{
addProperties(key, (ValueList) value);
}
else
{
addPropertyLogic(key, value);
}
}
}
/**
* Helper method to add a list of values under a key.
*
* @param key The key to add the values under.
* @param values The list of values to add.
*/
private void addProperties(Object key, ValueList values)
{
ValueList list = getValueList(key);
if (list == null)
{
put(key, values.clone());
}
else
{
list.addAll(values);
}
}
/**
* Helper method to add a value under a key.
*
* @param key The key to add the value under.
* @param value The value to add.
*/
private void addPropertyLogic(Object key, Object value)
{
ValueList list = getValueList(key);
if (list == null)
{
put(key, value);
}
else
{
list.add(value);
}
}
private static class ValueList extends ArrayList
{
/**
* Serial version id.
*/
static final long serialVersionUID = -5637755312744414675L;
}
/**
* Given a key, returns a list of values associated with that key.
*
* @param key The key.
* @return A list of values associated with the key.
*/
private ValueList getValueList(Object key)
{
ValueList list;
Object old = super.get(key);
if (old instanceof ValueList)
{
list = (ValueList) old;
}
else if (old != null)
{
list = new ValueList();
list.add(old);
put(key, list);
}
else
{
list = null;
}
return list;
}
/**
* Adds a <code>String</code> value to this map for the given property
* name.
*
* @param name the property name
* @param value the property value
*/
public void addProperty(String name, String value)
{
addPropertyLogic(name, TRIM_CONFIG_PROPERTY_VALUES && value != null ? value.trim() : value);
}
/**
* Adds a <code>ConfigMap</code> value to this map for the given property
* name.
*
* @param name the property name
* @param value the property value
*/
public void addProperty(String name, ConfigMap value)
{
addPropertyLogic(name, value);
}
/**
* Gets the set of property names contained in this map.
*
* @return a <code>Set</code> of property name <code>String</code>s.
*/
public Set propertyNames()
{
return keySet();
}
/**
* Sets a property name as allowed without needing to access the property
* value. This marks a property as allowed for validation purposes.
*
* @param name the property name to allow
*/
public void allowProperty(String name)
{
accessedKeys.add(name);
}
/**
* Gets the value for the given property name. Also records that this
* property was accessed.
*
* @param name the property name
* @return the value for the property, or null if property does not exist
* in this map.
*/
public Object get(Object name)
{
accessedKeys.add(name);
return super.get(name);
}
/**
* Helper method to get the property with the specified name as a string if possible.
*
* @param name The property name.
* @return The property object.
*/
private Object getSinglePropertyOrFail(Object name)
{
Object result = get(name);
if (result instanceof ValueList)
{
ConfigurationException exception = new ConfigurationException();
exception.setMessage
(UNEXPECTED_MULTIPLE_VALUES, new Object[] {name});
throw exception;
}
return result;
}
/**
* Gets the property with the specified name as a string if possible.
*
* @param name The property name.
* @return The property name.
*/
public String getProperty(String name)
{
return getPropertyAsString(name, null);
}
/**
* Gets the property with the specified name as a ConfigMap if possible,
* or returns the default value if the property is undefined.
*
* @param name the property name
* @param defaultValue the default value
* @return ConfigMap the ConfigMap object of the property
*/
public ConfigMap getPropertyAsMap(String name, ConfigMap defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof ConfigMap)
{
return (ConfigMap)prop;
}
return defaultValue;
}
/**
* Gets the property with the specified name as a String if possible,
* or returns the default value if the property is undefined.
*
* @param name the property name
* @param defaultValue the default value
* @return String the String value of the property
*/
public String getPropertyAsString(String name, String defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
return (String)prop;
}
return defaultValue;
}
/**
* Gets a property (or set of properties) as a List. If only one
* property exists it is added as the only entry to a new List.
*
* @param name the property name
* @param defaultValue the value to return if the property is not found
* @return the value for the property as a List if it exists in this map,
* otherwise the defaultValue is returned.
*/
public List getPropertyAsList(String name, List defaultValue)
{
Object prop = get(name);
if (prop != null)
{
if (prop instanceof List)
{
return (List) prop;
}
else
{
List list = new ArrayList();
list.add(prop);
return list;
}
}
return defaultValue;
}
/**
* Gets the property with the specified name as a boolean if possible,
* or returns the default value if the property is undefined.
*
* @param name the property name
* @param defaultValue the default value
* @return boolean the boolean value of the property
*/
public boolean getPropertyAsBoolean(String name, boolean defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
return Boolean.valueOf((String)prop).booleanValue();
}
return defaultValue;
}
/**
* Gets the property with the specified name as an int if possible,
* or returns the default value if the property is undefined.
*
* @param name the property name
* @param defaultValue the default value
* @return int the int value of the property
*/
public int getPropertyAsInt(String name, int defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
try
{
return Integer.parseInt((String)prop);
}
catch (NumberFormatException ex)
{
}
}
return defaultValue;
}
/**
* Gets the property with the specified name as a long if possible,
* or returns the default value if the property is undefined.
*
* @param name the property name
* @param defaultValue the default value
* @return long the long value of the property
*/
public long getPropertyAsLong(String name, long defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
try
{
return Long.parseLong((String)prop);
}
catch (NumberFormatException ex)
{
}
}
return defaultValue;
}
/**
* Returns a list of qualified property names that have not been accessed
* by one of the get*() methods.
*
* @return List a list of unused properties
*/
public List findAllUnusedProperties()
{
List result = new ArrayList();
findUnusedProperties("", true, result);
return result;
}
/**
* Gathers a collection of properties that exist in the map but have not
* been explicitly accessed nor marked as allowed. This list is helpful
* in validating a set of properties as one can detect those that are
* unknown or unexpected.
*
* @param parentPath Used to track the depth of property in a potential
* hierarchy of <code>ConfigMap</code>s.
* @param recurse Whether sub maps should be recursively searched.
* @param result the collection of unused properties in this map.
*/
public void findUnusedProperties
(String parentPath, boolean recurse, Collection result)
{
Iterator itr = entrySet().iterator();
while (itr.hasNext())
{
Map.Entry entry = (Map.Entry) itr.next();
Object key = entry.getKey();
String currentPath = parentPath + '/' + String.valueOf(key);
if (!accessedKeys.contains(key))
{
result.add(currentPath);
}
else if (recurse)
{
Object value = entry.getValue();
List values = value instanceof List ?
(List) value : Collections.singletonList(value);
for (int i = 0; i < values.size(); i++)
{
Object child = values.get(i);
if (child instanceof ConfigMap)
{
((ConfigMap) child).findUnusedProperties
(currentPath, recurse, result);
}
}
}
}
}
}