Merge "BUG-1431: make sure (Yang)InstanceIdentifier is Serializable"
authorTony Tkacik <ttkacik@cisco.com>
Mon, 15 Sep 2014 10:09:50 +0000 (10:09 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 15 Sep 2014 10:09:50 +0000 (10:09 +0000)
yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/InstanceIdentifier.java
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java
yang/yang-data-api/src/test/java/org/opendaylight/yangtools/yang/data/api/InstanceIdentifierTest.java

index 9a597152c3ce4f3aa1a4e04e8ff94c230378e0c4..9840338bb9442bd82db429ef1a2379db6a45d2ee 100644 (file)
@@ -13,13 +13,13 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-
 import java.io.IOException;
 import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-
 import org.opendaylight.yangtools.concepts.Builder;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.concepts.Path;
@@ -55,17 +55,29 @@ import org.opendaylight.yangtools.util.HashCodeBuilder;
  *
  */
 public class InstanceIdentifier<T extends DataObject> implements Path<InstanceIdentifier<? extends DataObject>>, Immutable, Serializable {
-    private static final long serialVersionUID = 1L;
+    private static final Field PATHARGUMENTS_FIELD;
+    private static final long serialVersionUID = 2L;
     /*
      * Protected to differentiate internal and external access. Internal
      * access is required never to modify the contents. References passed
      * to outside entities have to be wrapped in an unmodifiable view.
      */
-    protected final Iterable<PathArgument> pathArguments;
+    protected transient final Iterable<PathArgument> pathArguments;
     private final Class<T> targetType;
     private final boolean wildcarded;
     private final int hash;
 
+    static {
+        final Field f;
+        try {
+            f = InstanceIdentifier.class.getDeclaredField("pathArguments");
+        } catch (NoSuchFieldException | SecurityException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+        f.setAccessible(true);
+        PATHARGUMENTS_FIELD = f;
+    }
+
     InstanceIdentifier(final Class<T> type, final Iterable<PathArgument> pathArguments, final boolean wildcarded, final int hash) {
         this.pathArguments = Preconditions.checkNotNull(pathArguments);
         this.targetType = Preconditions.checkNotNull(type);
@@ -653,16 +665,26 @@ public class InstanceIdentifier<T extends DataObject> implements Path<InstanceId
     }
 
     private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
-        out.writeObject(targetType);
-        out.writeBoolean(wildcarded);
-        out.writeInt(hash);
-        out.write(Iterables.size(pathArguments));
+        out.defaultWriteObject();
+        out.writeInt(Iterables.size(pathArguments));
         for (Object o : pathArguments) {
             out.writeObject(o);
         }
     }
 
     private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
-        // TODO Auto-generated method stub
+        in.defaultReadObject();
+
+        final int size = in.readInt();
+        final List<PathArgument> args = new ArrayList<>(size);
+        for (int i = 0; i < size; ++i) {
+            args.add((PathArgument) in.readObject());
+        }
+
+        try {
+            PATHARGUMENTS_FIELD.set(this, ImmutableList.copyOf(args));
+        } catch (IllegalArgumentException | IllegalAccessException e) {
+            throw new IOException(e);
+        }
     }
 }
index 2916b88e8bfde2898622e3076b151f5e0b8421c5..dfd623fa84021d0452266c6941034eeabe629b70 100644 (file)
@@ -13,8 +13,12 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.lang.reflect.Array;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -72,14 +76,27 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
     private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
             AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
     private static final YangInstanceIdentifier EMPTY = trustedCreate(Collections.<PathArgument>emptyList());
+    private static final Field PATHARGUMENTS_FIELD;
 
-    private static final long serialVersionUID = 2L;
-    private final Iterable<PathArgument> pathArguments;
+    private static final long serialVersionUID = 3L;
+    private transient final Iterable<PathArgument> pathArguments;
     private final int hash;
 
-    private transient volatile ImmutableList<PathArgument> legacyPath = null;
+    private volatile ImmutableList<PathArgument> legacyPath = null;
     private transient volatile String toStringCache = null;
 
+    static {
+        final Field f;
+        try {
+            f = YangInstanceIdentifier.class.getDeclaredField("pathArguments");
+        } catch (NoSuchFieldException | SecurityException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+        f.setAccessible(true);
+
+        PATHARGUMENTS_FIELD = f;
+    }
+
     private final ImmutableList<PathArgument> getLegacyPath() {
         // Temporary variable saves a volatile read
         ImmutableList<PathArgument> ret = legacyPath;
@@ -802,4 +819,26 @@ public final class YangInstanceIdentifier implements Path<YangInstanceIdentifier
         }
         return ret;
     }
+
+    private void readObject(final ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+        inputStream.defaultReadObject();
+
+        try {
+            PATHARGUMENTS_FIELD.set(this, legacyPath);
+        } catch (IllegalArgumentException | IllegalAccessException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void writeObject(final ObjectOutputStream outputStream) throws IOException {
+        /*
+         * This may look strange, but what we are doing here is side-stepping the fact
+         * that pathArguments is not generally serializable. We are forcing instantiation
+         * of the legacy path, which is an ImmutableList (thus Serializable) and write
+         * it out. The read path does the opposite -- it reads the legacyPath and then
+         * uses invocation API to set the field.
+         */
+        getLegacyPath();
+        outputStream.defaultWriteObject();
+    }
 }
index 3cc626389c705b1186d8ffab111133d4fe849b32..d451d6e86166f359e8f79008f07318eb1d1957d3 100644 (file)
@@ -10,17 +10,19 @@ package org.opendaylight.yangtools.yang.data.api;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map.Entry;
-
 import org.junit.Test;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
@@ -301,4 +303,24 @@ public class InstanceIdentifierTest {
 
         assertNotNull( node1.toString() ); // for code coverage
     }
+
+    @Test
+    public void serializationTest() throws IOException, ClassNotFoundException {
+        final YangInstanceIdentifier expected = YangInstanceIdentifier.create(new NodeIdentifier(nodeName1), new NodeIdentifier(nodeName2));
+
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        final ObjectOutputStream oos = new ObjectOutputStream(bos);
+        oos.writeObject(expected);
+        oos.close();
+
+        final byte[] bytes = bos.toByteArray();
+        final ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+        final ObjectInputStream ois = new ObjectInputStream(bis);
+
+        final YangInstanceIdentifier read = (YangInstanceIdentifier) ois.readObject();
+        assertEquals(0, ois.available());
+        ois.close();
+
+        assertEquals(expected, read);
+    }
 }