*/
package org.opendaylight.netconf.sal.rest.impl;
-import com.google.common.base.Preconditions;
-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;
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;
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;
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.DataContainerNodeBuilder;
-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;
private final ControllerContext controllerContext;
public RestconfDocumentedExceptionMapper(final ControllerContext controllerContext) {
- this.controllerContext = Preconditions.checkNotNull(controllerContext);
+ this.controllerContext = requireNonNull(controllerContext);
}
@Override
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 var errorsSchemaNode = errorsEntry.getValue();
final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> errContBuild =
- Builders.containerBuilder((ContainerSchemaNode) errorsSchemaNode);
+ 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);
}
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 DataContainerNodeBuilder<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) {
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
// 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();
}
}
};
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);
}
}
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;
} 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
// 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();
}
}
};
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();
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 QName name = SchemaContext.NAME;
try {
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();
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();
}