Bind operation prefix to correct namespace
[netconf.git] / netconf / netconf-util / src / main / java / org / opendaylight / netconf / util / NetconfUtil.java
index c77679f04c06817b4dd9fee01078eb0b54a47d13..d5ade6f283a54d59731644a801e0b4664cbdefee 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.netconf.util;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.Iterator;
@@ -43,19 +44,60 @@ 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:
+     * <ul>
+     *   <li>OpenJDK DOM writer (com.sun.xml.internal.stream.writers.XMLDOMWriterImpl) throws
+     *       UnsupportedOperationException from its setNamespaceContext() method</li>
+     *   <li>Woodstox DOM writer (com.ctc.wstx.dom.WstxDOMWrappingWriter) works with namespace context, but treats
+     *       setPrefix() calls as hints -- which are not discoverable.</li>
+     * </ul>
+     *
+     * <p>
+     * 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);
 
     public static final QName NETCONF_QNAME =
             QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01", "netconf").intern();
     public static final QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data").intern();
-    public static final XMLOutputFactory XML_FACTORY;
 
-    private static final Logger LOG = LoggerFactory.getLogger(NetconfUtil.class);
+    public static final XMLOutputFactory XML_FACTORY;
 
     static {
         XML_FACTORY = XMLOutputFactory.newFactory();
         XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, false);
     }
 
+    private static final NamespaceSetter XML_NAMESPACE_SETTER = NamespaceSetter.forFactory(XML_FACTORY);
+
     private NetconfUtil() {
 
     }
@@ -107,6 +149,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);