scheu       2002/07/03 15:28:13

  Modified:    java/src/org/apache/axis/encoding
                        SerializationContextImpl.java
               java/test/encoding PackageTests.java
               java/test/wsdl/multiref Main.java
  Added:       java/src/org/apache/axis/utils IdentityHashMap.java
               java/test/encoding TestMultiRefIdentity.java
  Log:
  This is the fix for the multi-ref problem reported John Gregg.
  
  Summary of problem:
    The multi-ref serialization is using a HashMap to determine
    whether instances are multi-ref'd.  This does not work because a HashMap uses
    the hashCode/equals methods of the object...and for generated
    Axis beans these methods do not test object identity (they test member equality).
    This resulted in separate instances being serialized as one instance.
  
  Patch:
    John provided a small patch to SerializationContextImpl
    that forced the multi-ref HashMap to use the object identity
    hashCode.
  
    Thanks John for debugging this problem!!
  
    Tom Jordahl ran into problems when he installed the patch.
  
    (I looked at this a little and I believe the problem is due
     to the multi-ref HashMap being cloned and its keys iterated.
     This caused problems because the keys in the patch were no
     longer the original instance values.)
  
  Solution:
    I proposed an alternate solution that Tom agreed with.
    A new class IdentityHashMap is added to utils.  This class
    behaves like a HashMap except that under the covers the
    keys are differentiated by object identity instead of
    the keys hashCode method.  The keys() method returns
    the original key values instead of the mangled keys...
  
    Looking at the SerializationContextImpl code, I believe that
    the same problems can occur with the secondLevelObjects.
    So I changed secondLevelObjects to be an IdentityHashMap.
  
    I added John's test and I added an extra test to
    the existing multi-ref test to verify the new code.
  
  Revision  Changes    Path
  1.40      +18 -11    
xml-axis/java/src/org/apache/axis/encoding/SerializationContextImpl.java
  
  Index: SerializationContextImpl.java
  ===================================================================
  RCS file: 
