BUG-1431: make sure (Yang)InstanceIdentifier is Serializable 34/11134/3
authorRobert Varga <rovarga@cisco.com>
Sat, 13 Sep 2014 14:25:51 +0000 (16:25 +0200)
committerRobert Varga <rovarga@cisco.com>
Mon, 15 Sep 2014 09:31:06 +0000 (11:31 +0200)
This works around the fact that an Iterable cannot be serialized by
forcing instantiation of the legacy path. The read side then just does
the opposite.

For InstanceIdentifier the situation is similar, except we perform the
writeout/read-in ourselves.

Bumps the serialization format for InstanceIdentifier and
YangInstanceIdentifier to 2 and 3 respectively.

Change-Id: I668e2aeebde20b99cfe3b41594b3925697295d05
Signed-off-by: Robert Varga <rovarga@cisco.com>
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);
+    }
 }