X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fsal%2Frest%2Fimpl%2FRestconfDocumentedExceptionMapper.java;h=f52b42337e2a759ed52b01f9c85aa5bd5fba227f;hp=16b3ee67080ed70979a96d57d5b095a341bffc17;hb=06e889c9c78457590b6a0b62d89a6b9f44242a9f;hpb=e04d4f23ec7a8b275f06cf25cd45ce1c76e4703d diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDocumentedExceptionMapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDocumentedExceptionMapper.java index 16b3ee6708..f52b42337e 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDocumentedExceptionMapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDocumentedExceptionMapper.java @@ -8,55 +8,58 @@ package org.opendaylight.controller.sal.rest.impl; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERRORS_CONTAINER_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERROR_APP_TAG_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERROR_INFO_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERROR_LIST_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERROR_MESSAGE_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERROR_TAG_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.ERROR_TYPE_QNAME; -import static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.NAMESPACE; import com.google.common.base.Charsets; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.gson.stream.JsonWriter; +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; -import java.io.StringReader; -import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.Iterator; import java.util.List; -import java.util.Map.Entry; -import javax.activation.UnsupportedDataTypeException; 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.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.TransformerFactoryConfigurationError; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.opendaylight.controller.sal.rest.api.Draft02; import org.opendaylight.controller.sal.restconf.impl.ControllerContext; +import org.opendaylight.controller.sal.restconf.impl.InstanceIdentifierContext; +import org.opendaylight.controller.sal.restconf.impl.NormalizedNodeContext; import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException; import org.opendaylight.controller.sal.restconf.impl.RestconfError; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.data.api.Node; -import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; -import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +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.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.impl.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.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; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; /** * This class defines an ExceptionMapper that handles RestconfDocumentedExceptions thrown by resource implementations @@ -68,7 +71,13 @@ import org.xml.sax.InputSource; public class RestconfDocumentedExceptionMapper implements ExceptionMapper { private final static Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapper.class); - private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); + + private static final XMLOutputFactory XML_FACTORY; + + static { + XML_FACTORY = XMLOutputFactory.newFactory(); + XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + } @Context private HttpHeaders headers; @@ -78,9 +87,7 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper accepts = headers.getAcceptableMediaTypes(); + final List accepts = headers.getAcceptableMediaTypes(); accepts.remove(MediaType.WILDCARD_TYPE); LOG.debug("Accept headers: {}", accepts); @@ -95,7 +102,7 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper errors = exception.getErrors(); + final List errors = exception.getErrors(); if (errors.isEmpty()) { // We don't actually want to send any content but, if we don't set any content here, // the tomcat front-end will send back an html error report. To prevent that, set a @@ -104,162 +111,202 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper> errorNodes = ImmutableList.> builder(); - for (RestconfError error : errors) { - errorNodes.add(toDomNode(error)); + Preconditions.checkState(errorsSchemaNode instanceof ContainerSchemaNode, + "Found Errors SchemaNode isn't ContainerNode"); + final DataContainerNodeAttrBuilder errContBuild = + Builders.containerBuilder((ContainerSchemaNode) errorsSchemaNode); + + final List 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 listErorsBuilder = Builders + .mapBuilder((ListSchemaNode) errListSchemaNode); + + + for (final RestconfError error : errors) { + listErorsBuilder.withChild(toErrorEntryNode(error, errListSchemaNode)); } + errContBuild.withChild(listErorsBuilder.build()); - ImmutableCompositeNode errorsNode = ImmutableCompositeNode.create(ERRORS_CONTAINER_QNAME, errorNodes.build()); + final NormalizedNodeContext errContext = new NormalizedNodeContext(new InstanceIdentifierContext(null, + (DataSchemaNode) errorsSchemaNode, null, context.getGlobalSchema()), errContBuild.build()); Object responseBody; if (mediaType.getSubtype().endsWith("json")) { - responseBody = toJsonResponseBody(errorsNode, errorsSchemaNode); + responseBody = toJsonResponseBody(errContext, errorsSchemaNode); } else { - responseBody = toXMLResponseBody(errorsNode, errorsSchemaNode); + responseBody = toXMLResponseBody(errContext, errorsSchemaNode); } return Response.status(status).type(mediaType).entity(responseBody).build(); } - private Object toJsonResponseBody(final ImmutableCompositeNode errorsNode, final DataNodeContainer errorsSchemaNode) { - - JsonMapper jsonMapper = new JsonMapper(null); - - Object responseBody = null; - try { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - JsonWriter writer = new JsonWriter(new OutputStreamWriter(outStream, Charsets.UTF_8)); - writer.setIndent(" "); + private MapEntryNode toErrorEntryNode(final RestconfError error, final DataSchemaNode errListSchemaNode) { + Preconditions.checkArgument(errListSchemaNode instanceof ListSchemaNode, + "errListSchemaNode has to be of type ListSchemaNode"); + final ListSchemaNode listStreamSchemaNode = (ListSchemaNode) errListSchemaNode; + final DataContainerNodeAttrBuilder errNodeValues = Builders + .mapEntryBuilder(listStreamSchemaNode); + + List 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()); + + 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()); + + 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) + .withValue(error.getErrorAppTag()).build()); + } - jsonMapper.write(writer, errorsNode, errorsSchemaNode); - writer.flush(); + lsChildDataSchemaNode = ControllerContext.findInstanceDataChildrenByName( + (listStreamSchemaNode), "error-message"); + final DataSchemaNode errMsgSchemaNode = Iterables.getFirst(lsChildDataSchemaNode, null); + Preconditions.checkState(errMsgSchemaNode instanceof LeafSchemaNode); + errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errMsgSchemaNode) + .withValue(error.getErrorMessage()).build()); - responseBody = outStream.toString("UTF-8"); - } catch (IOException e) { - LOG.error("Error writing error response body", e); - } + // TODO : find how could we add possible "error-path" and "error-info" - return responseBody; + return errNodeValues.build(); } - private Object toXMLResponseBody(final ImmutableCompositeNode errorsNode, final DataNodeContainer errorsSchemaNode) { - - XmlMapper xmlMapper = new XmlMapper(); + private Object toJsonResponseBody(final NormalizedNodeContext errorsNode, final DataNodeContainer errorsSchemaNode) { - Object responseBody = null; - try { - Document xmlDoc = xmlMapper.write(errorsNode, errorsSchemaNode); + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + NormalizedNode data = errorsNode.getData(); + final InstanceIdentifierContext context = (InstanceIdentifierContext) errorsNode.getInstanceIdentifierContext(); + final DataSchemaNode schema = context.getSchemaNode(); - responseBody = documentToString(xmlDoc); - } catch (TransformerException | UnsupportedDataTypeException | UnsupportedEncodingException e) { - LOG.error("Error writing error response body", e); + SchemaPath path = context.getSchemaNode().getPath(); + final OutputStreamWriter outputWriter = new OutputStreamWriter(outStream, Charsets.UTF_8); + if (data == null) { + throw new RestconfDocumentedException(Response.Status.NOT_FOUND); } - return responseBody; - } - - private String documentToString(final Document doc) throws TransformerException, UnsupportedEncodingException { - Transformer transformer = createTransformer(); - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - - transformer.transform(new DOMSource(doc), new StreamResult(outStream)); + boolean isDataRoot = false; + URI initialNs = null; + if (SchemaPath.ROOT.equals(path)) { + isDataRoot = true; + } else { + path = path.getParent(); + // FIXME: Add proper handling of reading root. + } + if(!schema.isAugmenting() && !(schema instanceof SchemaContext)) { + initialNs = schema.getQName().getNamespace(); + } + final NormalizedNodeStreamWriter jsonWriter = JSONNormalizedNodeStreamWriter.create(context.getSchemaContext(),path,initialNs,outputWriter); + final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(jsonWriter); + try { + if(isDataRoot) { + writeDataRoot(outputWriter,nnWriter,(ContainerNode) data); + } else { + if(data instanceof MapEntryNode) { + data = ImmutableNodes.mapNodeBuilder(data.getNodeType()).withChild(((MapEntryNode) data)).build(); + } + nnWriter.write(data); + } + nnWriter.flush(); + outputWriter.flush(); + } + catch (final IOException e) { + LOG.warn("Error writing error response body", e); + } - return outStream.toString("UTF-8"); - } + return outStream.toString(); - private Transformer createTransformer() throws TransformerFactoryConfigurationError, - TransformerConfigurationException { - Transformer transformer = TRANSFORMER_FACTORY.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); - return transformer; } - private Node toDomNode(final RestconfError error) { - - CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); - builder.setQName(ERROR_LIST_QNAME); + private Object toXMLResponseBody(final NormalizedNodeContext errorsNode, final DataNodeContainer errorsSchemaNode) { - addLeaf(builder, ERROR_TYPE_QNAME, error.getErrorType().getErrorTypeTag()); - addLeaf(builder, ERROR_TAG_QNAME, error.getErrorTag().getTagValue()); - addLeaf(builder, ERROR_MESSAGE_QNAME, error.getErrorMessage()); - addLeaf(builder, ERROR_APP_TAG_QNAME, error.getErrorAppTag()); + final InstanceIdentifierContext pathContext = (InstanceIdentifierContext) errorsNode.getInstanceIdentifierContext(); + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - Node errorInfoNode = parseErrorInfo(error.getErrorInfo()); - if (errorInfoNode != null) { - builder.add(errorInfoNode); + XMLStreamWriter xmlWriter; + try { + xmlWriter = XML_FACTORY.createXMLStreamWriter(outStream, "UTF-8"); + } catch (final XMLStreamException e) { + throw new IllegalStateException(e); + } catch (final FactoryConfigurationError e) { + throw new IllegalStateException(e); } + NormalizedNode data = errorsNode.getData(); + SchemaPath schemaPath = pathContext.getSchemaNode().getPath(); - return builder.toInstance(); - } - - private Node parseErrorInfo(final String errorInfo) { - if (Strings.isNullOrEmpty(errorInfo)) { - return null; + boolean isDataRoot = false; + if (SchemaPath.ROOT.equals(schemaPath)) { + isDataRoot = true; + } else { + schemaPath = schemaPath.getParent(); } - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - - factory.setNamespaceAware(true); - factory.setCoalescing(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); - - // Wrap the error info content in a root element so it can be parsed - // as XML. The error info content may or may not be XML. If not then it will be - // parsed as text content of the element. - - String errorInfoWithRoot = new StringBuilder("") - .append(errorInfo).append("").toString(); - - Document doc = null; + final NormalizedNodeStreamWriter streamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, + pathContext.getSchemaContext(), schemaPath); + final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter); try { - doc = factory.newDocumentBuilder().parse(new InputSource(new StringReader(errorInfoWithRoot))); - } catch (Exception e) { - // TODO: what if the content is text that happens to contain invalid markup? - // Could wrap in CDATA and try again. - - LOG.warn("Error parsing restconf error-info, \"{}\", as XML", errorInfo, e); - return null; + 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(); + } + } + catch (final IOException e) { + LOG.warn("Error writing error response body.", e); } - Node errorInfoNode = XmlDocumentUtils.toDomNode(doc); - - if (errorInfoNode instanceof CompositeNode) { - CompositeNode compositeNode = (CompositeNode) XmlDocumentUtils.toDomNode(doc); - - // At this point the QName for the "error-info" CompositeNode doesn't contain the revision - // as it isn't present in the XML. So we'll copy all the child nodes and create a new - // CompositeNode with the full QName. This is done so the XML/JSON mapping code can - // locate the schema. + return outStream.toString(); + } - ImmutableList.Builder> childNodes = ImmutableList.builder(); - for (Entry>> entry : compositeNode.entrySet()) { - childNodes.addAll(entry.getValue()); + private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) + throws IOException { + try { + final QName name = SchemaContext.NAME; + xmlWriter.writeStartElement(name.getNamespace().toString(), name.getLocalName()); + for (final DataContainerChild child : data.getValue()) { + nnWriter.write(child); } - - errorInfoNode = ImmutableCompositeNode.create(ERROR_INFO_QNAME, childNodes.build()); + nnWriter.flush(); + xmlWriter.writeEndElement(); + xmlWriter.flush(); + } catch (final XMLStreamException e) { + Throwables.propagate(e); } - - return errorInfoNode; } - private void addLeaf(final CompositeNodeBuilder builder, final QName qname, - final String value) { - if (!Strings.isNullOrEmpty(value)) { - builder.addLeaf(qname, value); + private void writeDataRoot(final OutputStreamWriter outputWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) throws IOException { + final Iterator> iterator = data.getValue().iterator(); + while(iterator.hasNext()) { + final DataContainerChild child = iterator.next(); + nnWriter.write(child); + nnWriter.flush(); } } }