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;
+    }
+    
+}