Bump versions to 4.0.0-SNAPSHOT
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / rest / impl / RestconfDocumentedExceptionMapper.java
index 6f9616163431b22deb947d80bfad6a6fa925e5c4..ff0be39a807e4a41f232d143e1a62959d4e4f75a 100644 (file)
@@ -5,18 +5,16 @@
  * 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.sal.rest.impl;
 
-import com.google.common.base.Preconditions;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
 import com.google.gson.stream.JsonWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -24,6 +22,7 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.ext.ExceptionMapper;
 import javax.ws.rs.ext.Provider;
 import javax.xml.XMLConstants;
@@ -33,19 +32,21 @@ import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.netconf.sal.rest.api.Draft02;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.restconf.common.ErrorTags;
 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
-import org.opendaylight.restconf.common.context.NormalizedNodeContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-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.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.ForwardingNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
@@ -53,17 +54,12 @@ import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -91,7 +87,7 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
     private final ControllerContext controllerContext;
 
     public RestconfDocumentedExceptionMapper(final ControllerContext controllerContext) {
-        this.controllerContext = Preconditions.checkNotNull(controllerContext);
+        this.controllerContext = requireNonNull(controllerContext);
     }
 
     @Override
@@ -119,26 +115,21 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
             return Response.status(exception.getStatus()).type(MediaType.TEXT_PLAIN_TYPE).entity(" ").build();
         }
 
-        final int status = errors.iterator().next().getErrorTag().getStatusCode();
-
-        final DataNodeContainer errorsSchemaNode =
-                (DataNodeContainer) controllerContext.getRestconfModuleErrorsSchemaNode();
-
-        if (errorsSchemaNode == null) {
+        final Status status = ErrorTags.statusOf(errors.iterator().next().getErrorTag());
+        final var errorsEntry = controllerContext.getRestconfModuleErrorsSchemaNode();
+        if (errorsEntry == null) {
             return Response.status(status).type(MediaType.TEXT_PLAIN_TYPE).entity(exception.getMessage()).build();
         }
 
-        Preconditions.checkState(errorsSchemaNode instanceof ContainerSchemaNode,
-                "Found Errors SchemaNode isn't ContainerNode");
-        final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> errContBuild =
-                Builders.containerBuilder((ContainerSchemaNode) errorsSchemaNode);
+        final var errorsSchemaNode = errorsEntry.getValue();
+        final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> errContBuild =
+                SchemaAwareBuilders.containerBuilder(errorsSchemaNode);
 
-        final List<DataSchemaNode> schemaList = ControllerContext.findInstanceDataChildrenByName(errorsSchemaNode,
+        final var schemaList = ControllerContext.findInstanceDataChildrenByName(errorsSchemaNode,
                 Draft02.RestConfModule.ERROR_LIST_SCHEMA_NODE);
-        final DataSchemaNode errListSchemaNode = Iterables.getFirst(schemaList, null);
-        Preconditions.checkState(
-                errListSchemaNode instanceof ListSchemaNode, "Found Error SchemaNode isn't ListSchemaNode");
-        final CollectionNodeBuilder<MapEntryNode, MapNode> listErorsBuilder = Builders
+        final DataSchemaNode errListSchemaNode = ControllerContext.getFirst(schemaList);
+        checkState(errListSchemaNode instanceof ListSchemaNode, "Found Error SchemaNode isn't ListSchemaNode");
+        final CollectionNodeBuilder<MapEntryNode, SystemMapNode> listErorsBuilder = SchemaAwareBuilders
                 .mapBuilder((ListSchemaNode) errListSchemaNode);
 
 
@@ -147,54 +138,54 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         }
         errContBuild.withChild(listErorsBuilder.build());
 
-        final NormalizedNodeContext errContext =  new NormalizedNodeContext(new InstanceIdentifierContext<>(null,
-                (DataSchemaNode) errorsSchemaNode, null, controllerContext.getGlobalSchema()), errContBuild.build());
+        final NormalizedNodeContext errContext = new NormalizedNodeContext(
+            InstanceIdentifierContext.ofStack(errorsEntry.getKey(), null), errContBuild.build());
 
-        Object responseBody;
+        final String responseBody;
         if (mediaType.getSubtype().endsWith("json")) {
-            responseBody = toJsonResponseBody(errContext, errorsSchemaNode);
+            responseBody = toJsonResponseBody(errContext);
         } else {
-            responseBody = toXMLResponseBody(errContext, errorsSchemaNode);
+            responseBody = toXMLResponseBody(errContext);
         }
 
         return Response.status(status).type(mediaType).entity(responseBody).build();
     }
 
     private static MapEntryNode toErrorEntryNode(final RestconfError error, final DataSchemaNode errListSchemaNode) {
-        Preconditions.checkArgument(errListSchemaNode instanceof ListSchemaNode,
+        checkArgument(errListSchemaNode instanceof ListSchemaNode,
                 "errListSchemaNode has to be of type ListSchemaNode");
         final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) errListSchemaNode;
-        final DataContainerNodeAttrBuilder<NodeIdentifierWithPredicates, MapEntryNode> errNodeValues = Builders
+        final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> errNodeValues = SchemaAwareBuilders
                 .mapEntryBuilder(listStreamSchemaNode);
 
-        List<DataSchemaNode> lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
+        var lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
                 listStreamSchemaNode, "error-type");
