BUG-7833: Fix identityref codecs 70/53870/7
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 26 Mar 2017 19:38:00 +0000 (21:38 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 4 Apr 2017 15:59:51 +0000 (17:59 +0200)
Introduce QNameCodecUtil to help JSON and XML codecs correctly implement
their identityref codecs. As it turns out, both only need to provide
proper mapping functions without having to deal with multiple codec
hierarchies.

The patch is also more strict about parsing the string, as it rejects
malformed identityrefs, i.e. those which contain invalid characters.

This fixes BUG-7833, which is triggered by invalid PUT, where we parse
an empty string as a null identityref.

Change-Id: I1f3086a0f9e930f7782dff8d6c39329d4b28ed14
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/IdentityrefJSONCodec.java [new file with mode: 0644]
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringIdentityrefCodec.java [deleted file]
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/codec/QNameCodecUtil.java [new file with mode: 0644]

diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/IdentityrefJSONCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/IdentityrefJSONCodec.java
new file mode 100644 (file)
index 0000000..50379d3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.yang.data.codec.gson;
+
+import com.google.common.base.Preconditions;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.util.codec.QNameCodecUtil;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+final class IdentityrefJSONCodec implements JSONCodec<QName> {
+    private final SchemaContext schemaContext;
+    private final QNameModule parentModule;
+
+    IdentityrefJSONCodec(final SchemaContext context, final QNameModule parentModule) {
+        this.schemaContext = Preconditions.checkNotNull(context);
+        this.parentModule = Preconditions.checkNotNull(parentModule);
+    }
+
+    @Override
+    public Class<QName> getDataType() {
+        return QName.class;
+    }
+
+    @Override
+    public QName parseValue(final Object ctx, final String value) {
+        return QNameCodecUtil.decodeQName(value, prefix -> {
+            if (prefix.isEmpty()) {
+                return parentModule;
+            }
+
+            final Module module = schemaContext.findModuleByName(prefix, null);
+            Preconditions.checkArgument(module != null, "Could not find module %s", prefix);
+            return module.getQNameModule();
+        });
+    }
+
+    /**
+     * Serialize QName with specified JsonWriter.
+     *
+     * @param writer JsonWriter
+     * @param value QName
+     */
+    @Override
+    public void writeValue(final JsonWriter writer, final QName value) throws IOException {
+        final String str = QNameCodecUtil.encodeQName(value, uri -> {
+            final Module module = schemaContext.findModuleByNamespaceAndRevision(uri.getNamespace(), null);
+            Preconditions.checkArgument(module != null, "Cannot find module for %s", uri);
+            return module.getName();
+        });
+        writer.value(str);
+    }
+}
index 31b860d1e91b401a8d3ab0b015e0b59c9844043c..c24db7699414d8d762b6e4a78e70d77547e7df5e 100644 (file)
@@ -241,7 +241,7 @@ public final class JSONCodecFactory extends AbstractCodecFactory<JSONCodec<?>> {
 
     @Override
     protected JSONCodec<?> identityRefCodec(final IdentityrefTypeDefinition type, final QNameModule module) {
 
     @Override
     protected JSONCodec<?> identityRefCodec(final IdentityrefTypeDefinition type, final QNameModule module) {
-        return new JSONStringIdentityrefCodec(getSchemaContext(), module);
+        return new IdentityrefJSONCodec(getSchemaContext(), module);
     }
 
     @Override
     }
 
     @Override
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringIdentityrefCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringIdentityrefCodec.java
deleted file mode 100644 (file)
index c8ecc9e..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. 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.yang.data.codec.gson;
-
-import com.google.gson.stream.JsonWriter;
-import java.io.IOException;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.util.ModuleStringIdentityrefCodec;
-import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-
-final class JSONStringIdentityrefCodec extends ModuleStringIdentityrefCodec implements JSONCodec<QName> {
-    JSONStringIdentityrefCodec(final SchemaContext context, final QNameModule parentModule) {
-        super(context, parentModule);
-    }
-
-    @Override
-    protected Module moduleForPrefix(@Nonnull final String prefix) {
-        if (prefix.isEmpty()) {
-            return context.findModuleByNamespaceAndRevision(parentModuleQname.getNamespace(),
-                    parentModuleQname.getRevision());
-        }
-
-        return context.findModuleByName(prefix, null);
-    }
-
-    @Override
-    public Class<QName> getDataType() {
-        return QName.class;
-    }
-
-    @Override
-    public QName parseValue(final Object ctx, final String str) {
-        return deserialize(str);
-    }
-
-    @Override
-    public void writeValue(final JsonWriter ctx, final QName value) throws IOException {
-        ctx.value(serialize(value));
-    }
-}
index fbd99587cdef94a1452d29c85a73e2fac0f613e5..378fef581a3af2e5cda0de284ef230ec824e7047 100644 (file)
@@ -8,35 +8,25 @@
 
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
 
 package org.opendaylight.yangtools.yang.data.codec.xml;
 
+import com.google.common.base.Preconditions;
 import java.net.URI;
 import java.net.URI;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import javax.annotation.Nonnull;
+import java.util.Map.Entry;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.util.ModuleStringIdentityrefCodec;
+import org.opendaylight.yangtools.yang.data.util.codec.QNameCodecUtil;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-final class IdentityrefXmlCodec extends ModuleStringIdentityrefCodec implements XmlCodec<QName> {
-    private static final ThreadLocal<Deque<NamespaceContext>> TL_NSCONTEXT = new ThreadLocal<>();
+final class IdentityrefXmlCodec implements XmlCodec<QName> {
+    private final SchemaContext schemaContext;
+    private final QNameModule parentModule;
 
     IdentityrefXmlCodec(final SchemaContext context, final QNameModule parentModule) {
 
     IdentityrefXmlCodec(final SchemaContext context, final QNameModule parentModule) {
-        super(context, parentModule);
-    }
-
-    @Override
-    protected Module moduleForPrefix(@Nonnull final String prefix) {
-        if (prefix.isEmpty()) {
-            return context.findModuleByNamespaceAndRevision(parentModuleQname.getNamespace(),
-                    parentModuleQname.getRevision());
-        }
-
-        final String prefixedNS = getNamespaceContext().getNamespaceURI(prefix);
-        return context.findModuleByNamespaceAndRevision(URI.create(prefixedNS), null);
+        this.schemaContext = Preconditions.checkNotNull(context);
+        this.parentModule = Preconditions.checkNotNull(parentModule);
     }
 
     @Override
     }
 
     @Override
@@ -46,38 +36,26 @@ final class IdentityrefXmlCodec extends ModuleStringIdentityrefCodec implements
 
     @Override
     public QName parseValue(final NamespaceContext ctx, final String str) {
 
     @Override
     public QName parseValue(final NamespaceContext ctx, final String str) {
-        pushNamespaceContext(ctx);
-        try {
-            return deserialize(str);
-        } finally {
-            popNamespaceContext();
-        }
+        return QNameCodecUtil.decodeQName(str, prefix -> {
+            if (prefix.isEmpty()) {
+                return parentModule;
+            }
+
+            final String prefixedNS = ctx.getNamespaceURI(prefix);
+            final Module module = schemaContext.findModuleByNamespaceAndRevision(URI.create(prefixedNS), null);
+            Preconditions.checkArgument(module != null, "Could not find module for namespace %s", prefixedNS);
+            return module.getQNameModule();
+        });
     }
 
     @Override
     public void writeValue(final XMLStreamWriter ctx, final QName value) throws XMLStreamException {
     }
 
     @Override
     public void writeValue(final XMLStreamWriter ctx, final QName value) throws XMLStreamException {
-        // FIXME: this does not work correctly, as we need to populate entries into the namespace context
-        ctx.writeCharacters(serialize(value));
-    }
-
-    private static NamespaceContext getNamespaceContext() {
-        return TL_NSCONTEXT.get().getFirst();
-    }
-
-    private static void popNamespaceContext() {
-        final Deque<NamespaceContext> stack = TL_NSCONTEXT.get();
-        stack.pop();
-        if (stack.isEmpty()) {
-            TL_NSCONTEXT.set(null);
-        }
-    }
+        final RandomPrefix prefixes = new RandomPrefix(ctx.getNamespaceContext());
+        final String str = QNameCodecUtil.encodeQName(value, uri -> prefixes.encodePrefix(uri.getNamespace()));
 
 
-    private static void pushNamespaceContext(final NamespaceContext context) {
-        Deque<NamespaceContext> stack = TL_NSCONTEXT.get();
-        if (stack == null) {
-            stack = new ArrayDeque<>(1);
-            TL_NSCONTEXT.set(stack);
+        for (Entry<URI, String> e : prefixes.getPrefixes()) {
+            ctx.writeNamespace(e.getValue(), e.getKey().toString());
         }
         }
-        stack.push(context);
+        ctx.writeCharacters(str);
     }
 }
     }
 }
diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/codec/QNameCodecUtil.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/codec/QNameCodecUtil.java
new file mode 100644 (file)
index 0000000..a3d449c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.yang.data.util.codec;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import java.util.Iterator;
+import java.util.function.Function;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+
+/**
+ * Utility methods for parsing and writing QNames.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class QNameCodecUtil {
+    private static final Splitter COLON_SPLITTER = Splitter.on(':').trimResults();
+
+    public static QName decodeQName(final String str, final Function<String, QNameModule> prefixToModule) {
+        final Iterator<String> it = COLON_SPLITTER.split(str).iterator();
+
+        // Empty string
+        final String identifier;
+        final String prefix;
+        if (it.hasNext()) {
+            final String first = it.next();
+            if (it.hasNext()) {
+                // It is "prefix:value"
+                prefix = first;
+                identifier = it.next();
+                Preconditions.checkArgument(!it.hasNext(), "Malformed QName '" + str + "'");
+            } else {
+                prefix = "";
+                identifier = first;
+            }
+        } else {
+            prefix = "";
+            identifier = "";
+        }
+
+        final QNameModule module = prefixToModule.apply(prefix);
+        Preconditions.checkArgument(module != null, "Cannot resolve prefix '%s' from %s", prefix, str);
+        return QName.create(module, identifier);
+    }
+
+    public static String encodeQName(final QName qname, final Function<QNameModule, String> moduleToPrefix) {
+        final String prefix = moduleToPrefix.apply(qname.getModule());
+        Preconditions.checkArgument(prefix != null, "Cannot allocated prefix for %s", qname);
+        return prefix.isEmpty() ? qname.getLocalName() : prefix + ":" + qname.getLocalName();
+    }
+}