Author: cziegeler
Date: Tue Sep 3 09:12:45 2013
New Revision: 1519608
URL: http://svn.apache.org/r1519608
Log:
SLING-2779 : Support for default properties values of a resource. Apply
modified patch from Gilles Knobloch - I'Ve simplified the values(), keySet(),
entrySet() methods and moved the CompositeValueMap into the wrappers package. I
also dropped the util class completely
Added:
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
(with props)
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
(with props)
Modified:
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/package-info.java
Added:
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java?rev=1519608&view=auto
==============================================================================
---
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
(added)
+++
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
Tue Sep 3 09:12:45 2013
@@ -0,0 +1,243 @@
+/*
+ * 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 org.apache.sling.api.wrappers;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * An implementation of the {@link ValueMap} based on two {@link ValueMap}s:
+ * - One containing the properties
+ * - Another one containing the defaults to use in case the properties map
+ * does not contain the values.
+ * In case you would like to avoid duplicating properties on multiple
resources,
+ * you can use a <code>CompositeValueMap</code> to get a concatenated map of
+ * properties.
+ * @since 2.3
+ */
+public class CompositeValueMap implements ValueMap {
+
+ /**
+ * Current properties
+ */
+ private final ValueMap properties;
+
+ /**
+ * Default properties
+ */
+ private final ValueMap defaults;
+
+ /**
+ * Merge mode
+ */
+ private final boolean merge;
+
+ /**
+ * Constructor
+ * @param properties The {@link ValueMap} to read from
+ * @param defaults The default {@link ValueMap} to use as fallback
+ */
+ public CompositeValueMap(final ValueMap properties, final ValueMap
defaults) {
+ this(properties, defaults, true);
+ }
+
+ /**
+ * Constructor
+ * @param properties The {@link ValueMap} to read from
+ * @param defaults The default {@link ValueMap} to use as fallback
+ * @param merge Merge flag
+ * - If <code>true</code>, getting a key would return the
+ * current property map's value (if available), even if the
+ * corresponding default does not exist.
+ * - If <code>false</code>, getting a key would return
+ * <code>null</code> if the corresponding default does not
+ * exist
+ */
+ public CompositeValueMap(final ValueMap properties, final ValueMap
defaults, boolean merge) {
+ if (properties == null) {
+ throw new IllegalArgumentException("Properties need to be
provided");
+ }
+ this.properties = properties;
+ this.defaults = defaults != null ? defaults : ValueMap.EMPTY;
+ this.merge = merge;
+ }
+
+ // ---- ValueMap
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> T get(final String key, final Class<T> type) {
+ if (merge || defaults.containsKey(key)) {
+ // Check if property has been provided, if not use defaults
+ if (properties.containsKey(key)) {
+ return properties.get(key, type);
+ } else {
+ return defaults.get(key, type);
+ }
+ }
+
+ // Override mode and no default value provided for this key
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(final String key, final T defaultValue) {
+ if (defaultValue == null) {
+ return (T) get(key);
+ }
+
+ T value = get(key, (Class<T>) defaultValue.getClass());
+ if (value != null) {
+ return value;
+ }
+
+ return defaultValue;
+ }
+
+
+ // ---- Map
+
+ /**
+ * {@inheritDoc}
+ */
+ public int size() {
+ return keySet().size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isEmpty() {
+ if ( defaults.size() > 0 || (merge && properties.size() > 0) ) {
+ return false;
+ }
+ return size() == 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsKey(final Object key) {
+ return keySet().contains(key.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsValue(final Object value) {
+ return values().contains(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object get(final Object key) {
+ if (merge || defaults.containsKey(key)) {
+ // Check if property has been provided, if not use defaults
+ if (properties.containsKey(key)) {
+ return properties.get(key);
+ } else {
+ return defaults.get(key);
+ }
+ }
+
+ // Override mode and no default value provided for this key
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object put(final String aKey, final Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object remove(final Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void putAll(final Map<? extends String, ?> properties) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<String> keySet() {
+ return buildAggregatedMap().keySet();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Collection<Object> values() {
+ return buildAggregatedMap().values();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<Entry<String, Object>> entrySet() {
+ return buildAggregatedMap().entrySet();
+ }
+
+ /**
+ * Build the aggregated map containing all values.
+ */
+ private Map<String, Object> buildAggregatedMap() {
+ final Map<String, Object> entries = new HashMap<String, Object>();
+
+ // Add properties in merge mode or if defaults exists
+ for (final Entry<String, Object> entry : properties.entrySet()) {
+ if (merge || defaults.containsKey(entry.getKey())) {
+ entries.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ // Add missing defaults
+ for (final Entry<String, Object> entry : defaults.entrySet()) {
+ if ( ! entries.containsKey(entry.getKey()) ) {
+ entries.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ return entries;
+ }
+}
Propchange:
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
------------------------------------------------------------------------------
svn:keywords = author date id revision rev url
Modified:
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/package-info.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/package-info.java?rev=1519608&r1=1519607&r2=1519608&view=diff
==============================================================================
---
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/package-info.java
(original)
+++
sling/trunk/bundles/api/src/main/java/org/apache/sling/api/wrappers/package-info.java
Tue Sep 3 09:12:45 2013
@@ -17,7 +17,7 @@
* under the License.
*/
-@Version("2.2")
+@Version("2.3")
package org.apache.sling.api.wrappers;
import aQute.bnd.annotation.Version;
Added:
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java?rev=1519608&view=auto
==============================================================================
---
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
(added)
+++
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
Tue Sep 3 09:12:45 2013
@@ -0,0 +1,266 @@
+/*
+ * 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 org.apache.sling.api.wrappers;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CompositeValueMapTest {
+
+ // Test property names
+ private static final String PROP_NAME_UNCHANGED = "unchangedProp";
+ private static final String PROP_NAME_OVERRIDDEN = "overriddenProp";
+ private static final String PROP_NAME_NEW_TYPE = "newTypeProp";
+ private static final String PROP_NAME_ADDED = "addedProp";
+ private static final String PROP_NAME_DOES_NOT_EXIST = "doesNotExistProp";
+
+ // Default resource's property values
+ private static final String PROP_DEFAULT_UNCHANGED = "Default value of
property '" + PROP_NAME_UNCHANGED + "'";
+ private static final String PROP_DEFAULT_OVERRIDDEN = "Default value of
property '" + PROP_NAME_OVERRIDDEN + "'";
+ private static final String PROP_DEFAULT_NEW_TYPE = "10";
+
+ // Extended resource's property values
+ private static final String PROP_EXTENDED_OVERRIDDEN = "Extended value of
property '" + PROP_NAME_OVERRIDDEN + "'";
+ private static final Long PROP_EXTENDED_NEW_TYPE = 10L;
+ private static final String PROP_EXTENDED_ADDED = "Extended value of
property '" + PROP_NAME_ADDED + "'";
+
+ private Map<String, Object> defaultProps = getDefaultProps();
+ private Map<String, Object> extendedProps = getExtendedProps();
+
+ @Test
+ public void testMerge() throws Exception {
+ // Get value map for extended node using default node as defaults
+ CompositeValueMap valueMap = new CompositeValueMap(
+ getExtendedProps(),
+ getDefaultProps()
+ );
+
+ Set<CompositeValueMapTestResult> expectations = new
HashSet<CompositeValueMapTestResult>();
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_UNCHANGED));
+ expectations.add(new
CompositeValueMapTestResult(PROP_NAME_OVERRIDDEN));
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_NEW_TYPE,
false, PROP_EXTENDED_NEW_TYPE.getClass()));
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_ADDED));
+ expectations.add(new
CompositeValueMapTestResult(PROP_NAME_DOES_NOT_EXIST));
+
+ verifyResults(valueMap, expectations);
+ }
+
+ @Test
+ public void testMergeNoDefaults() throws Exception {
+ // Get value map for extended node using an empty default
+ CompositeValueMap valueMap = new CompositeValueMap(
+ getExtendedProps(),
+ null
+ );
+
+ Set<CompositeValueMapTestResult> expectations = new
HashSet<CompositeValueMapTestResult>();
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_UNCHANGED,
true)); // Property won't exist as there is no default
+ expectations.add(new
CompositeValueMapTestResult(PROP_NAME_OVERRIDDEN));
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_NEW_TYPE,
false, PROP_EXTENDED_NEW_TYPE.getClass()));
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_ADDED));
+ expectations.add(new
CompositeValueMapTestResult(PROP_NAME_DOES_NOT_EXIST));
+
+ verifyResults(valueMap, expectations);
+ }
+
+ @Test
+ public void testOverride() throws Exception {
+ // Get value map for extended node using default node as defaults
+ // and override only mode
+ CompositeValueMap valueMap = new CompositeValueMap(
+ getExtendedProps(),
+ getDefaultProps(),
+ false
+ );
+
+ Set<CompositeValueMapTestResult> expectations = new
HashSet<CompositeValueMapTestResult>();
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_UNCHANGED));
+ expectations.add(new
CompositeValueMapTestResult(PROP_NAME_OVERRIDDEN));
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_NEW_TYPE,
false, PROP_EXTENDED_NEW_TYPE.getClass()));
+ expectations.add(new CompositeValueMapTestResult(PROP_NAME_ADDED,
true)); // Property won't exist as there is no default and it's an override
+ expectations.add(new
CompositeValueMapTestResult(PROP_NAME_DOES_NOT_EXIST));
+
+ verifyResults(valueMap, expectations);
+ }
+
+ @Test
+ public void testOverrideNoDefaults() throws Exception {
+ // Get value map for extended node using an empty default
+ // and override only mode
+ CompositeValueMap valueMap = new CompositeValueMap(
+ getExtendedProps(),
+ null,
+ false
+ );
+
+ Assert.assertTrue("Final map should be empty", valueMap.isEmpty());
+ }
+
+ private ValueMap getDefaultProps() {
+ final Map<String, Object> defaultProps = new HashMap<String, Object>();
+
+ defaultProps.put(PROP_NAME_UNCHANGED, PROP_DEFAULT_UNCHANGED);
+ defaultProps.put(PROP_NAME_OVERRIDDEN, PROP_DEFAULT_OVERRIDDEN);
+ defaultProps.put(PROP_NAME_NEW_TYPE, PROP_DEFAULT_NEW_TYPE);
+
+ return new ValueMapDecorator(defaultProps);
+ }
+
+ private ValueMap getExtendedProps() {
+ final Map<String, Object> defaultProps = new HashMap<String, Object>();
+
+ defaultProps.put(PROP_NAME_OVERRIDDEN, PROP_EXTENDED_OVERRIDDEN);
+ defaultProps.put(PROP_NAME_NEW_TYPE, PROP_EXTENDED_NEW_TYPE);
+ defaultProps.put(PROP_NAME_ADDED, PROP_EXTENDED_ADDED);
+
+ return new ValueMapDecorator(defaultProps);
+ }
+
+ private void verifyResults(CompositeValueMap valueMap,
Set<CompositeValueMapTestResult> expectations) {
+ Map<String, Object> expectedMap = new HashMap<String, Object>();
+
+ int expectedSize = 0;
+ for (CompositeValueMapTestResult testResult : expectations) {
+ String property = testResult.propertyName;
+
+ if (testResult.doesNotExist()) {
+ Assert.assertFalse("Property '" + property + "' should NOT
exist", valueMap.containsKey(property));
+
+ } else if (testResult.shouldBeDeleted()) {
+ Assert.assertFalse("Property '" + property + "' should NOT be
part of the final map", valueMap.containsKey(property));
+ Assert.assertNull("Property '" + property + "' should be
null", valueMap.get(property));
+
+ } else {
+ Assert.assertTrue("Property '" + property + "' should be part
of the final map", valueMap.containsKey(property));
+ expectedSize++;
+
+ if (testResult.shouldBeUnchanged()) {
+ Assert.assertEquals("Property '" + property + "' should
NOT have changed", testResult.defaultValue, valueMap.get(property));
+ expectedMap.put(property, testResult.defaultValue);
+ }
+
+ if (testResult.shouldBeOverriden()) {
+ Assert.assertEquals("Property '" + property + "' should
have changed", testResult.extendedValue, valueMap.get(property));
+ expectedMap.put(property, testResult.extendedValue);
+ }
+
+ if (testResult.shouldHaveNewType()) {
+ Assert.assertTrue("Type of property '" + property + "'
should have changed",
valueMap.get(property).getClass().equals(testResult.expectedNewType));
+ expectedMap.put(property, testResult.extendedValue);
+ }
+
+ if (testResult.shouldBeAdded()) {
+ Assert.assertEquals("Property '" + property + "' should
have been added", testResult.extendedValue, valueMap.get(property));
+ expectedMap.put(property, testResult.extendedValue);
+ }
+ }
+ }
+
+ Assert.assertEquals("Final map size does NOT match", expectedSize,
valueMap.size());
+ Assert.assertEquals("Final map entries do NOT match",
expectedMap.entrySet(), valueMap.entrySet());
+ Assert.assertEquals("Final map keys do NOT match",
expectedMap.keySet(), valueMap.keySet());
+ Assert.assertTrue("Final map values do NOT match expected: <" +
expectedMap.values() + "> but was: <" + valueMap.values() + ">",
CollectionUtils.isEqualCollection(expectedMap.values(), valueMap.values()));
+ }
+
+ /**
+ * <code>CompositeValueMapTestResult</code> is an internal helper to
analyze
+ * test result and check if the value retrieved from the map matches the
+ * expected value.
+ */
+ private class CompositeValueMapTestResult {
+ private final String propertyName;
+ private final Object defaultValue;
+ private final Object extendedValue;
+ private final boolean shouldBeDeleted;
+ private final Class expectedNewType;
+
+ private CompositeValueMapTestResult(String propertyName) {
+ this(propertyName, false);
+ }
+
+ private CompositeValueMapTestResult(String propertyName, boolean
shouldBeDeleted) {
+ this(propertyName, shouldBeDeleted, null);
+ }
+
+ private CompositeValueMapTestResult(String propertyName, boolean
shouldBeDeleted, Class expectedNewType) {
+ this.propertyName = propertyName;
+ this.defaultValue = defaultProps.get(propertyName);
+ this.extendedValue = extendedProps.get(propertyName);
+ this.shouldBeDeleted = shouldBeDeleted;
+ this.expectedNewType = expectedNewType;
+ }
+
+ /**
+ * Checks if the value should not have changed
+ * @return <code>true</code> if the value should not have changed
+ */
+ boolean shouldBeUnchanged() {
+ return defaultValue != null && extendedValue == null;
+ }
+
+ /**
+ * Checks if the value should have been overridden
+ * @return <code>true</code> if the value should have been overridden
+ */
+ boolean shouldBeOverriden() {
+ return defaultValue != null && extendedValue != null;
+ }
+
+ /**
+ * Checks if the value should have a new type
+ * @return <code>true</code> if the value should have a new type
+ */
+ boolean shouldHaveNewType() {
+ return expectedNewType != null;
+ }
+
+ /**
+ * Checks if the property should have been added
+ * @return <code>true</code> if the property should have been added
+ */
+ boolean shouldBeAdded() {
+ return defaultValue == null && extendedValue != null;
+ }
+
+ /**
+ * Checks if the property should have been deleted
+ * @return <code>true</code> if the property should have been deleted
+ */
+ boolean shouldBeDeleted() {
+ return shouldBeDeleted;
+ }
+
+ /**
+ * Checks if the property should not exist
+ * @return <code>true</code> if the property should not exist
+ */
+ boolean doesNotExist() {
+ return defaultValue == null && extendedValue == null;
+ }
+
+ }
+
+}
Propchange:
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sling/trunk/bundles/api/src/test/java/org/apache/sling/api/wrappers/CompositeValueMapTest.java
------------------------------------------------------------------------------
svn:keywords = author date id revision rev url