-        final DataSchemaNode errTypSchemaNode = Iterables.getFirst(lsChildDataSchemaNode, null);
-        Preconditions.checkState(errTypSchemaNode instanceof LeafSchemaNode);
-        errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errTypSchemaNode)
-                .withValue(error.getErrorType().getErrorTypeTag()).build());
+        final DataSchemaNode errTypSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+        checkState(errTypSchemaNode instanceof LeafSchemaNode);
+        errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errTypSchemaNode)
+                .withValue(error.getErrorType().elementBody()).build());
 
         lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
                 listStreamSchemaNode, "error-tag");
-        final DataSchemaNode errTagSchemaNode = Iterables.getFirst(lsChildDataSchemaNode, null);
-        Preconditions.checkState(errTagSchemaNode instanceof LeafSchemaNode);
-        errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errTagSchemaNode)
-                .withValue(error.getErrorTag().getTagValue()).build());
+        final DataSchemaNode errTagSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+        checkState(errTagSchemaNode instanceof LeafSchemaNode);
+        errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errTagSchemaNode)
+                .withValue(error.getErrorTag().elementBody()).build());
 
         if (error.getErrorAppTag() != null) {
             lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
                     listStreamSchemaNode, "error-app-tag");
-            final DataSchemaNode errAppTagSchemaNode = Iterables.getFirst(lsChildDataSchemaNode, null);
-            Preconditions.checkState(errAppTagSchemaNode instanceof LeafSchemaNode);
-            errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errAppTagSchemaNode)
+            final DataSchemaNode errAppTagSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+            checkState(errAppTagSchemaNode instanceof LeafSchemaNode);
+            errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errAppTagSchemaNode)
                     .withValue(error.getErrorAppTag()).build());
         }
 
         lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName(
                 listStreamSchemaNode, "error-message");
-        final DataSchemaNode errMsgSchemaNode = Iterables.getFirst(lsChildDataSchemaNode, null);
-        Preconditions.checkState(errMsgSchemaNode instanceof LeafSchemaNode);
-        errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errMsgSchemaNode)
+        final DataSchemaNode errMsgSchemaNode = ControllerContext.getFirst(lsChildDataSchemaNode);
+        checkState(errMsgSchemaNode instanceof LeafSchemaNode);
+        errNodeValues.withChild(SchemaAwareBuilders.leafBuilder((LeafSchemaNode) errMsgSchemaNode)
                 .withValue(error.getErrorMessage()).build());
 
         if (error.getErrorInfo() != null) {
@@ -210,35 +201,36 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         return errNodeValues.build();
     }
 
