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=2e4e00de90905960b19313e945a7cf72a23e5cbc;hp=10201ab6f5148c78480b9cceea34b77766f0027e;hb=9ba2b4eca79bcc0e78099b133296801c8d45a6c4;hpb=8964c8d23226be1bcb47d799834f1dd436a7f5ca 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 10201ab6f5..2e4e00de90 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,56 +8,63 @@ 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.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; import com.google.gson.stream.JsonWriter; 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.XMLConstants; +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.AugmentationIdentifier; +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.JSONCodecFactory; +import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory; +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 @@ -70,6 +77,13 @@ 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 +107,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 +116,346 @@ 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) { + 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 jsonMapper = new JsonMapper(null); + 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()); + + if(error.getErrorInfo() != null) { + // Oddly, error-info is defined as an empty container in the restconf yang. Apparently the + // intention is for implementors to define their own data content so we'll just treat it as a leaf + // with string data. + errNodeValues.withChild(ImmutableNodes.leafNode(Draft02.RestConfModule.ERROR_INFO_QNAME, + error.getErrorInfo())); + } - Object responseBody = null; - try { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - JsonWriter writer = new JsonWriter(new OutputStreamWriter(outStream, Charsets.UTF_8)); - writer.setIndent(" "); + // TODO : find how could we add possible "error-path" + + return errNodeValues.build(); + } - jsonMapper.write(writer, errorsNode, errorsSchemaNode); - writer.flush(); + private Object toJsonResponseBody(final NormalizedNodeContext errorsNode, final DataNodeContainer errorsSchemaNode) { - responseBody = outStream.toString("UTF-8"); - } catch (IOException e) { - LOG.error("Error writing error response body", e); + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + 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, Charsets.UTF_8); + if (data == null) { + throw new RestconfDocumentedException(Response.Status.NOT_FOUND); + } + + 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 JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(outputWriter); + final NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter( + JSONCodecFactory.create(context.getSchemaContext()), path, 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 + // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but + // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior + // for error-info. + final NormalizedNodeStreamWriter streamWriter = new DelegatingNormalizedNodeStreamWriter(jsonStreamWriter) { + @Override + public void leafNode(final NodeIdentifier name, final Object value) throws IOException { + if(name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) { + jsonWriter.name(Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName()); + jsonWriter.value(value.toString()); + } else { + super.leafNode(name, value); + } + } + }; + + final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter); + 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(); - return responseBody; } - private Object toXMLResponseBody(final ImmutableCompositeNode errorsNode, final DataNodeContainer errorsSchemaNode) { + private Object toXMLResponseBody(final NormalizedNodeContext errorsNode, final DataNodeContainer errorsSchemaNode) { - XmlMapper xmlMapper = new XmlMapper(); + final InstanceIdentifierContext pathContext = errorsNode.getInstanceIdentifierContext(); + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - Object responseBody = null; + final XMLStreamWriter xmlWriter; try { - Document xmlDoc = xmlMapper.write(errorsNode, errorsSchemaNode); - - responseBody = documentToString(xmlDoc); - } catch (TransformerException | UnsupportedDataTypeException | UnsupportedEncodingException e) { - LOG.error("Error writing error response body", e); + 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 responseBody; - } + boolean isDataRoot = false; + if (SchemaPath.ROOT.equals(schemaPath)) { + isDataRoot = true; + } else { + schemaPath = schemaPath.getParent(); + } - private String documentToString(final Document doc) throws TransformerException, UnsupportedEncodingException { - Transformer transformer = createTransformer(); - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, + pathContext.getSchemaContext(), schemaPath); + + // 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 + // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but + // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior + // for error-info. + final NormalizedNodeStreamWriter streamWriter = new DelegatingNormalizedNodeStreamWriter(xmlStreamWriter) { + @Override + public void leafNode(final NodeIdentifier name, final Object value) 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); + xmlWriter.writeCharacters(value.toString()); + xmlWriter.writeEndElement(); + } catch (XMLStreamException e) { + throw new IOException("Error writing error-info", e); + } + } else { + super.leafNode(name, value); + } + } + }; - transformer.transform(new DOMSource(doc), new StreamResult(outStream)); + final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter); + try { + 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); + } - return outStream.toString("UTF-8"); + return outStream.toString(); } - private Transformer createTransformer() throws TransformerFactoryConfigurationError, - TransformerConfigurationException { - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.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 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); + } + nnWriter.flush(); + xmlWriter.writeEndElement(); + xmlWriter.flush(); + } catch (final XMLStreamException e) { + Throwables.propagate(e); + } } - private Node toDomNode(final RestconfError error) { + 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(); + } + } - CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); - builder.setQName(ERROR_LIST_QNAME); + private static class DelegatingNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter { + private final NormalizedNodeStreamWriter delegate; - 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()); + DelegatingNormalizedNodeStreamWriter(NormalizedNodeStreamWriter delegate) { + this.delegate = delegate; + } - Node errorInfoNode = parseErrorInfo(error.getErrorInfo()); - if (errorInfoNode != null) { - builder.add(errorInfoNode); + @Override + public void leafNode(NodeIdentifier name, Object value) throws IOException, IllegalArgumentException { + delegate.leafNode(name, value); } - return builder.toInstance(); - } + @Override + public void startLeafSet(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + delegate.startLeafSet(name, childSizeHint); + } - private Node parseErrorInfo(final String errorInfo) { - if (Strings.isNullOrEmpty(errorInfo)) { - return null; + @Override + public void leafSetEntryNode(Object value) throws IOException, IllegalArgumentException { + delegate.leafSetEntryNode(value); } - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setCoalescing(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); + @Override + public void startContainerNode(NodeIdentifier name, int childSizeHint) throws IOException, + IllegalArgumentException { + delegate.startContainerNode(name, childSizeHint); + } - // 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. + @Override + public void startUnkeyedList(NodeIdentifier name, int childSizeHint) throws IOException, + IllegalArgumentException { + delegate.startUnkeyedList(name, childSizeHint); + } - String errorInfoWithRoot = new StringBuilder("") - .append(errorInfo).append("").toString(); + @Override + public void startUnkeyedListItem(NodeIdentifier name, int childSizeHint) throws IOException, + IllegalStateException { + delegate.startUnkeyedListItem(name, childSizeHint); + } - Document doc = null; - 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. + @Override + public void startMapNode(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + delegate.startMapNode(name, childSizeHint); + } - LOG.warn("Error parsing restconf error-info, \"{}\", as XML", errorInfo, e); - return null; + @Override + public void startMapEntryNode(NodeIdentifierWithPredicates identifier, int childSizeHint) throws IOException, + IllegalArgumentException { + delegate.startMapEntryNode(identifier, childSizeHint); } - Node errorInfoNode = XmlDocumentUtils.toDomNode(doc); + @Override + public void startOrderedMapNode(NodeIdentifier name, int childSizeHint) throws IOException, + IllegalArgumentException { + delegate.startOrderedMapNode(name, childSizeHint); + } - if (errorInfoNode instanceof CompositeNode) { - CompositeNode compositeNode = (CompositeNode) XmlDocumentUtils.toDomNode(doc); + @Override + public void startChoiceNode(NodeIdentifier name, int childSizeHint) throws IOException, + IllegalArgumentException { + delegate.startChoiceNode(name, childSizeHint); + } - // 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. + @Override + public void startAugmentationNode(AugmentationIdentifier identifier) throws IOException, + IllegalArgumentException { + delegate.startAugmentationNode(identifier); + } - ImmutableList.Builder> childNodes = ImmutableList.builder(); - for (Entry>> entry : compositeNode.entrySet()) { - childNodes.addAll(entry.getValue()); - } + @Override + public void anyxmlNode(NodeIdentifier name, Object value) throws IOException, IllegalArgumentException { + delegate.anyxmlNode(name, value); + } - errorInfoNode = ImmutableCompositeNode.create(ERROR_INFO_QNAME, childNodes.build()); + @Override + public void endNode() throws IOException, IllegalStateException { + delegate.endNode(); } - return errorInfoNode; - } + @Override + public void close() throws IOException { + delegate.close(); + } - private void addLeaf(final CompositeNodeBuilder builder, final QName qname, - final String value) { - if (!Strings.isNullOrEmpty(value)) { - builder.addLeaf(qname, value); + @Override + public void flush() throws IOException { + delegate.flush(); } } }