From 3fef6ae3770c27bc84c39477cef53f79415c400b Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 26 Mar 2017 21:38:00 +0200 Subject: [PATCH] BUG-7833: Fix identityref codecs 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 --- .../data/codec/gson/IdentityrefJSONCodec.java | 61 +++++++++++++++++ .../data/codec/gson/JSONCodecFactory.java | 2 +- .../gson/JSONStringIdentityrefCodec.java | 48 ------------- .../data/codec/xml/IdentityrefXmlCodec.java | 68 +++++++------------ .../yang/data/util/codec/QNameCodecUtil.java | 59 ++++++++++++++++ 5 files changed, 144 insertions(+), 94 deletions(-) create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/IdentityrefJSONCodec.java delete mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringIdentityrefCodec.java create mode 100644 yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/codec/QNameCodecUtil.java 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 index 0000000000..50379d3098 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/IdentityrefJSONCodec.java @@ -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 { + 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 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); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java index 31b860d1e9..c24db76994 100644 --- a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodecFactory.java @@ -241,7 +241,7 @@ public final class JSONCodecFactory extends AbstractCodecFactory> { @Override protected JSONCodec identityRefCodec(final IdentityrefTypeDefinition type, final QNameModule module) { - return new JSONStringIdentityrefCodec(getSchemaContext(), module); + return new IdentityrefJSONCodec(getSchemaContext(), module); } @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 index c8ecc9e34e..0000000000 --- a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONStringIdentityrefCodec.java +++ /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 { - 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 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)); - } -} diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java index fbd99587cd..378fef581a 100644 --- a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java +++ b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java @@ -8,35 +8,25 @@ package org.opendaylight.yangtools.yang.data.codec.xml; +import com.google.common.base.Preconditions; 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 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; -final class IdentityrefXmlCodec extends ModuleStringIdentityrefCodec implements XmlCodec { - private static final ThreadLocal> TL_NSCONTEXT = new ThreadLocal<>(); +final class IdentityrefXmlCodec implements XmlCodec { + private final SchemaContext schemaContext; + private 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 @@ -46,38 +36,26 @@ final class IdentityrefXmlCodec extends ModuleStringIdentityrefCodec implements @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 { - // 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 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 stack = TL_NSCONTEXT.get(); - if (stack == null) { - stack = new ArrayDeque<>(1); - TL_NSCONTEXT.set(stack); + for (Entry 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 index 0000000000..a3d449c423 --- /dev/null +++ b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/codec/QNameCodecUtil.java @@ -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 prefixToModule) { + final Iterator 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 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(); + } +} -- 2.36.6