/home/cvs/xml-axis/java/src/org/apache/axis/encoding/SerializationContextImpl.java,v
  retrieving revision 1.39
  retrieving revision 1.40
  diff -u -r1.39 -r1.40
  --- SerializationContextImpl.java     3 Jul 2002 17:50:26 -0000       1.39
  +++ SerializationContextImpl.java     3 Jul 2002 22:28:12 -0000       1.40
  @@ -68,6 +68,7 @@
   import org.apache.axis.handlers.soap.SOAPService;
   import org.apache.axis.attachments.Attachments;
   import org.apache.axis.client.Call;
  +import org.apache.axis.utils.IdentityHashMap;
   import org.apache.axis.utils.JavaUtils;
   import org.apache.axis.utils.Mapping;
   import org.apache.axis.utils.NSStack;
  @@ -149,7 +150,7 @@
        * A place to hold objects we cache for multi-ref serialization, and
        * remember the IDs we assigned them.
        */
  -    private HashMap multiRefValues = null;
  +    private IdentityHashMap multiRefValues = null;
       private int multiRefIndex = -1;
   
       class MultiRefItem {
  @@ -166,16 +167,17 @@
   
       }
       /**
  -     * These three variables are necessary to process multi-level object graphs for 
multi-ref
  -     * serialization.
  +     * These three variables are necessary to process 
  +     * multi-level object graphs for multi-ref serialization.
        * While writing out nested multi-ref objects (via outputMultiRef), we
  -     * will fill the secondLevelObjects vector with any new objects encountered.
  +     * will fill the secondLevelObjects vector 
  +     * with any new objects encountered.
        * The outputMultiRefsFlag indicates whether we are currently within the
        * outputMultiRef() method (so that serialization() knows to update the
        * secondLevelObjects vector).
        * The forceSer variable is the trigger to force actual serialization of the 
indicated object.
        */
  -    private HashSet secondLevelObjects = null;
  +    private IdentityHashMap secondLevelObjects = null;
       private Object forceSer = null;
       private boolean outputMultiRefsFlag = false;
   
  @@ -631,7 +633,7 @@
           // processing.
           if (doMultiRefs && (value != forceSer) && !isPrimitive(value, javaType)) {
               if (multiRefIndex == -1)
  -                multiRefValues = new HashMap();
  +                multiRefValues = new IdentityHashMap();
   
               String id;
               MultiRefItem mri = (MultiRefItem)multiRefValues.get(value);
  @@ -650,8 +652,8 @@
                    */
                   if (outputMultiRefsFlag) {
                       if (secondLevelObjects == null)
  -                        secondLevelObjects = new HashSet();
  -                    secondLevelObjects.add(value);
  +                        secondLevelObjects = new IdentityHashMap();
  +                    secondLevelObjects.put(value, value);
                   }
               } else {
                   id = mri.id;
  @@ -716,7 +718,8 @@
                              "CDATA",
                              encodingStyle);
   
  -        Iterator i = ((HashMap)multiRefValues.clone()).keySet().iterator();
  +        Iterator i = 
  +            ((IdentityHashMap)multiRefValues.clone()).keys().iterator();
           while (i.hasNext()) {
               while (i.hasNext()) {
                   Object val = i.next();
  @@ -735,8 +738,12 @@
                             true);   // mri.sendType
               }
   
  +            // Done processing the iterated values.  During the serialization
  +            // of the values, we may have run into new nested values.  These
  +            // were placed in the secondLevelObjects map, which we will now
  +            // process by changing the iterator to locate these values. 
               if (secondLevelObjects != null) {
  -                i = secondLevelObjects.iterator();
  +                i = secondLevelObjects.keys().iterator();
                   secondLevelObjects = null;
               }
           }
  @@ -1055,7 +1062,7 @@
               }
   
               SerializerInfo info = null;
  -// If the javaType is abstract, try getting a
  +            // If the javaType is abstract, try getting a
               // serializer that matches the value's class.
               if (javaType != null &&
                   !javaType.isPrimitive() &&
  
  
  
  1.1                  xml-axis/java/src/org/apache/axis/utils/IdentityHashMap.java
  
  Index: IdentityHashMap.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   *
   * Copyright (c) 2001 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Axis" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  package org.apache.axis.utils;
  
  import org.apache.axis.AxisFault;
  import java.util.Collection;
  import java.util.Map;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Set;
  
  /**
   * IdentityHashMap is like HashMap except that it uses the
   * System identityHashCode of the keys to guarantee uniqueness.
   *
   * @author Rich Scheuerle
   */
  public class IdentityHashMap
  {
      private HashMap keysMap = new HashMap();
      private HashMap valuesMap = new HashMap();
  
      /** 
       * Default Constructor.  
       */
      public IdentityHashMap() { 
          keysMap = new HashMap();
          valuesMap = new HashMap();        
      }
  
      /** 
       * Clear the map  
       */
      public void clear() {
          keysMap.clear();
          valuesMap.clear();
      }
  
      /** 
       * Clone the map
       * @return cloned IdentityHashMap
       */
      public Object clone() {
          IdentityHashMap newMap = new IdentityHashMap();
          newMap.keysMap = (HashMap) keysMap.clone();
          newMap.valuesMap = (HashMap) valuesMap.clone();
          return newMap;
      }
  
      /** 
       * Query if map contains key
       * @param Object key
       * @return boolean indicating if map contains key
       */
      public boolean containsKey(Object key) {
          return keysMap.containsKey(System.identityHashCode(key) + "");
      }
  
      /** 
       * Query if map contains value
       * @param Object value
       * @return boolean indicating if map contains value
       */
      public boolean containsValue(Object value) {
          return valuesMap.containsValue(value);
      }
  
      /**
       * Get value for a particular key
       * @param Object key
       * @return Object value or null
       */
      public Object get(Object key) {
          return valuesMap.get(System.identityHashCode(key) + "");
      }
  
      /**
       * Query if map is empty
       * @return boolean indicating if map is empty
       */
      public boolean isEmpty() {      
          return valuesMap.isEmpty();
      }
  
      /**
       * Get the collection of keys.
       * @return Collection of keys
       */
      public Collection keys() {
          return keysMap.values();
      }
  
      /**
       * Put value for a particular key
       * @param Object key
       * @param Object value
       */
      public void put(Object key, Object value) {
          keysMap.put(System.identityHashCode(key) + "", key);
          valuesMap.put(System.identityHashCode(key) + "", value);
      }
  
      /**
       * Remove mapping for a particular key
       * @param Object key
       * @return Object previous value or null
       */
      public Object remove(Object key) {
          keysMap.remove(System.identityHashCode(key) + "");
          return valuesMap.remove(System.identityHashCode(key) + "");
      }
  
      /**
       * Get the size
       * @return Collection of keys
       */
      public int size() {
          return keysMap.size();
      }
  
  
      /**
       * Get the collection of values.
       * @return Collection of values
       */
      public Collection values() {
          return valuesMap.values();
      }
  
  
  }
  
  
  
  1.18      +1 -1      xml-axis/java/test/encoding/PackageTests.java
  
  Index: PackageTests.java
  ===================================================================
  RCS file: /home/cvs/xml-axis/java/test/encoding/PackageTests.java,v
  retrieving revision 1.17
  retrieving revision 1.18
  diff -u -r1.17 -r1.18
  --- PackageTests.java 28 Jun 2002 12:36:51 -0000      1.17
  +++ PackageTests.java 3 Jul 2002 22:28:13 -0000       1.18
  @@ -36,7 +36,7 @@
           suite.addTestSuite(TestBeanDeser2.class);
           suite.addTestSuite(TestRoundTrip.class);
           suite.addTestSuite(TestOmittedValues.class);
  -
  +        suite.addTestSuite(TestMultiRefIdentity.class);
           return suite;
       }
   }
  
  
  
  1.1                  xml-axis/java/test/encoding/TestMultiRefIdentity.java
  
  Index: TestMultiRefIdentity.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   *
   * Copyright (c) 2002 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Axis" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  package test.encoding;
  
  import java.io.CharArrayWriter;
  
  import javax.xml.namespace.QName;
  
  import junit.framework.Test;
  import junit.framework.TestCase;
  import junit.framework.TestSuite;
  
  import org.apache.axis.encoding.SerializationContextImpl;
  
  /**
   * @author John Gregg ([EMAIL PROTECTED])
   * @author $Author: scheu $
   * @version $Revision: 1.1 $
   */
  public class TestMultiRefIdentity extends TestCase {
      
      public static Test suite() {
          return new TestSuite(test.encoding.TestMultiRefIdentity.class);
      }
      
      public static void main(String[] argv) {
          
          boolean swing = false;
          if (argv.length > 0) {
              if ("-swing".equals(argv[0])) {
                  swing = true;
              }
          }
          
          if (swing) {
              junit.swingui.TestRunner.main(new String[] 
{"test.encoding.TestMultiRefIdentity"});
          } else {
              System.out.println("use '-swing' for the Swing version.");
              junit.textui.TestRunner.main(new String[] 
{"test.encoding.TestMultiRefIdentity"});
          }
      }
      
      
      public TestMultiRefIdentity(String name) {
          super(name);
      }
      
      /**
         Tests when beans are identical and use default hashCode().
      */
      public void testIdentity1() throws Exception {
          TestBeanA tb1 = new TestBeanA();
          tb1.s1 = "john";
          TestBeanA tb2 = tb1;
          
          CharArrayWriter caw = new CharArrayWriter();
          SerializationContextImpl sci = new SerializationContextImpl(caw);
          sci.setDoMultiRefs(true);
          sci.serialize(new QName("someLocalPart"), null, tb1, TestBeanA.class);
          sci.serialize(new QName("someOtherLocalPart"), null, tb2, TestBeanA.class);
          
          String s = caw.toString();
          
          // Cheap but fragile.
          int first = s.indexOf("#id0");
          int last = s.lastIndexOf("#id0");
          assertTrue(first >= 0);
          assertTrue(last >= 0 && last != first);
      }
      
      /**
         Tests when beans are identical and use their own hashCode().
      */
      public void testIdentity2() throws Exception {
          TestBeanB tb1 = new TestBeanB();
          tb1.s1 = "john";
          TestBeanB tb2 = tb1;
          
          CharArrayWriter caw = new CharArrayWriter();
          SerializationContextImpl sci = new SerializationContextImpl(caw);
          sci.setDoMultiRefs(true);
          sci.serialize(new QName("someLocalPart"), null, tb1, TestBeanB.class);
          sci.serialize(new QName("someOtherLocalPart"), null, tb2, TestBeanB.class);
          
          String s = caw.toString();
          
          // Cheap but fragile.
          int first = s.indexOf("#id0");
          int last = s.lastIndexOf("#id0");
          assertTrue(first >= 0);
          assertTrue(last >= 0 && last != first);
      }
      
      /**
         Tests when beans have different contents and rely on default hashCode().
      */
      public void testEquality1() throws Exception {
          TestBeanA tb1 = new TestBeanA();
          tb1.s1 = "john";
          TestBeanA tb2 = new TestBeanA();
          tb2.s1 = "gregg";
          
          CharArrayWriter caw = new CharArrayWriter();
          SerializationContextImpl sci = new SerializationContextImpl(caw);
          sci.setDoMultiRefs(true);
          sci.serialize(new QName("someLocalPart"), null, tb1, TestBeanA.class);
          sci.serialize(new QName("someOtherLocalPart"), null, tb2, TestBeanA.class);
          
          String s = caw.toString();
          
          // Cheap but fragile.
          int first = s.indexOf("#id0");
          int last = s.lastIndexOf("#id1");
          assertTrue(first >= 0);
          assertTrue(last >= 0);
      }
      
      /**
         Tests when beans have same contents but rely on default hashCode().
      */
      public void testEquality2() throws Exception {
          TestBeanA tb1 = new TestBeanA();
          tb1.s1 = "john";
          TestBeanA tb2 = new TestBeanA();
          tb2.s1 = "john";
          
          CharArrayWriter caw = new CharArrayWriter();
          SerializationContextImpl sci = new SerializationContextImpl(caw);
          sci.setDoMultiRefs(true);
          sci.serialize(new QName("someLocalPart"), null, tb1, TestBeanA.class);
          sci.serialize(new QName("someOtherLocalPart"), null, tb2, TestBeanA.class);
          
          String s = caw.toString();
          
          // Cheap but fragile.
          int first = s.indexOf("#id0");
          int last = s.lastIndexOf("#id1");
          assertTrue(first >= 0);
          assertTrue(last >= 0);
      }
      
      /**
         Tests when beans have same contents and use their own hashCode().
      */
      public void testEquality3() throws Exception {
          TestBeanB tb1 = new TestBeanB();
          tb1.s1 = "john";
          TestBeanB tb2 = new TestBeanB();
          tb2.s1 = "john";
          
          CharArrayWriter caw = new CharArrayWriter();
          SerializationContextImpl sci = new SerializationContextImpl(caw);
          sci.setDoMultiRefs(true);
          sci.serialize(new QName("someLocalPart"), null, tb1, TestBeanB.class);
          sci.serialize(new QName("someOtherLocalPart"), null, tb2, TestBeanB.class);
          
          String s = caw.toString();
          
          // Cheap but fragile.
          int first = s.indexOf("#id0");
          int last = s.lastIndexOf("#id1");
          assertTrue(first >= 0);
          assertTrue(last >= 0 && last != first);
      }
      
      class TestBeanA {
          String s1 = null;
          
          // uses default equals() and hashCode().
      }
      
      class TestBeanB {
          String s1 = null;
          
          public boolean equals(Object o) {
              if (o == null) return false;
              if (this == o) return true;
              if (!o.getClass().equals(this.getClass())) return false;
              
              TestBeanB tbb = (TestBeanB)o;
              if (this.s1 != null) {
                  return this.s1.equals(tbb.s1);
              } else {
                  return this.s1 == tbb.s1;
              }
          }
          
          public int hashCode() {
              // XXX???
              if (this.s1 == null) return super.hashCode();
              else return this.s1.hashCode();
          }
      }
  }
  
  
  
  1.4       +38 -0     xml-axis/java/test/wsdl/multiref/Main.java
  
  Index: Main.java
  ===================================================================
  RCS file: /home/cvs/xml-axis/java/test/wsdl/multiref/Main.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- Main.java 14 Jun 2002 18:48:40 -0000      1.3
  +++ Main.java 3 Jul 2002 22:28:13 -0000       1.4
  @@ -183,6 +183,44 @@
           }
   
           // ----------------------
  +        // Create a 'loop' tree.  The children of the root have children that 
