Correct root vs. nested writeout
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / get / AbstractGet.java
index 7bf710f37076e609b47cdd92b50288d6c8c08b40..c323069e12b3dee5ca98e74264240a40cdb534af 100644 (file)
  * 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.mdsal.connector.ops.get;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
 import java.io.IOException;
+import java.util.Optional;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import javax.xml.transform.dom.DOMResult;
-import org.opendaylight.controller.config.util.xml.DocumentedException;
-import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
-import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
-import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
-import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.netconf.api.DocumentedException;
+import org.opendaylight.netconf.api.xml.XmlElement;
 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
 import org.opendaylight.netconf.mdsal.connector.ops.Datastore;
 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
-import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
-import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaPath;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.YangInstanceIdentifierWriter;
+import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
+// FIXME: seal when we have JDK17+
 public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY;
+    private static final String FILTER = "filter";
 
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractGet.class);
+    static {
+        XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
+        XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+    }
 
-    protected static final String FILTER = "filter";
-    static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
+    // FIXME: hide this field
     protected final CurrentSchemaContext schemaContext;
     private final FilterContentValidator validator;
 
-    public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
+    // FIXME: package-private
+    protected AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
         super(netconfSessionIdForReporting);
         this.schemaContext = schemaContext;
-        this.validator = new FilterContentValidator(schemaContext);
+        validator = new FilterContentValidator(schemaContext);
     }
 
-    private static final XMLOutputFactory XML_OUTPUT_FACTORY;
-
-    static {
-        XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
-        XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
-    }
-
-    protected Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data, final YangInstanceIdentifier dataRoot) {
-
+    // FIXME: hide this method
+    // FIXME: throw a DocumentedException
+    protected Node transformNormalizedNode(final Document document, final NormalizedNode data,
+                                           final YangInstanceIdentifier dataRoot) {
         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
-
         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
+        final EffectiveModelContext currentContext = schemaContext.getCurrentContext();
 
         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
-                schemaContext.getCurrentContext(), getSchemaPath(dataRoot));
+            currentContext);
 
-        final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
+        try {
+            if (dataRoot.isEmpty()) {
+                writeRoot(nnStreamWriter, data);
+            } else {
+                write(nnStreamWriter, currentContext, dataRoot.coerceParent(), data);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
 
-        writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
         return result.getNode();
     }
 
-
-    private XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
-        try {
-            return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
-        } catch (final XMLStreamException e) {
-            throw new RuntimeException(e);
+    private static void write(final NormalizedNodeStreamWriter nnStreamWriter,
+            final EffectiveModelContext currentContext, final YangInstanceIdentifier parent, final NormalizedNode data)
+                throws IOException {
+        try (var yiidWriter = YangInstanceIdentifierWriter.open(nnStreamWriter, currentContext, parent)) {
+            try (var nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true)) {
+                nnWriter.write(data);
+            }
         }
     }
 
-    private static final Function<PathArgument, QName> PATH_ARG_TO_QNAME = new Function<YangInstanceIdentifier.PathArgument, QName>() {
-        @Override
-        public QName apply(final YangInstanceIdentifier.PathArgument input) {
-            return input.getNodeType();
-        }
-    };
+    private static void writeRoot(final NormalizedNodeStreamWriter nnStreamWriter, final NormalizedNode data)
+            throws IOException {
+        checkArgument(data instanceof ContainerNode, "Unexpected root data %s", data);
 
-    private SchemaPath getSchemaPath(final YangInstanceIdentifier dataRoot) {
-        return SchemaPath.create(Iterables.transform(dataRoot.getPathArguments(), PATH_ARG_TO_QNAME), dataRoot.equals(ROOT));
+        try (var nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true)) {
+            for (var child : ((ContainerNode) data).body()) {
+                nnWriter.write(child);
+            }
+        }
     }
 
-    private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
+    private static XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
         try {
-            if (data.getNodeType().equals(SchemaContext.NAME)) {
-                for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
-                    nnWriter.write(child);
-                }
-            } else {
-                nnWriter.write(data);
-            }
-            nnWriter.flush();
-            xmlWriter.flush();
-        } catch (XMLStreamException | IOException e) {
-            Throwables.propagate(e);
+            return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
+        } catch (final XMLStreamException e) {
+            throw new RuntimeException(e);
         }
     }
 
