You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-dev@axis.apache.org by ba...@apache.org on 2008/09/17 22:31:04 UTC
svn commit: r696437 - in /webservices/axis2/trunk/java/modules/kernel:
src/org/apache/axis2/context/ test/org/apache/axis2/context/
Author: barrettj
Date: Wed Sep 17 13:31:03 2008
New Revision: 696437
URL: http://svn.apache.org/viewvc?rev=696437&view=rev
Log:
Fix intermittent ConcurrentModificationException on async responses caused by accessing the properties map.
Implement a ConcurrentHashMap with the same null semantics as HashTable. Add associated tests to verify
semantics of the collection and the externalized form.
Added:
webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java
webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java
Modified:
webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java
webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java
webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java
webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java
webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java
webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java
Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java?rev=696437&r1=696436&r2=696437&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/AbstractContext.java Wed Sep 17 13:31:03 2008
@@ -28,10 +28,16 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
/**
* This is the top most level of the Context hierarchy and is a bag of properties.
@@ -52,6 +58,7 @@
protected long lastTouchedTime;
protected transient AbstractContext parent;
+
protected transient Map properties;
private transient Map propertyDifferences;
@@ -93,9 +100,7 @@
* {@link #setProperty(String, Object)} & {@link #removeProperty(String)}instead.
*/
public Map getProperties() {
- if (this.properties == null) {
- this.properties = new HashMap(DEFAULT_MAP_SIZE);
- }
+ initPropertiesMap();
return properties;
}
@@ -106,9 +111,7 @@
* @return Iterator over a collection of keys
*/
public Iterator getPropertyNames() {
- if (properties == null) {
- properties = new HashMap(DEFAULT_MAP_SIZE);
- }
+ initPropertiesMap();
return properties.keySet().iterator();
}
@@ -174,9 +177,7 @@
* @param value
*/
public void setProperty(String key, Object value) {
- if (this.properties == null) {
- this.properties = new HashMap(DEFAULT_MAP_SIZE);
- }
+ initPropertiesMap();
properties.put(key, value);
addPropertyDifference(key, value, false);
if (DEBUG_ENABLED) {
@@ -231,9 +232,7 @@
* @param value
*/
public void setNonReplicableProperty(String key, Object value) {
- if (this.properties == null) {
- this.properties = new HashMap(DEFAULT_MAP_SIZE);
- }
+ initPropertiesMap();
properties.put(key, value);
}
@@ -328,7 +327,10 @@
}
}
}
- this.properties = properties;
+ // The Map we got argument is probably NOT an instance of the Concurrent
+ // map we use to store properties, so create a new one using the values from the
+ // argument map.
+ this.properties = new ConcurrentHashMapNullSemantics(properties);
}
}
}
@@ -341,9 +343,7 @@
*/
public void mergeProperties(Map props) {
if (props != null) {
- if (this.properties == null) {
- this.properties = new HashMap(DEFAULT_MAP_SIZE);
- }
+ initPropertiesMap();
for (Iterator iterator = props.keySet().iterator();
iterator.hasNext();) {
Object key = iterator.next();
@@ -417,4 +417,271 @@
log.debug("==================");
}
}
+
+ /**
+ * If the 'properties' map has not been allocated yet, then allocate it.
+ */
+ private void initPropertiesMap() {
+ if (properties == null) {
+ // This needs to be a concurrent collection to prevent ConcurrentModificationExcpetions
+ // for async-on-the-wire. It was originally:
+// properties = new HashMap(DEFAULT_MAP_SIZE);
+ properties = new ConcurrentHashMapNullSemantics(DEFAULT_MAP_SIZE);
+ }
+ }
+}
+
+/**
+ * ConcurrentHashMap that supports the same null semantics of HashMap, which means allowing null
+ * as a key and/or value. ConcurrentHashMap throws an NullPointerException if either of those
+ * are null. This is done by representing null keys and/or values with a non-null value stored in
+ * the collection and mapping to actual null values for the methods such as put, get, and remove.
+ *
+ * @param <K> Key type
+ * @param <V> Value type
+ */
+class ConcurrentHashMapNullSemantics<K,V> extends ConcurrentHashMap<K,V> {
+
+ private static final long serialVersionUID = 3740068332380174316L;
+
+ // Constants to represent a null key or value in the collection since actual null values
+ // will cause a NullPointerExcpetion in a ConcurrentHashMap collection.
+ private static final Object NULL_KEY_INDICATOR = new Object();
+ private static final Object NULL_VALUE_INDICATOR = new Object();
+
+ public ConcurrentHashMapNullSemantics() {
+ super();
+ }
+ public ConcurrentHashMapNullSemantics(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public ConcurrentHashMapNullSemantics(Map<? extends K, ? extends V> map) {
+ super(map);
+ }
+
+ /**
+ * Similar to ConcurrentHashMap.put except null is allowed for the key and/or value.
+ * @see java.util.concurrent.ConcurrentHashMap#put(java.lang.Object, java.lang.Object)
+ */
+ public V put(K key, V value) {
+ return (V) valueFromMap(super.put((K) keyIntoMap(key), (V) valueIntoMap(value)));
+ }
+
+ /**
+ * Similar to ConcurrentHashMap.get except null is allowed for the key and/or value.
+ * @see java.util.concurrent.ConcurrentHashMap#get(java.lang.Object)
+ */
+ public V get(Object key) {
+ return (V) valueFromMap(super.get(keyIntoMap(key)));
+ }
+
+ /**
+ * Similar to ConcurrentHashMap.remove except null is allowed for the key and/or value.
+ * @see java.util.concurrent.ConcurrentHashMap#remove(java.lang.Object)
+ */
+ public V remove(Object key) {
+ // If the key is null, then look for the null key constant value in the table.
+ return (V) valueFromMap(super.remove(keyIntoMap(key)));
+ }
+
+ /**
+ * Similar to entrySet EXCEPT (1) nulls are allowed for keys and values and (2) this
+ * does NOT RETURN a LIVE SET. Any changes made to the returned set WILL NOT be reflected in
+ * the underlying collection. Also, any changes made to the collection after the set is
+ * returned WILL NOT be reflected in the set. This method returns a copy of the entries in the
+ * underlying collection at the point it is called.
+ *
+ * @see java.util.concurrent.ConcurrentHashMap#entrySet()
+ */
+ public Set<Map.Entry<K,V>> entrySet() {
+ // The super returns a ConcurrentHashMap$EntrySet
+ Set<Map.Entry<K, V>> collectionSet = super.entrySet();
+ Set<Map.Entry<K, V>> returnSet = new HashSet<Map.Entry<K, V>>();
+ Iterator<Map.Entry<K, V>> collectionSetIterator = collectionSet.iterator();
+ // Go through the set that will be returned mapping keys and values back to null as
+ // appropriate.
+ while (collectionSetIterator.hasNext()) {
+ Map.Entry entry = collectionSetIterator.next();
+ Object useKey = keyFromMap(entry.getKey());
+ Object useValue = valueFromMap(entry.getValue());
+ Map.Entry<K, V> returnEntry = new SetEntry<K, V>((K) useKey, (V) useValue);
+ returnSet.add(returnEntry);
+ }
+ return returnSet;
+ }
+
+ /**
+ * Similar to keySet EXCEPT (1) nulls are allowed for keys and (2) this
+ * does NOT RETURN a LIVE SET. Any changes made to the returned set WILL NOT be reflected in
+ * the underlying collection. Also, any changes made to the collection after the set is
+ * returned WILL NOT be reflected in the set. This method returns a copy of the keys in the
+ * underlying collection at the point it is called.
+ *
+ * @see java.util.concurrent.ConcurrentHashMap#keySet()
+ */
+ public Set<K> keySet() {
+ Set<K> keySet = new SetKey<K>(super.keySet());
+ return keySet;
+ }
+
+ /**
+ * Similar to containsKey except a null key is allowed.
+ *
+ * @see java.util.concurrent.ConcurrentHashMap#containsKey(java.lang.Object)
+ */
+ public boolean containsKey(Object key) {
+ return super.containsKey(keyIntoMap(key));
+ }
+
+ // REVIEW: Some of these may already work. Any that are using put or get to access the elements
+ // in the Map will work. These are not currently used for AbstractContext properties, so there
+ // are no tests yet to verify they work (except as noted below)
+ public boolean contains(Object value) {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public boolean containsValue(Object value) {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public Enumeration<V> elements() {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public Enumeration<K> keys() {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ // Note that putAll(..) works for nulls because it is using put(K,V) for each element in the
+ // argument Map.
+// public void putAll(Map<? extends K, ? extends V> t) {
+// throw new UnsupportedOperationException("Not implemented for null semantics");
+// }
+ public V putIfAbsent(K key, V value) {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public boolean remove(Object key, Object value) {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public boolean replace(K key, V oldValue, V newValue) {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+ public Collection<V> values() {
+ throw new UnsupportedOperationException("Not implemented for null semantics");
+ }
+
+ /**
+ * Returns a key that can be set into the collection. If the key is null, a non-null value
+ * representing a null key is returned.
+ * @param key The key to be set into the collection; it can be null
+ * @return key if it is non-null, or a constant representing a null key if key was null.
+ */
+ private Object keyIntoMap(Object key) {
+ if (key == null) {
+ return NULL_KEY_INDICATOR;
+ } else {
+ return key;
+ }
+ }
+
+ /**
+ * Returns a key that was retrieved from the collection. If the key the constant representing
+ * a previously put null key, then null will be returned. Otherwise, the key is returned.
+ * @param key The key retreived from the collection
+ * @return null if the key represents a previously put key that was null, otherwise the
+ * value of key is returned.
+ */
+ private Object keyFromMap(Object key) {
+ if (key == NULL_KEY_INDICATOR) {
+ return null;
+ } else {
+ return key;
+ }
+ }
+
+ /**
+ * Returns a value that can be set into the collection. If the valuse is null, a non-null value
+ * representing a null value is returned.
+ * @param value The value to be set into the collection; it can be null
+ * @return value if it is non-null, or a constant representing a null value if value was null.
+ */
+ private Object valueIntoMap(Object value) {
+ if (value == null) {
+ return NULL_VALUE_INDICATOR;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Returns a value that was retrieved from the collection. If the value the constant representing
+ * a previously put null value, then null will be returned. Otherwise, the value is returned.
+ * @param value The value retreived from the collection
+ * @return null if the value represents a previously put value that was null, otherwise the
+ * value of value is returned.
+ */
+ private Object valueFromMap(Object value) {
+ if (value == NULL_VALUE_INDICATOR) {
+ return null;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * An Entry to be returned in a Set for the elements in the collection. Note that both the key
+ * and the value may be null.
+ *
+ * @param <K> Key type
+ * @param <V> Value type
+ */
+ class SetEntry<K, V> implements Map.Entry<K, V> {
+ private K theKey = null;
+ private V theValue = null;
+
+ SetEntry(K key, V value) {
+ this.theKey = key;
+ this.theValue = value;
+ }
+
+ public K getKey() {
+ return this.theKey;
+ }
+
+ public V getValue() {
+ return this.theValue;
+ }
+
+ public V setValue(V value) {
+ this.theValue = value;
+ return this.theValue;
+ }
+
+ }
+
+ /**
+ * A set of Keys returned from the collection. Note that a key may be null.
+ *
+ * @param <K> Key type
+ */
+ class SetKey<K> extends AbstractSet<K> {
+ Set<K> set = null;
+ SetKey(Set collectionKeySet) {
+ set = new HashSet<K>();
+ Iterator collectionIterator = collectionKeySet.iterator();
+ while (collectionIterator.hasNext()) {
+ Object useKey = collectionIterator.next();
+ set.add((K) keyFromMap(useKey));
+ }
+ }
+ @Override
+ public Iterator<K> iterator() {
+ return set.iterator();
+ }
+
+ @Override
+ public int size() {
+ return set.size();
+ }
+ }
}
Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java?rev=696437&r1=696436&r2=696437&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/MessageContext.java Wed Sep 17 13:31:03 2008
@@ -3295,7 +3295,7 @@
log.trace(getLogIDString() +
": readExternal(): About to read properties, marker is: " + marker);
}
- properties = in.readHashMap();
+ properties = in.readMap(new ConcurrentHashMapNullSemantics());
//---------------------------------------------------------
Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java?rev=696437&r1=696436&r2=696437&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/OperationContext.java Wed Sep 17 13:31:03 2008
@@ -572,7 +572,7 @@
// properties
//---------------------------------------------------------
in.readUTF(); // read marker
- properties = in.readHashMap();
+ properties = in.readMap(new ConcurrentHashMapNullSemantics());
//---------------------------------------------------------
// axis operation meta data
Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java?rev=696437&r1=696436&r2=696437&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceContext.java Wed Sep 17 13:31:03 2008
@@ -434,7 +434,7 @@
//---------------------------------------------------------
// properties
//---------------------------------------------------------
- properties = in.readHashMap();
+ properties = in.readMap(new ConcurrentHashMapNullSemantics());
//---------------------------------------------------------
// AxisService
Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java?rev=696437&r1=696436&r2=696437&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/ServiceGroupContext.java Wed Sep 17 13:31:03 2008
@@ -378,7 +378,7 @@
//---------------------------------------------------------
// properties
//---------------------------------------------------------
- properties = in.readHashMap();
+ properties = in.readMap(new ConcurrentHashMapNullSemantics());
//---------------------------------------------------------
// AxisServiceGroup
Modified: webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java?rev=696437&r1=696436&r2=696437&view=diff
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java (original)
+++ webservices/axis2/trunk/java/modules/kernel/src/org/apache/axis2/context/SessionContext.java Wed Sep 17 13:31:03 2008
@@ -275,7 +275,7 @@
//---------------------------------------------------------
// properties
//---------------------------------------------------------
- properties = in.readHashMap();
+ properties = in.readMap(new ConcurrentHashMapNullSemantics());
//---------------------------------------------------------
// "nested"
Added: webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java?rev=696437&view=auto
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java (added)
+++ webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesExternalizeTest.java Wed Sep 17 13:31:03 2008
@@ -0,0 +1,231 @@
+/*
+ * 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.axis2.context;
+
+import org.apache.axis2.description.OutOnlyAxisOperation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the externalization of properties on an AbstractContext. Since each of the AbstractContext
+ * subclasses have their own read and write External methods, each one is tested.
+ */
+public class ContextPropertiesExternalizeTest extends TestCase {
+
+ public void testExternalizeMessageContext() {
+ MessageContext mc = new MessageContext();
+ mc.setProperty("key1", "value1");
+ mc.setProperty("key2", "value2");
+ mc.setProperty(null, "value3_nullKey");
+ mc.setProperty("key4_nullValue", null);
+ mc.setProperty("key5", "value5");
+
+ assertTrue(mc.properties instanceof ConcurrentHashMapNullSemantics);
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+ mc.writeExternal(oos);
+ oos.flush();
+ oos.close();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+
+ MessageContext mcRead = new MessageContext();
+ mcRead.readExternal(ois);
+
+ assertEquals(5, mcRead.properties.size());
+ assertEquals("value1", mcRead.getProperty("key1"));
+ assertEquals("value2", mcRead.getProperty("key2"));
+ assertEquals("value3_nullKey", mcRead.getProperty(null));
+ assertNull(mcRead.getProperty("key4_nullValue"));
+ assertEquals("value5", mcRead.getProperty("key5"));
+ assertTrue(mcRead.properties instanceof ConcurrentHashMapNullSemantics);
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ fail("Caught exception: " + ex);
+ }
+
+
+ }
+
+ public void testExternalizeOperationContext() {
+ OperationContext ctx = new OperationContext(new OutOnlyAxisOperation(), new ServiceContext());
+
+ ctx.setProperty("key1", "value1");
+ ctx.setProperty("key2", "value2");
+ ctx.setProperty(null, "value3_nullKey");
+ ctx.setProperty("key4_nullValue", null);
+ ctx.setProperty("key5", "value5");
+
+ assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+ ctx.writeExternal(oos);
+ oos.flush();
+ oos.close();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+
+ OperationContext ctxRead = new OperationContext();
+ ctxRead.readExternal(ois);
+
+ assertEquals(5, ctxRead.properties.size());
+ assertEquals("value1", ctxRead.getProperty("key1"));
+ assertEquals("value2", ctxRead.getProperty("key2"));
+ assertEquals("value3_nullKey", ctxRead.getProperty(null));
+ assertNull(ctxRead.getProperty("key4_nullValue"));
+ assertEquals("value5", ctxRead.getProperty("key5"));
+ assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ fail("Caught exception: " + ex);
+ }
+ }
+
+ public void testExternalizeServiceContext() {
+ ServiceContext ctx = new ServiceContext();
+
+ ctx.setProperty("key1", "value1");
+ ctx.setProperty("key2", "value2");
+ ctx.setProperty(null, "value3_nullKey");
+ ctx.setProperty("key4_nullValue", null);
+ ctx.setProperty("key5", "value5");
+
+ assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+ ctx.writeExternal(oos);
+ oos.flush();
+ oos.close();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+
+ ServiceContext ctxRead = new ServiceContext();
+ ctxRead.readExternal(ois);
+
+ assertEquals(5, ctxRead.properties.size());
+ assertEquals("value1", ctxRead.getProperty("key1"));
+ assertEquals("value2", ctxRead.getProperty("key2"));
+ assertEquals("value3_nullKey", ctxRead.getProperty(null));
+ assertNull(ctxRead.getProperty("key4_nullValue"));
+ assertEquals("value5", ctxRead.getProperty("key5"));
+ assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ fail("Caught exception: " + ex);
+ }
+ }
+
+ public void testExternalizeServiceGroupContext() {
+ ServiceGroupContext ctx = new ServiceGroupContext();
+
+ ctx.setProperty("key1", "value1");
+ ctx.setProperty("key2", "value2");
+ ctx.setProperty(null, "value3_nullKey");
+ ctx.setProperty("key4_nullValue", null);
+ ctx.setProperty("key5", "value5");
+
+ assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+ ctx.writeExternal(oos);
+ oos.flush();
+ oos.close();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+
+ ServiceGroupContext ctxRead = new ServiceGroupContext();
+ ctxRead.readExternal(ois);
+
+ assertEquals(5, ctxRead.properties.size());
+ assertEquals("value1", ctxRead.getProperty("key1"));
+ assertEquals("value2", ctxRead.getProperty("key2"));
+ assertEquals("value3_nullKey", ctxRead.getProperty(null));
+ assertNull(ctxRead.getProperty("key4_nullValue"));
+ assertEquals("value5", ctxRead.getProperty("key5"));
+ assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ fail("Caught exception: " + ex);
+ }
+ }
+
+ public void testExternalizeSessionContext() {
+ SessionContext ctx = new SessionContext();
+
+ ctx.setProperty("key1", "value1");
+ ctx.setProperty("key2", "value2");
+ ctx.setProperty(null, "value3_nullKey");
+ ctx.setProperty("key4_nullValue", null);
+ ctx.setProperty("key5", "value5");
+
+ assertTrue(ctx.properties instanceof ConcurrentHashMapNullSemantics);
+
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+ ctx.writeExternal(oos);
+ oos.flush();
+ oos.close();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bais);
+
+ SessionContext ctxRead = new SessionContext();
+ ctxRead.readExternal(ois);
+
+ assertEquals(5, ctxRead.properties.size());
+ assertEquals("value1", ctxRead.getProperty("key1"));
+ assertEquals("value2", ctxRead.getProperty("key2"));
+ assertEquals("value3_nullKey", ctxRead.getProperty(null));
+ assertNull(ctxRead.getProperty("key4_nullValue"));
+ assertEquals("value5", ctxRead.getProperty("key5"));
+ assertTrue(ctxRead.properties instanceof ConcurrentHashMapNullSemantics);
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ fail("Caught exception: " + ex);
+ }
+ }
+}
Added: webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java
URL: http://svn.apache.org/viewvc/webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java?rev=696437&view=auto
==============================================================================
--- webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java (added)
+++ webservices/axis2/trunk/java/modules/kernel/test/org/apache/axis2/context/ContextPropertiesTest.java Wed Sep 17 13:31:03 2008
@@ -0,0 +1,309 @@
+/*
+ * 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.axis2.context;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the setting of properties on an AbstractContext instance. Originally the properties
+ * collection was a HashMap, but was changed to a Concurrent collection because multiple threads
+ * could be accessing the properties at the same time for aysnc wire flows, when processing
+ * responses.
+ *
+ * The intent is to retain the original semantics of the HashMap which allows null keys and null
+ * values, since areas of the code seem to make use of those semantics. Note that most of the
+ * tests below use MessageContext as the concreate implemntation of the abstract
+ * AbstractContext.
+ */
+public class ContextPropertiesTest extends TestCase {
+
+ /**
+ * Test basic property setting, including using null keys and values.
+ */
+ public void testProperties() {
+ MessageContext mc = new MessageContext();
+
+ // Test getting a non-existent key returns a null value
+ assertNull(mc.getProperty("NonexistenKey"));
+
+ // Test setting a null property value;
+ mc.setProperty("testProperty1", null);
+ assertNull(mc.getProperty("testProperty1"));
+
+ // Test setting a null property key
+ mc.setProperty(null, "value");
+ Object value = mc.getProperty(null);
+ assertEquals("value", (String) value);
+
+ // Test setting a null key and value
+ mc.setProperty(null, null);
+ assertNull(mc.getProperty(null));
+
+ // Test setting a null value after a valid value; the next get should return null
+ String property2 = "testProperty2";
+ String testValue2 = "value2";
+ mc.setProperty(property2, testValue2);
+ Object value2 = mc.getProperty(property2);
+ assertEquals(testValue2, value2);
+ mc.setProperty(property2, null);
+ assertNull(mc.getProperty(property2));
+
+ }
+
+ /**
+ * Test setting the properties based on an input Map. NOTE that MessageContext has logic
+ * in setProperties(Map) that always performs a copy by setting a COPY_PROPERTIES flag.
+ * There there is an additional test below that uses a mock object implemntation of
+ * AbstractContext to make sure the collection remains a concurrent one.
+ * @see #testPropertiesAssignment_MockAbstractContext()
+ */
+ public void testPropertiesAssignment() {
+ MessageContext mc = new MessageContext();
+
+ Map map = new HashMap();
+ map.put("keyFromMap1", "valueFromMap1");
+ map.put("keyFromMap2", "valueFromMap2");
+ map.put("keyFromMap3", null);
+ map.put(null, "valueFromMapNullKey");
+
+
+ mc.setProperties(map);
+ assertEquals("valueFromMap1", mc.getProperty("keyFromMap1"));
+ assertEquals("valueFromMap2", mc.getProperty("keyFromMap2"));
+ assertEquals(null, mc.getProperty("keyFromMap3"));
+ assertEquals("valueFromMapNullKey", mc.getProperty(null));
+ }
+
+ /**
+ * Test setting the properties based on an input Map. Make sure that the resulting collection
+ * on the AbstractContext is a Concurrent collection.
+ * @see #testPropertiesAssignment()
+ */
+ public void testPropertiesAssignment_MockAbstractContext() {
+ MockAbstractContext mc = new MockAbstractContext();
+ assertTrue(mc.getProperties() instanceof ConcurrentHashMapNullSemantics);
+
+ Map map = new HashMap();
+ map.put("keyFromMap1", "valueFromMap1");
+ map.put("keyFromMap2", "valueFromMap2");
+ map.put("keyFromMap3", null);
+ map.put(null, "valueFromMapNullKey");
+
+ mc.setProperties(map);
+ assertEquals("valueFromMap1", mc.getProperty("keyFromMap1"));
+ assertEquals("valueFromMap2", mc.getProperty("keyFromMap2"));
+ assertEquals(null, mc.getProperty("keyFromMap3"));
+ assertEquals("valueFromMapNullKey", mc.getProperty(null));
+ assertTrue(mc.getProperties() instanceof ConcurrentHashMapNullSemantics);
+
+ }
+
+ /**
+ * Test removing elements from the collection.
+ */
+ public void testPropertiesRemove() {
+ MessageContext mc = new MessageContext();
+ // Remove null key not previously added, make sure it is gone
+ mc.removeProperty(null);
+
+ // Remove null key previously added
+ mc.setProperty(null, "ValueForNullKey");
+ assertEquals("ValueForNullKey", mc.getProperty(null));
+ mc.removeProperty(null);
+ assertNull(mc.getProperty(null));
+
+ // Remove non-existent key
+ assertNull(mc.getProperty("NonexistentKey"));
+ mc.removeProperty("NonexistentKey");
+ assertNull(mc.getProperty("NonexistentKey"));
+
+ // Remove non-null key, non-null value make sure it is gone
+ mc.setProperty("nonNullKey1", "nonNullValue1");
+ assertEquals("nonNullValue1", mc.getProperty("nonNullKey1"));
+ mc.removeProperty("nonNullKey1");
+ assertNull(mc.getProperty("nonNullKey1"));
+
+ // Remove non-null key, null value & make sure it is gone
+ mc.setProperty("nonNullKey2", null);
+ assertEquals(null, mc.getProperty("nonNullKey2"));
+ mc.removeProperty("nonNullKey2");
+ assertNull(mc.getProperty("nonNullKey2"));
+
+ }
+
+ public void testCollectionEntrySet() {
+ MockAbstractContext mc = new MockAbstractContext();
+ mc.setProperty("key1", "value1");
+ mc.setProperty("key2", "value2");
+ mc.setProperty("key3", null);
+ mc.setProperty(null, "value4-nullkey");
+ mc.setProperty("key5", "value5");
+
+ Map checkProperties = mc.getProperties();
+ Set entrySet = checkProperties.entrySet();
+ Iterator entryIterator = entrySet.iterator();
+ int correctEntries = 0;
+ while (entryIterator.hasNext()) {
+ Map.Entry entry = (Map.Entry) entryIterator.next();
+ // The collection uses Object instances to represent nulls in the collection. If
+ // the conversion back to nulls hasn't occured, that will cause a CastClassException
+ // when it is casted to a (String).
+ try {
+ String checkKey = (String) entry.getKey();
+ String checkValue = (String) entry.getValue();
+ if ("key1".equals(checkKey) && "value1".equals(checkValue)) {
+ correctEntries++;
+ } else if ("key2".equals(checkKey) && "value2".equals(checkValue)) {
+ correctEntries++;
+ } else if ("key3".equals(checkKey) && (checkValue == null)) {
+ correctEntries++;
+ } else if ((checkKey == null) && "value4-nullkey".equals(checkValue)) {
+ correctEntries++;
+ } else if ("key5".equals(checkKey) && "value5".equals(checkValue)) {
+ correctEntries++;
+ } else {
+ fail("Invalid entry: key: " + checkKey + ", value: " + checkValue);
+ }
+ } catch (ClassCastException e) {
+ e.printStackTrace();
+ fail("Caught ClassCastException " + e.toString());
+ }
+ }
+ assertEquals(5, correctEntries);
+ }
+
+ public void testCollectionKeySet() {
+ MockAbstractContext mc = new MockAbstractContext();
+ mc.setProperty("key1", "value1");
+ mc.setProperty("key2", "value2");
+ mc.setProperty("key3", null);
+ mc.setProperty(null, "value4-nullkey");
+ mc.setProperty("key5", "value5");
+
+ Map checkProperties = mc.getProperties();
+ Set keySet = checkProperties.keySet();
+ Iterator keyIterator = keySet.iterator();
+ int correctEntries = 0;
+ while (keyIterator.hasNext()) {
+ // The collection uses Object instances to represent nulls in the collection. If
+ // the conversion back to nulls hasn't occured, that will cause a CastClassException
+ // when it is casted to a (String).
+ try {
+ String checkKey = (String) keyIterator.next();
+ if ("key1".equals(checkKey)) {
+ correctEntries++;
+ } else if ("key2".equals(checkKey)) {
+ correctEntries++;
+ } else if ("key3".equals(checkKey)) {
+ correctEntries++;
+ } else if ((checkKey == null)) {
+ correctEntries++;
+ } else if ("key5".equals(checkKey)) {
+ correctEntries++;
+ } else {
+ fail("Invalid entry: key: " + checkKey);
+ }
+ } catch (ClassCastException e) {
+ fail("Caught ClassCastException " + e.toString());
+ }
+ }
+ assertEquals(5, correctEntries);
+ }
+
+ public void testCollectionContainsKey() {
+ MockAbstractContext mc = new MockAbstractContext();
+ mc.setProperty("key1", "value1");
+ mc.setProperty("key2", "value2");
+ mc.setProperty("key3", null);
+ mc.setProperty(null, "value4-nullkey");
+ mc.setProperty("key5", "value5");
+
+ Map checkCollection = mc.getProperties();
+
+ assertTrue(checkCollection.containsKey("key1"));
+ assertTrue(checkCollection.containsKey(null));
+ assertFalse(checkCollection.containsKey("notHere"));
+ }
+
+ /**
+ * Test some aspects of the Concurrent collection directly, such as creating with and without
+ * generics, and creating with a Map.
+ */
+ public void testConcurrentHashMapNullSemantics() {
+
+ ConcurrentHashMapNullSemantics noGenerics = new ConcurrentHashMapNullSemantics();
+ noGenerics.put("key", "value");
+ noGenerics.put(1, 2);
+
+ ConcurrentHashMapNullSemantics<String, String> stringGenerics = new ConcurrentHashMapNullSemantics<String, String>();
+ stringGenerics.put("key", "value");
+
+ ConcurrentHashMapNullSemantics<Integer, Integer> integerGenerics = new ConcurrentHashMapNullSemantics<Integer, Integer>();
+ integerGenerics.put(new Integer(1), new Integer(2));
+
+ Map<String, String> map = new HashMap<String, String>();
+ map.put("k1", "v1");
+ map.put("k2", "v2");
+ map.put(null, "v3NullKey");
+ map.put("k3_null", null);
+ ConcurrentHashMapNullSemantics<String, String> useMapGenerics = new ConcurrentHashMapNullSemantics<String, String>(map);
+ assertEquals("v1", useMapGenerics.get("k1"));
+ assertEquals("v2", useMapGenerics.get("k2"));
+ assertEquals("v3NullKey", useMapGenerics.get(null));
+ assertNull(useMapGenerics.get("k3_null"));
+
+ // put returns the previous value if there was one, or null
+ assertEquals("v1", useMapGenerics.put("k1", "newK1Value"));
+ assertNull(useMapGenerics.put("k3_null", "newK3Value"));
+ assertNull(useMapGenerics.put("noSuchKey-put", "value6"));
+
+ // remove returns the value if there was an entry with that key, or null
+ assertNull(useMapGenerics.remove("noSuchKey-remove"));
+ useMapGenerics.put("key7", null);
+ assertNull(useMapGenerics.remove("key7"));
+
+ ConcurrentHashMapNullSemantics useMapNoGenerics = new ConcurrentHashMapNullSemantics(map);
+ assertEquals("v1", useMapNoGenerics.get("k1"));
+ assertEquals("v2", useMapNoGenerics.get("k2"));
+
+ }
+
+}
+
+class MockAbstractContext extends AbstractContext {
+
+ MockAbstractContext() {
+ properties = new ConcurrentHashMapNullSemantics();
+ }
+
+ @Override
+ public ConfigurationContext getRootContext() {
+ return null;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+}