-    private static Object toJsonResponseBody(final NormalizedNodeContext errorsNode,
-                                             final DataNodeContainer errorsSchemaNode) {
+    private static String toJsonResponseBody(final NormalizedNodeContext errorsNode) {
         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-        NormalizedNode<?, ?> data = errorsNode.getData();
-        final InstanceIdentifierContext<?> context = errorsNode.getInstanceIdentifierContext();
+        NormalizedNode data = errorsNode.getData();
+        final InstanceIdentifierContext context = errorsNode.getInstanceIdentifierContext();
         final DataSchemaNode schema = (DataSchemaNode) context.getSchemaNode();
 
-        SchemaPath path = context.getSchemaNode().getPath();
         final OutputStreamWriter outputWriter = new OutputStreamWriter(outStream, StandardCharsets.UTF_8);
         if (data == null) {
             throw new RestconfDocumentedException(Response.Status.NOT_FOUND);
         }
 
-        boolean isDataRoot = false;
-        URI initialNs = null;
-        if (SchemaPath.ROOT.equals(path)) {
+        final boolean isDataRoot;
+        final var stack = context.inference().toSchemaInferenceStack();
+        if (stack.isEmpty()) {
             isDataRoot = true;
         } else {
-            path = path.getParent();
+            isDataRoot = false;
+            stack.exit();
             // FIXME: Add proper handling of reading root.
         }
+
+        XMLNamespace initialNs = null;
         if (!schema.isAugmenting() && !(schema instanceof SchemaContext)) {
             initialNs = schema.getQName().getNamespace();
         }
 
         final JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(outputWriter);
         final NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
-            JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context.getSchemaContext()), path,
-            initialNs, jsonWriter);
+            JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(context.getSchemaContext()),
+            stack.toInference(), initialNs, jsonWriter);
 
         // We create a delegating writer to special-case error-info as error-info is defined as an empty
         // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
@@ -246,18 +238,38 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
         // for error-info.
         final NormalizedNodeStreamWriter streamWriter = new ForwardingNormalizedNodeStreamWriter() {
+            private boolean inOurLeaf;
+
             @Override
             protected NormalizedNodeStreamWriter delegate() {
                 return jsonStreamWriter;
             }
 
             @Override
-            public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+            public void startLeafNode(final NodeIdentifier name) throws IOException {
                 if (name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+                    inOurLeaf = true;
                     jsonWriter.name(Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName());
+                } else {
+                    super.startLeafNode(name);
+                }
+            }
+
+            @Override
+            public void scalarValue(final Object value) throws IOException {
+                if (inOurLeaf) {
                     jsonWriter.value(value.toString());
                 } else {
-                    super.leafNode(name, value);
+                    super.scalarValue(value);
+                }
+            }
+
+            @Override
+            public void endNode() throws IOException {
+                if (inOurLeaf) {
+                    inOurLeaf = false;
+                } else {
+                    super.endNode();
                 }
             }
         };
@@ -268,7 +280,9 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
                 writeDataRoot(outputWriter,nnWriter,(ContainerNode) data);
             } else {
                 if (data instanceof MapEntryNode) {
-                    data = ImmutableNodes.mapNodeBuilder(data.getNodeType()).withChild((MapEntryNode) data).build();
+                    data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+                        .withChild((MapEntryNode) data)
+                        .build();
                 }
                 nnWriter.write(data);
             }
@@ -279,16 +293,16 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         }
 
         try {
-            return outStream.toString(StandardCharsets.UTF_8.name());
-        } catch (UnsupportedEncodingException e) {
-            // Shouldn't happen
-            return "Failure encoding error response: " + e;
+            streamWriter.close();
+        } catch (IOException e) {
+            LOG.warn("Failed to close stream writer", e);
         }
+
+        return outStream.toString(StandardCharsets.UTF_8);
     }
 
