BUG 2743 - Added support for runtime RPC's to netconf mdsal northbound.
[controller.git] / opendaylight / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / controller / netconf / mdsal / connector / ops / get / AbstractGet.java
index 8f6ff417d6499d47dc594bc5ab5fb2de03e937d0..9a66ceb5bcd0d8dc66fc77cfa2f215a37e77d223 100644 (file)
@@ -8,42 +8,62 @@
 
 package org.opendaylight.controller.netconf.mdsal.connector.ops.get;
 
+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 com.google.common.collect.Lists;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
 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.netconf.api.NetconfDocumentedException;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag;
+import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType;
 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
 import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext;
 import org.opendaylight.controller.netconf.mdsal.connector.ops.Datastore;
-import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation;
+import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation;
 import org.opendaylight.controller.netconf.util.xml.XmlElement;
+import org.opendaylight.controller.sal.connect.netconf.util.InstanceIdToNodes;
 import org.opendaylight.yangtools.yang.common.QName;
 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.DataContainerNode;
 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.transform.dom.DomUtils;
+import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
 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.w3c.dom.Document;
+import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
-public abstract class AbstractGet extends AbstractLastNetconfOperation {
+public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
 
-    protected static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractGet.class);
 
+    protected static final String FILTER = "filter";
+    static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
     protected final CurrentSchemaContext schemaContext;
 
-
     public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
         super(netconfSessionIdForReporting);
         this.schemaContext = schemaContext;
@@ -57,7 +77,6 @@ public abstract class AbstractGet extends AbstractLastNetconfOperation {
     }
 
     protected Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data, final YangInstanceIdentifier dataRoot) {
-//        boolean isDataRoot = true;
 
         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
 
@@ -68,20 +87,11 @@ public abstract class AbstractGet extends AbstractLastNetconfOperation {
 
         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter);
 
-//        if (isDataRoot) {
         writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
-//        } else {
-//            if (data instanceof MapEntryNode) {
-//                // Restconf allows returning one list item. We need to wrap it
-//                // in map node in order to serialize it properly
-//                data = ImmutableNodes.mapNodeBuilder(data.getNodeType()).addChild((MapEntryNode) data).build();
-//            }
-//            nnWriter.write(data);
-//            nnWriter.flush();
-//        }
         return result.getNode();
     }
 
+
     private XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
         try {
             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
@@ -104,9 +114,12 @@ public abstract class AbstractGet extends AbstractLastNetconfOperation {
     // TODO this code is located in Restconf already
     private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
         try {
-            final QName name = SchemaContext.NAME;
-            for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
-                nnWriter.write(child);
+            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();
@@ -115,9 +128,100 @@ public abstract class AbstractGet extends AbstractLastNetconfOperation {
         }
     }
 
+    private DataSchemaNode getSchemaNodeFromNamespace(final XmlElement element) throws NetconfDocumentedException {
+
+        try {
+            final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(element.getNamespace()), null);
+            DataSchemaNode dataSchemaNode = module.getDataChildByName(element.getName());
+            if (dataSchemaNode != null) {
+                return dataSchemaNode;
+            }
+        } catch (URISyntaxException e) {
+            LOG.debug("Error during parsing of element namespace, this should not happen since namespace of an xml " +
+                    "element is valid and if the xml was parsed then the URI should be as well");
+            throw new IllegalArgumentException("Unable to parse element namespace, this should not happen since " +
+                    "namespace of an xml element is valid and if the xml was parsed then the URI should be as well");
+        }
+        throw new NetconfDocumentedException("Unable to find node with namespace: " + element.getNamespace() + "in schema context: " + schemaContext.getCurrentContext().toString(),
+                ErrorType.application,
+                ErrorTag.unknown_namespace,
+                ErrorSeverity.error);
+    }
+
+    protected Element serializeNodeWithParentStructure(Document document, YangInstanceIdentifier dataRoot, NormalizedNode node) {
+        if (!dataRoot.equals(ROOT)) {
+            return (Element) transformNormalizedNode(document,
+                    InstanceIdToNodes.serialize(schemaContext.getCurrentContext(), dataRoot, node),
+                    ROOT);
+        }
+        return  (Element) transformNormalizedNode(document, node, ROOT);
+    }
+
+    /**
+     *
+     * @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 <data/> container in the response.
+     *         if filter is not present we want to read the entire datastore - return ROOT.
+     * @throws NetconfDocumentedException
+     */
+    protected Optional<YangInstanceIdentifier> getDataRootFromFilter(XmlElement operationElement) throws NetconfDocumentedException {
+        Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
+        if (filterElement.isPresent()) {
+            if (filterElement.get().getChildElements().size() == 0) {
+                return Optional.absent();
+            }
+            return Optional.of(getInstanceIdentifierFromFilter(filterElement.get()));
+        } else {
+            return Optional.of(ROOT);
+        }
+    }
+
+    @VisibleForTesting
+    protected YangInstanceIdentifier getInstanceIdentifierFromFilter(XmlElement filterElement) throws NetconfDocumentedException {
+
+        if (filterElement.getChildElements().size() != 1) {
+            throw new NetconfDocumentedException("Multiple filter roots not supported yet",
+                    ErrorType.application, ErrorTag.operation_not_supported, ErrorSeverity.error);
+        }
+
+        XmlElement element = filterElement.getOnlyChildElement();
+        DataSchemaNode schemaNode = getSchemaNodeFromNamespace(element);
+
+        return getReadPointFromNode(YangInstanceIdentifier.builder().build(), filterToNormalizedNode(element, schemaNode));
+    }
+
+    private YangInstanceIdentifier getReadPointFromNode(final YangInstanceIdentifier pathArg, final NormalizedNode nNode) {
+        final YangInstanceIdentifier path = pathArg.node(nNode.getIdentifier());
+        if (nNode instanceof DataContainerNode) {
+            DataContainerNode node = (DataContainerNode) nNode;
+            if (node.getValue().size() == 1) {
+                return getReadPointFromNode(path, (NormalizedNode) Lists.newArrayList(node.getValue()).get(0));
+            }
+        }
+        return path;
+    }
+
+    private NormalizedNode filterToNormalizedNode(XmlElement element, DataSchemaNode schemaNode) throws NetconfDocumentedException {
+        DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory
+                .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext());
+
+        final NormalizedNode parsedNode;
+
+        if (schemaNode instanceof ContainerSchemaNode) {
+            parsedNode = parserFactory.getContainerNodeParser().parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
+        } else if (schemaNode instanceof ListSchemaNode) {
+            parsedNode = parserFactory.getMapNodeParser().parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
+        } else {
+            throw new NetconfDocumentedException("Schema node of the top level element is not an instance of container or list",
+                    ErrorType.application, ErrorTag.unknown_element, ErrorSeverity.error);
+        }
+        return parsedNode;
+    }
+
     protected static final class GetConfigExecution {
-        private final Optional<Datastore> datastore;
 
+        private final Optional<Datastore> datastore;
         public GetConfigExecution(final Optional<Datastore> datastore) {
             this.datastore = datastore;
         }
@@ -140,8 +244,6 @@ public abstract class AbstractGet extends AbstractLastNetconfOperation {
                 throw new NetconfDocumentedException("Get-config source attribute error: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
             }
 
-            // Add filter
-
             return new GetConfigExecution(sourceDatastore);
         }
 
@@ -157,6 +259,7 @@ public abstract class AbstractGet extends AbstractLastNetconfOperation {
             xml.checkName(operationName);
             xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
         }
+
     }
 
 }