Bug 6006 - UnionTypeCodec fails to handle indentityref 56/39856/4
authorIsaku Yamahata <isaku.yamahata@intel.com>
Thu, 2 Jun 2016 00:40:34 +0000 (17:40 -0700)
committerRobert Varga <nite@hq.sk>
Wed, 8 Jun 2016 12:22:57 +0000 (12:22 +0000)
UnionTypeCodec doesn't know that identityref requires special handling
because the actual derived identityref classes are determined at runtime.

Change-Id: I817190a380f0325b0bb8ee6f13a7d94cffc29113
Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com>
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/BindingCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/yangtools/binding/data/codec/impl/UnionTypeCodec.java
binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/UnionTypeWithIdentityrefTest.java [new file with mode: 0644]
binding/mdsal-binding-test-model/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/mdsal/test/bug/_6006/rev160607/UnionTypeBuilder.java [new file with mode: 0644]
binding/mdsal-binding-test-model/src/main/yang/opendaylight-bug-6006.yang [new file with mode: 0644]

index 0452a7dabb40207a821fb64b7552423858e0210e..468f69cec9e4bcc4b0c47cd347de7a763ca1ea1a 100644 (file)
@@ -287,6 +287,10 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
         } else {
             throw new IllegalArgumentException("Unsupported leaf node type " + schema.getClass());
         }
+        return getCodec(valueType, instantiatedType);
+    }
+
+    Codec<Object, Object> getCodec(final Class<?> valueType, final TypeDefinition<?> instantiatedType) {
         if (Class.class.equals(valueType)) {
             @SuppressWarnings({ "unchecked", "rawtypes" })
             final Codec<Object, Object> casted = (Codec) identityCodec;
@@ -300,12 +304,12 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
                 return ValueTypeCodec.EMPTY_CODEC;
             }
         } else if (BindingReflections.isBindingClass(valueType)) {
-                            return getCodec(valueType, instantiatedType);
+            return getCodecForBindingClass(valueType, instantiatedType);
         }
         return ValueTypeCodec.NOOP_CODEC;
     }
 