-    private static Object toXMLResponseBody(final NormalizedNodeContext errorsNode,
-                                            final DataNodeContainer errorsSchemaNode) {
-        final InstanceIdentifierContext<?> pathContext = errorsNode.getInstanceIdentifierContext();
+    private static String toXMLResponseBody(final NormalizedNodeContext errorsNode) {
+        final InstanceIdentifierContext pathContext = errorsNode.getInstanceIdentifierContext();
         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
 
         final XMLStreamWriter xmlWriter;
@@ -297,18 +311,19 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         } catch (final XMLStreamException | FactoryConfigurationError e) {
             throw new IllegalStateException(e);
         }
-        NormalizedNode<?, ?> data = errorsNode.getData();
-        SchemaPath schemaPath = pathContext.getSchemaNode().getPath();
+        NormalizedNode data = errorsNode.getData();
 
-        boolean isDataRoot = false;
-        if (SchemaPath.ROOT.equals(schemaPath)) {
+        final boolean isDataRoot;
+        final var stack = pathContext.inference().toSchemaInferenceStack();
+        if (stack.isEmpty()) {
             isDataRoot = true;
         } else {
-            schemaPath = schemaPath.getParent();
+            isDataRoot = false;
+            stack.exit();
         }
 
         final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
-                pathContext.getSchemaContext(), schemaPath);
+                stack.toInference());
 
         // We create a delegating writer to special-case error-info as error-info is defined as an empty
         // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
@@ -316,25 +331,53 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
         // for error-info.
         final NormalizedNodeStreamWriter streamWriter = new ForwardingNormalizedNodeStreamWriter() {
+            private boolean inOurLeaf;
+
             @Override
             protected NormalizedNodeStreamWriter delegate() {
                 return xmlStreamWriter;
             }
 
             @Override
-            public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+            public void startLeafNode(final NodeIdentifier name) throws IOException {
                 if (name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
                     String ns = Draft02.RestConfModule.ERROR_INFO_QNAME.getNamespace().toString();
                     try {
                         xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
                                 Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName(), ns);
+                    } catch (XMLStreamException e) {
+                        throw new IOException("Error writing error-info", e);
+                    }
+                    inOurLeaf = true;
+                } else {
+                    super.startLeafNode(name);
+                }
+            }
+
+            @Override
+            public void scalarValue(final Object value) throws IOException {
+                if (inOurLeaf) {
+                    try {
                         xmlWriter.writeCharacters(value.toString());
+                    } catch (XMLStreamException e) {
+                        throw new IOException("Error writing error-info", e);
+                    }
+                } else {
+                    super.scalarValue(value);
+                }
+            }
+
+            @Override
+            public void endNode() throws IOException {
+                if (inOurLeaf) {
+                    try {
                         xmlWriter.writeEndElement();
                     } catch (XMLStreamException e) {
                         throw new IOException("Error writing error-info", e);
                     }
+                    inOurLeaf = false;
                 } else {
-                    super.leafNode(name, value);
+                    super.endNode();
                 }
             }
         };
@@ -347,7 +390,9 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
                 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();
+                    data = ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
+                        .addChild((MapEntryNode) data)
+                        .build();
                 }
                 nnWriter.write(data);
                 nnWriter.flush();
@@ -356,34 +401,28 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
             LOG.warn("Error writing error response body.", e);
         }
 
-        try {
-            return outStream.toString(StandardCharsets.UTF_8.name());
-        } catch (UnsupportedEncodingException e) {
-            // Shouldn't happen
-            return "Failure encoding error response: " + e;
-        }
+        return outStream.toString(StandardCharsets.UTF_8);
     }
 
     private static void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter,
-                                         final ContainerNode data)
-            throws IOException {
+                                         final ContainerNode data) throws IOException {
+        final QName name = SchemaContext.NAME;
         try {
-            final QName name = SchemaContext.NAME;
             xmlWriter.writeStartElement(name.getNamespace().toString(), name.getLocalName());
-            for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
+            for (final DataContainerChild child : data.body()) {
                 nnWriter.write(child);
             }
             nnWriter.flush();
             xmlWriter.writeEndElement();
             xmlWriter.flush();
         } catch (final XMLStreamException e) {
-            Throwables.propagate(e);
+            throw new IOException("Failed to write elements", e);
         }
     }
 
     private static void writeDataRoot(final OutputStreamWriter outputWriter, final NormalizedNodeWriter nnWriter,
                                       final ContainerNode data) throws IOException {
-        for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
+        for (final DataContainerChild child : data.body()) {
             nnWriter.write(child);
             nnWriter.flush();
         }