Skip to content
Snippets Groups Projects
Commit 118f66e9 authored by felmer's avatar felmer
Browse files

LMS-1484 add property inheritance

SVN: 16114
parent deaaef84
No related branches found
No related tags found
No related merge requests found
...@@ -16,32 +16,53 @@ ...@@ -16,32 +16,53 @@
package ch.systemsx.cisd.common.utilities; package ch.systemsx.cisd.common.utilities;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils; import org.apache.commons.lang.SystemUtils;
/** /**
* Another class of Properties that allows recursive references for property keys and values. For * An extension of {@link Properties}. The extension avoid duplicating properties by reusing. There
* example, * are two ways to reuse properties:
* <ol>
* <li>Use properties in property values. For example,
* *
* <pre> * <pre>
* A=12345678 * A=12345678
* B=${A}90 * B=${A}90
* C=${B} plus more * C=${B} plus more
* </pre>
* *
* </code></pre> * will result in <code>getProperty("C")</code> returning the value "1234567890 plus more". Cyclic
* references are handled by removing the current key before resolving it, i.e. when setting A=${B}
* and B=${A} and then asking for A, you will get ${A}.
* <li>Inherit properties. For example,
* *
* will result in <code>getProperty("C")</code> returning the value "1234567890 plus more". The * <pre>
* keys will be rewritten when queried (dynamically), thus the order of adding properties is * type.code = ALPHA
* unimportant. Cyclic references are handled by removing the current key before resolving it, i.e. * type.label = Alpha
* when setting A=${B} and B=${A} and then asking for A, you will get ${A}. * validator.order = 1
* validator.type. = type.
* my.validator. = validator.
* my.validator.type.label = A L P H A
* </pre>
* will result in <code>getProperty("my.validator.type.code")</code> returning the value "ALPHA".
* All keys ending with a dot '.' should refer to a key prefix. All properties starting with
* this prefix are also properties of the subtree starting with the key with the dot at the end.
* Inherit properties can be overridden.
* </ol>
* *
* @author Christian Ribeaud * @author Christian Ribeaud
* @author Franz-Josef Elmer
*/ */
public final class ExtendedProperties extends Properties public final class ExtendedProperties extends Properties
{ {
private static final String PATH_DELIMITER = ".";
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** Usual (or default) property separator in the super class. */ /** Usual (or default) property separator in the super class. */
...@@ -74,6 +95,7 @@ public final class ExtendedProperties extends Properties ...@@ -74,6 +95,7 @@ public final class ExtendedProperties extends Properties
/** /**
* Returns a subset of given <code>Properties</code> based on given property key prefix. * Returns a subset of given <code>Properties</code> based on given property key prefix.
* The subset contains also inherited properties.
* *
* @param prefix string, each property key should start with. * @param prefix string, each property key should start with.
* @param dropPrefix If <code>true</code> the prefix will be removed from the key. * @param dropPrefix If <code>true</code> the prefix will be removed from the key.
...@@ -88,19 +110,45 @@ public final class ExtendedProperties extends Properties ...@@ -88,19 +110,45 @@ public final class ExtendedProperties extends Properties
{ {
assert prefix != null : "Missing prefix"; assert prefix != null : "Missing prefix";
return gs(prefix, dropPrefix, new HashSet<String>());
}
private ExtendedProperties gs(final String prefix, final boolean dropPrefix, Set<String> keys)
{
final ExtendedProperties result = new ExtendedProperties(); final ExtendedProperties result = new ExtendedProperties();
final int prefixLength = prefix.length(); final int prefixLength = prefix.length();
for (final Enumeration<?> enumeration = propertyNames(); enumeration.hasMoreElements(); ) for (final Enumeration<?> enumeration = propertyNames(); enumeration.hasMoreElements(); )
{ {
final String key = enumeration.nextElement().toString(); final String key = enumeration.nextElement().toString();
if (key.startsWith(prefix)) if (key.startsWith(prefix) && key.endsWith(PATH_DELIMITER))
{
assertNoCyclicDependency(keys, key);
keys.add(key);
String inheritTree = super.getProperty(key);
for (Entry<Object, Object> entry : gs(inheritTree, true, keys).entrySet())
{
String newKey = key.substring(0, key.length()) + entry.getKey();
result.put(createKey(newKey, dropPrefix, prefixLength), entry.getValue());
}
keys.remove(key);
}
}
for (final Enumeration<?> enumeration = propertyNames(); enumeration.hasMoreElements(); )
{
final String key = enumeration.nextElement().toString();
if (key.startsWith(prefix) && key.endsWith(PATH_DELIMITER) == false)
{ {
result.put(dropPrefix ? key.substring(prefixLength) : key, getProperty(key)); result.put(createKey(key, dropPrefix, prefixLength), getProperty(key));
} }
} }
return result; return result;
} }
private String createKey(final String key, final boolean dropPrefix, final int prefixLength)
{
return dropPrefix ? key.substring(prefixLength) : key;
}
/** /**
* Removes all properties with names starting with given prefix * Removes all properties with names starting with given prefix
*/ */
...@@ -116,7 +164,7 @@ public final class ExtendedProperties extends Properties ...@@ -116,7 +164,7 @@ public final class ExtendedProperties extends Properties
} }
} }
private final String expandValue(final String key, final String value) private final String expandValue(final String key, final String value, Set<String> keys)
{ {
if (value == null || value.length() < MIN_LENGTH) if (value == null || value.length() < MIN_LENGTH)
{ {
...@@ -131,11 +179,13 @@ public final class ExtendedProperties extends Properties ...@@ -131,11 +179,13 @@ public final class ExtendedProperties extends Properties
while (startName >= 0 && endName > startName) while (startName >= 0 && endName > startName)
{ {
final String paramName = result.substring(startName + prefixLen, endName); final String paramName = result.substring(startName + prefixLen, endName);
// recurse into this variable, prevent cyclic references by removing the current key String paramValue = null;
// before asking for the property and the setting it again afterwards. if (keys.contains(paramName) == false)
remove(key); {
final String paramValue = getProperty(paramName); keys.add(key);
super.setProperty(key, value); paramValue = getProperty(paramName, keys);
keys.remove(key);
}
if (paramValue != null) if (paramValue != null)
{ {
result.replace(startName, endName + suffixLen, paramValue); result.replace(startName, endName + suffixLen, paramValue);
...@@ -167,6 +217,35 @@ public final class ExtendedProperties extends Properties ...@@ -167,6 +217,35 @@ public final class ExtendedProperties extends Properties
// //
/** /**
* Returns the value of specified property or <code>null</code> if undefined. This method
* behaves differently then the same method of the superclass:
* <ul>
* <li>
* If nothing found for the specified key other keys are tried as follows:
* For all dot characters '.' in the key (starting from the right most) it looks recursively for
* a replacement of the left part of the key (including the dot) among all properties.
* <p>
* Example:
* <pre>
* type.code = ALPHA
* type.label = Alpha
* validator.order = 1
* validator.type. = type.
* my.validator. = validator.
* my.validator.type.label = A L P H A
* </pre>
* The following table shows the returned value of <code>getProperty()</code> for various keys:
* <table border=1 cellspacing=1 cellpadding=5>
* <tr><th>Key</th><th>Value</th></tr>
* <tr><td>validator.order</td><td>1</td></tr>
* <tr><td>validator.type.code</td><td>ALPHA</td></tr>
* <tr><td>validator.type.label</td><td>Alpha</td></tr>
* <tr><td>my.validator.order</td><td>1</td></tr>
* <tr><td>my.validator.type.code</td><td>ALPHA</td></tr>
* <tr><td>my.validator.type.label</td><td>A L P H A</td></tr>
* </table>
* This mechanism allows to inherit property values from complete subtrees of properties.
* <li>
* Any parameter like <code>${propertyName}</code> in property value will be replaced with the * Any parameter like <code>${propertyName}</code> in property value will be replaced with the
* value of property with name <code>propertyName</code>. * value of property with name <code>propertyName</code>.
* <p> * <p>
...@@ -183,7 +262,7 @@ public final class ExtendedProperties extends Properties ...@@ -183,7 +262,7 @@ public final class ExtendedProperties extends Properties
* <pre> * <pre>
* Alphabet starts with: abcdefgh * Alphabet starts with: abcdefgh
* </pre> * </pre>
* * </ul>
* </p> * </p>
* *
* @see java.util.Properties#getProperty(java.lang.String) * @see java.util.Properties#getProperty(java.lang.String)
...@@ -191,10 +270,46 @@ public final class ExtendedProperties extends Properties ...@@ -191,10 +270,46 @@ public final class ExtendedProperties extends Properties
@Override @Override
public final String getProperty(final String key) public final String getProperty(final String key)
{ {
final String result = super.getProperty(key); return getProperty(key, new HashSet<String>());
return result == null ? null : expandValue(key, result); }
private String getProperty(final String key, Set<String> keys)
{
String result = super.getProperty(key);
if (result == null)
{
int index = key.length();
while (index > 0)
{
int lastIndexOfPathDelimiter = key.lastIndexOf(PATH_DELIMITER, index);
if (lastIndexOfPathDelimiter >= 0)
{
String newPath =
super.getProperty(key.substring(0, lastIndexOfPathDelimiter + 1));
if (newPath != null)
{
assertNoCyclicDependency(keys, key);
keys.add(key);
String newKey = newPath + key.substring(lastIndexOfPathDelimiter + 1);
result = getProperty(newKey, keys);
keys.remove(key);
break;
}
}
index = lastIndexOfPathDelimiter - 1;
}
}
return result == null ? null : expandValue(key, result, keys);
} }
private void assertNoCyclicDependency(Set<String> keys, final String key)
{
if (keys.contains(key))
{
throw new IllegalArgumentException("Cyclic definition of property '" + key + "'.");
}
}
/** /**
* @see java.util.Properties#getProperty(java.lang.String, java.lang.String) * @see java.util.Properties#getProperty(java.lang.String, java.lang.String)
*/ */
...@@ -202,7 +317,7 @@ public final class ExtendedProperties extends Properties ...@@ -202,7 +317,7 @@ public final class ExtendedProperties extends Properties
public final String getProperty(final String key, final String defaultValue) public final String getProperty(final String key, final String defaultValue)
{ {
final String result = getProperty(key); final String result = getProperty(key);
return result == null ? expandValue(key, defaultValue) : result; return result == null ? defaultValue : result;
} }
@Override @Override
......
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
package ch.systemsx.cisd.common.utilities; package ch.systemsx.cisd.common.utilities;
import static org.testng.AssertJUnit.assertEquals; import java.util.Properties;
import org.testng.AssertJUnit;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
...@@ -26,7 +27,7 @@ import org.testng.annotations.Test; ...@@ -26,7 +27,7 @@ import org.testng.annotations.Test;
* *
* @author Christian Ribeaud * @author Christian Ribeaud
*/ */
public final class ExtendedPropertiesTest public final class ExtendedPropertiesTest extends AssertJUnit
{ {
private ExtendedProperties extendedProperties; private ExtendedProperties extendedProperties;
...@@ -38,7 +39,7 @@ public final class ExtendedPropertiesTest ...@@ -38,7 +39,7 @@ public final class ExtendedPropertiesTest
props.setProperty("un", "${one}"); props.setProperty("un", "${one}");
props.setProperty("two", "zwei"); props.setProperty("two", "zwei");
props.setProperty("three", "drei"); props.setProperty("three", "drei");
props.setProperty("four", "${one}${three}"); props.setProperty("four", "${un}${three}");
extendedProperties = props; extendedProperties = props;
} }
...@@ -59,7 +60,7 @@ public final class ExtendedPropertiesTest ...@@ -59,7 +60,7 @@ public final class ExtendedPropertiesTest
{ {
assertEquals("eins", extendedProperties.getProperty("one")); assertEquals("eins", extendedProperties.getProperty("one"));
assertEquals("eins", extendedProperties.getProperty("un")); assertEquals("eins", extendedProperties.getProperty("un"));
assertEquals("einsdrei", extendedProperties.getProperty("four")); assertEquals("einsdrei", extendedProperties.getProperty("four", "abc"));
} }
@Test @Test
...@@ -116,7 +117,108 @@ public final class ExtendedPropertiesTest ...@@ -116,7 +117,108 @@ public final class ExtendedPropertiesTest
public final void testGetUnalteredProperty() public final void testGetUnalteredProperty()
{ {
assertEquals("${one}", extendedProperties.getUnalteredProperty("un")); assertEquals("${one}", extendedProperties.getUnalteredProperty("un"));
System.out.println(extendedProperties); }
@Test
public final void testGetPropertyWithInheritTree()
{
Properties properties = new Properties();
properties.setProperty("default.code", "42");
properties.setProperty("type.code", "ABC-${default.code}");
properties.setProperty("type.label", "abc");
properties.setProperty("validator.order", "1");
properties.setProperty("validator.type.", "type.");
properties.setProperty("my.validator.", "validator.");
properties.setProperty("my.validator.order", "2");
properties.setProperty("my.validator.type.label", "a-b-c");
properties.setProperty("another.validator.", "my.validator.");
properties.setProperty("another.validator.type.code", "alpha-${default.code}");
Properties props = ExtendedProperties.createWith(properties);
assertEquals("2", props.getProperty("my.validator.order"));
assertEquals("ABC-42", props.getProperty("my.validator.type.code"));
assertEquals("a-b-c", props.getProperty("my.validator.type.label"));
assertEquals("2", props.getProperty("another.validator.order"));
assertEquals("alpha-42", props.getProperty("another.validator.type.code"));
assertEquals("a-b-c", props.getProperty("another.validator.type.label"));
}
@Test
public final void testGetPropertyWithCyclicInheritance()
{
Properties properties = new Properties();
properties.setProperty("a.", "b.");
properties.setProperty("b.", "a.");
properties.setProperty("my.", "b.");
Properties props = ExtendedProperties.createWith(properties);
try
{
props.getProperty("my.code");
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ex)
{
assertEquals("Cyclic definition of property 'b.code'.", ex.getMessage());
}
}
@Test
public final void testGetSubsetDroppingPrefixWithInheritTree()
{
ExtendedProperties properties = new ExtendedProperties();
properties.setProperty("default.code", "42");
properties.setProperty("type.code", "ABC-${default.code}");
properties.setProperty("type.label", "abc");
properties.setProperty("validator.order", "1");
properties.setProperty("validator.type.", "type.");
properties.setProperty("my.validator.", "validator.");
properties.setProperty("my.validator.order", "2");
properties.setProperty("my.validator.type.label", "a-b-c");
Properties subset = properties.getSubset("my.validator.", true);
assertEquals("2", subset.getProperty("order"));
assertEquals("ABC-42", subset.getProperty("type.code"));
assertEquals("a-b-c", subset.getProperty("type.label"));
assertEquals(3, subset.size());
}
@Test
public final void testGetSubsetNotDroppingPrefixWithInheritTree()
{
ExtendedProperties properties = new ExtendedProperties();
properties.setProperty("default.code", "42");
properties.setProperty("type.code", "ABC-${default.code}");
properties.setProperty("type.label", "abc");
properties.setProperty("validator.order", "1");
properties.setProperty("validator.type.", "type.");
properties.setProperty("my.validator.", "validator.");
properties.setProperty("my.validator.order", "2");
properties.setProperty("my.validator.type.label", "a-b-c");
Properties subset = properties.getSubset("my.", false);
assertEquals("2", subset.getProperty("my.validator.order"));
assertEquals("ABC-42", subset.getProperty("my.validator.type.code"));
assertEquals("a-b-c", subset.getProperty("my.validator.type.label"));
assertEquals(3, subset.size());
} }
@Test
public final void testGetSubsetWithCyclicInheritance()
{
Properties properties = new Properties();
properties.setProperty("a.", "b.");
properties.setProperty("b.", "a.");
properties.setProperty("my.", "b.");
ExtendedProperties props = ExtendedProperties.createWith(properties);
try
{
props.getSubset("my.", true);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException ex) {
assertEquals("Cyclic definition of property 'b.'.", ex.getMessage());
}
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment