blob: c0d7b6c47e856b7dfda6930f8a4926f396cad775 [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);
}
}
}
}
}
}