From 197acb99bed8c585e26091eafb761481533718a0 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Thu, 17 Jan 2019 13:05:32 +0100 Subject: [PATCH] Bind operation prefix to correct namespace During NetconfUtil.writeNormalizedNode() we failed to set a prefix for the netconf operation leading to a warning. Make sure we set the correct NamespaceContext and fallback to setPrefix() in case the underlying DOM writer does not support setNamespaceContext(). JIRA: NETCONF-603 Change-Id: Icff5e045c4e901bb53990177d448a1ea43952c62 Signed-off-by: Robert Varga Signed-off-by: Tomas Cere --- .../netconf/util/AnyXmlNamespaceContext.java | 99 +++++++++++++++++++ .../netconf/util/NetconfUtil.java | 44 +++++++++ .../netconf/util/NetconfUtilTest.java | 2 +- 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/AnyXmlNamespaceContext.java diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/AnyXmlNamespaceContext.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/AnyXmlNamespaceContext.java new file mode 100644 index 0000000000..3cd7e730f8 --- /dev/null +++ b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/AnyXmlNamespaceContext.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, 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.netconf.util; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; +import static javax.xml.XMLConstants.XMLNS_ATTRIBUTE; +import static javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI; +import static javax.xml.XMLConstants.XML_NS_PREFIX; +import static javax.xml.XMLConstants.XML_NS_URI; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableListMultimap; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import javax.xml.namespace.NamespaceContext; +import org.opendaylight.yangtools.concepts.Immutable; + +final class AnyXmlNamespaceContext implements Immutable, NamespaceContext { + private static final ImmutableBiMap FIXED_PREFIX_TO_URI = ImmutableBiMap.of( + XML_NS_PREFIX, XML_NS_URI, + XMLNS_ATTRIBUTE, XMLNS_ATTRIBUTE_NS_URI); + + private final ImmutableListMultimap uriToPrefix; + private final ImmutableBiMap prefixToUri; + + AnyXmlNamespaceContext(final Map prefixToUri) { + final ImmutableListMultimap.Builder uriToPrefixBuilder = ImmutableListMultimap.builder(); + final BiMap prefixToUriBuilder = HashBiMap.create(); + + // Populate well-known prefixes + prefixToUriBuilder.putAll(FIXED_PREFIX_TO_URI); + uriToPrefixBuilder.putAll(FIXED_PREFIX_TO_URI.inverse().entrySet()); + + // Deal with default namespace first ... + final String defaultURI = prefixToUri.get(""); + if (defaultURI != null) { + checkMapping("", defaultURI); + uriToPrefixBuilder.put(defaultURI, ""); + prefixToUriBuilder.putIfAbsent("", defaultURI); + } + + // ... and then process all the rest + for (Entry entry : prefixToUri.entrySet()) { + final String prefix = requireNonNull(entry.getKey()); + if (!prefix.isEmpty()) { + final String namespaceURI = requireNonNull(entry.getValue()); + checkMapping(prefix, namespaceURI); + uriToPrefixBuilder.put(namespaceURI, prefix); + prefixToUriBuilder.putIfAbsent(prefix, namespaceURI); + } + } + + this.uriToPrefix = uriToPrefixBuilder.build(); + this.prefixToUri = ImmutableBiMap.copyOf(prefixToUriBuilder); + } + + @Override + public String getNamespaceURI(final String prefix) { + return getValue(prefixToUri, prefix, ""); + } + + @Override + public String getPrefix(final String namespaceURI) { + return getValue(prefixToUri.inverse(), namespaceURI, null); + } + + @Override + public Iterator getPrefixes(final String namespaceURI) { + checkArgument(namespaceURI != null); + return uriToPrefix.get(namespaceURI).iterator(); + } + + Collection> uriPrefixEntries() { + return uriToPrefix.entries(); + } + + private static void checkMapping(final String prefix, final String namespaceURI) { + checkArgument(!namespaceURI.isEmpty(), "Namespace must not be empty (%s)", prefix); + checkArgument(!FIXED_PREFIX_TO_URI.containsKey(prefix), "Cannot bind prefix %s", prefix); + checkArgument(!FIXED_PREFIX_TO_URI.containsValue(namespaceURI), "Cannot bind namespace %s", namespaceURI); + } + + private static String getValue(final ImmutableBiMap map, final String key, + final String defaultValue) { + checkArgument(key != null); + final String found; + return (found = map.get(key)) == null ? defaultValue : found; + } +} diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java index 3bf93f8d01..2bf0856092 100644 --- a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java +++ b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java @@ -9,6 +9,7 @@ package org.opendaylight.netconf.util; import static com.google.common.base.Preconditions.checkState; +import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.net.URISyntaxException; import java.util.Iterator; @@ -49,6 +50,45 @@ import org.w3c.dom.Document; import org.xml.sax.SAXException; public final class NetconfUtil { + /** + * Shim interface to handle differences around namespace handling between various XMLStreamWriter implementations. + * Specifically: + *
    + *
  • OpenJDK DOM writer (com.sun.xml.internal.stream.writers.XMLDOMWriterImpl) throws + * UnsupportedOperationException from its setNamespaceContext() method
  • + *
  • Woodstox DOM writer (com.ctc.wstx.dom.WstxDOMWrappingWriter) works with namespace context, but treats + * setPrefix() calls as hints -- which are not discoverable.
  • + *
+ * + *

+ * Due to this we perform a quick test for behavior and decide the appropriate strategy. + */ + @FunctionalInterface + private interface NamespaceSetter { + void initializeNamespace(XMLStreamWriter writer) throws XMLStreamException; + + static NamespaceSetter forFactory(final XMLOutputFactory xmlFactory) { + final String netconfNamespace = NETCONF_QNAME.getNamespace().toString(); + final AnyXmlNamespaceContext namespaceContext = new AnyXmlNamespaceContext(ImmutableMap.of( + "op", netconfNamespace)); + + try { + final XMLStreamWriter testWriter = xmlFactory.createXMLStreamWriter(new DOMResult( + XmlUtil.newDocument())); + testWriter.setNamespaceContext(namespaceContext); + } catch (final UnsupportedOperationException e) { + // This happens with JDK's DOM writer, which we may be using + LOG.warn("Unable to set namespace context, falling back to setPrefix()", e); + return writer -> writer.setPrefix("op", netconfNamespace); + } catch (XMLStreamException e) { + throw new ExceptionInInitializerError(e); + } + + // Success, we can use setNamespaceContext() + return writer -> writer.setNamespaceContext(namespaceContext); + } + } + private static final Logger LOG = LoggerFactory.getLogger(NetconfUtil.class); // FIXME: document what exactly this QName means, as it is not referring to a tangible node nor the ietf-module. @@ -60,6 +100,7 @@ public final class NetconfUtil { Revision.of("2011-06-01")), "netconf").intern(); // FIXME: is this the device-bound revision? public static final QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data").intern(); + public static final XMLOutputFactory XML_FACTORY; static { @@ -69,6 +110,8 @@ public final class NetconfUtil { XML_FACTORY = f; } + private static final NamespaceSetter XML_NAMESPACE_SETTER = NamespaceSetter.forFactory(XML_FACTORY); + private NetconfUtil() { // No-op } @@ -123,6 +166,7 @@ public final class NetconfUtil { } final XMLStreamWriter writer = XML_FACTORY.createXMLStreamWriter(result); + XML_NAMESPACE_SETTER.initializeNamespace(writer); try ( NormalizedNodeStreamWriter normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, context, schemaPath); diff --git a/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/NetconfUtilTest.java b/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/NetconfUtilTest.java index d2b3e17a47..16842040a5 100644 --- a/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/NetconfUtilTest.java +++ b/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/NetconfUtilTest.java @@ -77,8 +77,8 @@ public class NetconfUtilTest { final DOMResult result = new DOMResult(XmlUtil.newDocument()); final SchemaPath path = SchemaPath.create(true, NetconfState.QNAME); NetconfUtil.writeNormalizedNode(sessions, result, path, context); - final Document expected = XmlUtil.readXmlToDocument(getClass().getResourceAsStream("/sessions.xml")); final Document actual = (Document) result.getNode(); + final Document expected = XmlUtil.readXmlToDocument(getClass().getResourceAsStream("/sessions.xml")); final Diff diff = XMLUnit.compareXML(expected, actual); Assert.assertTrue(diff.toString(), diff.similar()); } -- 2.36.6