reference the root.
  +        // In this test both children have the same data (thus the same equals()).
  +        // There should still be separate nodes passed over the wire
  +        t = new Node();
  +        t.setData(0);
  +        l = new Node();
  +        l.setData(1);    
  +        r = new Node();
  +        r.setData(1);
  +        t.setLeft(l);
  +        t.setRight(r);
  +
  +        l.setLeft(t);
  +        l.setRight(t);
  +        r.setLeft(t);
  +        r.setRight(t);
  +
  +        holder = new NodeHolder(t);
  +
  +        // Test for loops
  +        rc = remoteTest.testLoop(holder);
  +        if (rc == 0) {
  +            // System.err.println("Passed testLoop 1B");
  +        } else {
  +            System.err.println("Failed testLoop 1B");
  +            throw new Exception("Failed testLoop 1B with "+rc);
  +        }
  +        // Test returns the tree.  To make sure it returned it successfully,
  +        // invoke the test again!
  +        rc = remoteTest.testLoop(holder);
  +        if (rc == 0) {
  +            // System.err.println("Passed testLoop 2B");
  +        } else {
  +            System.err.println("Failed testLoop 2B");
  +            throw new Exception("Failed testLoop 2B with "+rc);
  +        }
  +
  +        // ----------------------
           // Test passing of the same node argument.
           t = new Node();
           t.setData(0);
  
  
  


Reply via email to