-    protected Element serializeNodeWithParentStructure(Document document, YangInstanceIdentifier dataRoot, NormalizedNode node) {
-        if (!dataRoot.equals(ROOT)) {
-            return (Element) transformNormalizedNode(document,
-                    ImmutableNodes.fromInstanceId(schemaContext.getCurrentContext(), dataRoot, node),
-                    ROOT);
-        }
-        return  (Element) transformNormalizedNode(document, node, ROOT);
+    protected Element serializeNodeWithParentStructure(final Document document, final YangInstanceIdentifier dataRoot,
+                                                       final NormalizedNode node) {
+        return (Element) transformNormalizedNode(document, node, dataRoot);
     }
 
     /**
+     * Obtain data root according to filter from operation element.
      *
      * @param operationElement operation element
-     * @return if Filter is present and not empty returns Optional of the InstanceIdentifier to the read location in datastore.
-     *          empty filter returns Optional.absent() which should equal an empty &lt;data/&gt; container in the response.
-     *         if filter is not present we want to read the entire datastore - return ROOT.
-     * @throws DocumentedException
+     * @return if filter is present and not empty returns Optional of the InstanceIdentifier to the read location
+     *      in datastore. Empty filter returns Optional.absent() which should equal an empty &lt;data/&gt;
+     *      container in the response. If filter is not present we want to read the entire datastore - return ROOT.
+     * @throws DocumentedException if not possible to get identifier from filter
      */
-    protected Optional<YangInstanceIdentifier> getDataRootFromFilter(XmlElement operationElement) throws DocumentedException {
-        Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
+    protected Optional<YangInstanceIdentifier> getDataRootFromFilter(final XmlElement operationElement)
+            throws DocumentedException {
+        final Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
         if (filterElement.isPresent()) {
             if (filterElement.get().getChildElements().size() == 0) {
-                return Optional.absent();
+                return Optional.empty();
             }
             return Optional.of(getInstanceIdentifierFromFilter(filterElement.get()));
-        } else {
-            return Optional.of(ROOT);
         }
+
+        return Optional.of(YangInstanceIdentifier.empty());
     }
 
     @VisibleForTesting
-    protected YangInstanceIdentifier getInstanceIdentifierFromFilter(XmlElement filterElement) throws DocumentedException {
+    protected YangInstanceIdentifier getInstanceIdentifierFromFilter(final XmlElement filterElement)
+            throws DocumentedException {
 
         if (filterElement.getChildElements().size() != 1) {
             throw new DocumentedException("Multiple filter roots not supported yet",
-                    ErrorType.application, ErrorTag.operation_not_supported, ErrorSeverity.error);
+                    ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
         }
 
-        XmlElement element = filterElement.getOnlyChildElement();
+        final XmlElement element = filterElement.getOnlyChildElement();
         return validator.validate(element);
     }
 
     protected static final class GetConfigExecution {
-
         private final Optional<Datastore> datastore;
-        public GetConfigExecution(final Optional<Datastore> datastore) {
-            this.datastore = datastore;
-        }
 
-        public Optional<Datastore> getDatastore() {
-            return datastore;
+        GetConfigExecution(final Optional<Datastore> datastore) {
+            this.datastore = datastore;
         }
 
         static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws DocumentedException {
             try {
                 validateInputRpc(xml, operationName);
             } catch (final DocumentedException e) {
-                throw new DocumentedException("Incorrect RPC: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
+                throw new DocumentedException("Incorrect RPC: " + e.getMessage(), e, e.getErrorType(), e.getErrorTag(),
+                        e.getErrorSeverity(), e.getErrorInfo());
             }
 
             final Optional<Datastore> sourceDatastore;
             try {
                 sourceDatastore = parseSource(xml);
             } catch (final DocumentedException e) {
-                throw new DocumentedException("Get-config source attribute error: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
+                throw new DocumentedException("Get-config source attribute error: " + e.getMessage(), e,
+                        e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
             }
 
             return new GetConfigExecution(sourceDatastore);
@@ -190,16 +180,19 @@ public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
         private static Optional<Datastore> parseSource(final XmlElement xml) throws DocumentedException {
             final Optional<XmlElement> sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY,
                     XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
-
-            return  sourceElement.isPresent() ?
-                    Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName())) : Optional.<Datastore>absent();
+            return sourceElement.isPresent()
+                    ? Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName()))
+                    : Optional.empty();
         }
 
-        private static void validateInputRpc(final XmlElement xml, String operationName) throws DocumentedException{
+        private static void validateInputRpc(final XmlElement xml, final String operationName) throws
+                DocumentedException {
             xml.checkName(operationName);
             xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
         }
 
+        public Optional<Datastore> getDatastore() {
+            return datastore;
+        }
     }
-
 }