-    private Codec<Object, Object> getCodec(final Class<?> valueType, final TypeDefinition<?> instantiatedType) {
+    private Codec<Object, Object> getCodecForBindingClass(final Class<?> valueType, final TypeDefinition<?> instantiatedType) {
         @SuppressWarnings("rawtypes")
         TypeDefinition rootType = instantiatedType;
         while (rootType.getBaseType() != null) {
@@ -316,7 +320,7 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree
         } else if (rootType instanceof InstanceIdentifierTypeDefinition) {
             return ValueTypeCodec.encapsulatedValueCodecFor(valueType, instanceIdentifierCodec);
         } else if (rootType instanceof UnionTypeDefinition) {
-            final Callable<UnionTypeCodec> loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) rootType);
+            final Callable<UnionTypeCodec> loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) rootType, this);
             try {
                 return loader.call();
             } catch (final Exception e) {
index c483fe7f25cfa169b32e768dd718bdca9d6ec7fb..19f79fa2d120237df3e5abea2b119d11b30bf6ac 100644 (file)
@@ -9,12 +9,15 @@ package org.opendaylight.yangtools.binding.data.codec.impl;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.BaseEncoding;
+import com.google.common.util.concurrent.ExecutionError;
+import com.google.common.util.concurrent.UncheckedExecutionException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import javax.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.binding.BindingMapping;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
@@ -22,11 +25,14 @@ import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
 
 final class UnionTypeCodec extends ReflectionBasedCodec {
 
+    private final Codec<Object, Object> identityrefCodec;
     private final ImmutableSet<UnionValueOptionContext> typeCodecs;
     private final Constructor<?> charConstructor;
 
-    private UnionTypeCodec(final Class<?> unionCls,final Set<UnionValueOptionContext> codecs) {
+    private UnionTypeCodec(final Class<?> unionCls,final Set<UnionValueOptionContext> codecs,
+                           @Nullable Codec<Object, Object> identityrefCodec) {
         super(unionCls);
+        this.identityrefCodec = identityrefCodec;
         try {
             charConstructor = unionCls.getConstructor(char[].class);
             typeCodecs = ImmutableSet.copyOf(codecs);
@@ -35,37 +41,43 @@ final class UnionTypeCodec extends ReflectionBasedCodec {
         }
     }
 
-    static Callable<UnionTypeCodec> loader(final Class<?> unionCls, final UnionTypeDefinition unionType) {
+    static Callable<UnionTypeCodec> loader(final Class<?> unionCls, final UnionTypeDefinition unionType,
+                                           BindingCodecContext bindingCodecContext) {
         return new Callable<UnionTypeCodec>() {
             @Override
             public UnionTypeCodec call() throws NoSuchMethodException, SecurityException {
+                Codec<Object, Object> identityrefCodec = null;
                 Set<UnionValueOptionContext> values = new HashSet<>();
-                for(TypeDefinition<?> subtype : unionType.getTypes()) {
+                for (TypeDefinition<?> subtype : unionType.getTypes()) {
                     String methodName = "get" + BindingMapping.getClassName(subtype.getQName());
                     Method valueGetter = unionCls.getMethod(methodName);
                     Class<?> valueType = valueGetter.getReturnType();
-                    Codec<Object, Object> valueCodec = UnionTypeCodec.getCodecForType(valueType, subtype);
+                    Codec<Object, Object> valueCodec = bindingCodecContext.getCodec(valueType, subtype);
+                    if (Class.class.equals(valueType)) {
+                        identityrefCodec = valueCodec;
+                    }
                     values.add(new UnionValueOptionContext(valueType,valueGetter, valueCodec));
                 }
-                return new UnionTypeCodec(unionCls, values);
+                return new UnionTypeCodec(unionCls, values, identityrefCodec);
             }
         };
     }
 
-    private static Codec<Object, Object> getCodecForType(final Class<?> valueType, final TypeDefinition<?> subtype) {
-        if (subtype.getBaseType() instanceof UnionTypeDefinition) {
+    @Override
+    public Object deserialize(final Object input) {
+        if (identityrefCodec != null) {
             try {
-                return UnionTypeCodec.loader(valueType, (UnionTypeDefinition) subtype.getBaseType()).call();
-            } catch (final Exception e) {
-                throw new IllegalStateException("Could not construct Union Type Codec");
+                Object identityref = identityrefCodec.deserialize(input);
+                return typeClass.getConstructor(Class.class).newInstance(identityref);
+            } catch (UncheckedExecutionException | ExecutionError e) {
+                // ignore this exception caused by deserialize()
+            } catch (NoSuchMethodException e) {
+                // caused by getContructor(). this case shouldn't happen.
+                throw new IllegalStateException("Could not construct instance", e);
+            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+                // ignore this exception caused by newInstance()
             }
-        } else {
-            return ValueTypeCodec.getCodecFor(valueType, subtype);
         }
-    }
-
-    @Override
-    public Object deserialize(final Object input) {
         try {
             if (input instanceof byte[]) {
                 return charConstructor.newInstance(BaseEncoding.base64().encode((byte[]) input).toCharArray());
diff --git a/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/UnionTypeWithIdentityrefTest.java b/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/yangtools/binding/data/codec/test/UnionTypeWithIdentityrefTest.java
new file mode 100644 (file)
index 0000000..d69c183
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2016 Intel Corporation and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yangtools.binding.data.codec.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map.Entry;
+import javassist.ClassPool;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.OpendaylightBug6006Data;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.UnionNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.UnionNodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.UnionType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.UnionTypeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.IdentBase;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.IdentOne;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.IdentTwo;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+
+public class UnionTypeWithIdentityrefTest extends AbstractBindingRuntimeTest {
+    private static final String identOneString = "IdentOne";
+    public static final QName NODE_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:mdsal:test:bug:6066", "2016-06-07", "union-node");
+    public static final QName NODE_LEAF_QNAME = QName.create(NODE_QNAME, "value");
+
+    private BindingNormalizedNodeCodecRegistry registry;
+
+    @Override
+    public void setup() {
+        super.setup();
+        JavassistUtils utils = JavassistUtils.forClassPool(ClassPool.getDefault());
+        registry = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(utils));
+        registry.onBindingRuntimeContextUpdated(getRuntimeContext());
+    }
+
+    @Test
+    public void bug6006Test() {
+        UnionType unionType = UnionTypeBuilder.getDefaultInstance(identOneString);
+        UnionNode unionNode = new UnionNodeBuilder().setValue(unionType).build();
+        NormalizedNode<?, ?> normalizedUnionNode = registry
+            .toNormalizedNode(InstanceIdentifier.builder(UnionNode.class).build(), unionNode)
+            .getValue();
+
+        Entry<InstanceIdentifier<?>, DataObject> unionNodeEntry = registry.fromNormalizedNode(
+                YangInstanceIdentifier.of(normalizedUnionNode.getNodeType()), normalizedUnionNode);
+        DataObject unionNodeObj = unionNodeEntry.getValue();
+        assertTrue(unionNodeObj instanceof UnionNode);
+        UnionType unionTypeObj = ((UnionNode) unionNodeObj).getValue();
+        assertEquals(null, unionTypeObj.getUint8());
+        assertEquals(IdentOne.class, unionTypeObj.getIdentityref());
+    }
+}
diff --git a/binding/mdsal-binding-test-model/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/mdsal/test/bug/_6006/rev160607/UnionTypeBuilder.java b/binding/mdsal-binding-test-model/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/mdsal/test/bug/_6006/rev160607/UnionTypeBuilder.java
new file mode 100644 (file)
index 0000000..98105ac
--- /dev/null
@@ -0,0 +1,29 @@
+package org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.IdentBase;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.IdentOne;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.bug._6006.rev160607.IdentTwo;
+
+/**
+ * The purpose of generated class in src/main/java for Union types is to create new instances of unions from a string representation.
+ * In some cases it is very difficult to automate it since there can be unions such as (uint32 - uint16), or (string - uint32).
+ *
+ * The reason behind putting it under src/main/java is:
+ * This class is generated in form of a stub and needs to be finished by the user. This class is generated only once to prevent
+ * loss of user code.
+ *
+ */
+public class UnionTypeBuilder {
+
+    public static UnionType getDefaultInstance(java.lang.String defaultValue) {
+        Class<? extends IdentBase> identityref = null;
+        if (defaultValue.equals("IdentOne")) {
+            return new UnionType(IdentOne.class);
+        }
+        if (defaultValue.equals("IdentTwo")) {
+            return new UnionType(IdentTwo.class);
+        }
+        throw new IllegalArgumentException("Unknown UnionType string " + defaultValue);
+    }
+
+}
diff --git a/binding/mdsal-binding-test-model/src/main/yang/opendaylight-bug-6006.yang b/binding/mdsal-binding-test-model/src/main/yang/opendaylight-bug-6006.yang
new file mode 100644 (file)
index 0000000..df57a2f
--- /dev/null
@@ -0,0 +1,36 @@
+module opendaylight-bug-6006 {
+    namespace "urn:opendaylight:params:xml:ns:yang:mdsal:test:bug:6006";
+    prefix bug-6006-test;
+
+    description
+        "This module contains test case for bug 6006.
+         https://bugs.opendaylight.org/show_bug.cgi?id=6006";
+
+    revision "2016-06-07" {
+        description
+        "Test model for testing union data types.";
+    }
+
+    identity ident-base;
+    identity ident-one {
+        base ident-base;
+    }
+    identity ident-two {
+        base ident-base;
+    }
+
+    typedef union-type {
+        type union {
+            type uint8;
+            type identityref {
+                base ident-base;
+            }
+        }
+    }
+
+    container union-node {
+        leaf value {
+            type union-type;
+        }
+    }
+}