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=456354bbf0eac9c9eab31c0d72800f5f3a6462e6;hb=9ba2b4eca79bcc0e78099b133296801c8d45a6c4;hpb=b80124e3f7b11cf2f5e5bd4a6b033d855ff4d0d4 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 456354bbf0..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,266 +8,454 @@ package org.opendaylight.controller.sal.rest.impl; +import com.google.common.base.Charsets; +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.core.Response.Status; 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 static org.opendaylight.controller.sal.rest.api.Draft02.RestConfModule.*; - +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; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.gson.stream.JsonWriter; /** - * This class defines an ExceptionMapper that handles RestconfDocumentedExceptions thrown by - * resource implementations and translates appropriately to restconf error response as defined in - * the RESTCONF RFC draft. + * This class defines an ExceptionMapper that handles RestconfDocumentedExceptions thrown by resource implementations + * and translates appropriately to restconf error response as defined in the RESTCONF RFC draft. * * @author Thomas Pantelis */ @Provider public class RestconfDocumentedExceptionMapper implements ExceptionMapper { - private final static Logger LOG = LoggerFactory.getLogger( RestconfDocumentedExceptionMapper.class ); + private final static Logger LOG = LoggerFactory.getLogger(RestconfDocumentedExceptionMapper.class); + + private static final XMLOutputFactory XML_FACTORY; + + static { + XML_FACTORY = XMLOutputFactory.newFactory(); + XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + } @Context private HttpHeaders headers; @Override - public Response toResponse( RestconfDocumentedException exception ) { - - LOG.debug( "In toResponse: {}", exception.getMessage() ); + public Response toResponse(final RestconfDocumentedException exception) { - // Default to the content type if there's no Accept header + LOG.debug("In toResponse: {}", exception.getMessage()); - MediaType mediaType = headers.getMediaType(); + final List accepts = headers.getAcceptableMediaTypes(); + accepts.remove(MediaType.WILDCARD_TYPE); - List accepts = headers.getAcceptableMediaTypes(); + LOG.debug("Accept headers: {}", accepts); - LOG.debug( "Accept headers: {}", accepts ); - - if( accepts != null && accepts.size() > 0 ) { - mediaType = accepts.get( 0 ); // just pick the first one + final MediaType mediaType; + if (accepts != null && accepts.size() > 0) { + mediaType = accepts.get(0); // just pick the first one + } else { + // Default to the content type if there's no Accept header + mediaType = MediaType.APPLICATION_JSON_TYPE; } - LOG.debug( "Using MediaType: {}", mediaType ); + LOG.debug("Using MediaType: {}", mediaType); - List errors = exception.getErrors(); - if( errors.isEmpty() ) { + 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 // single space char in the entity. - return Response.status( exception.getStatus() ) - .type( MediaType.TEXT_PLAIN_TYPE ) - .entity( " " ).build(); + return Response.status(exception.getStatus()).type(MediaType.TEXT_PLAIN_TYPE).entity(" ").build(); } - Status status = errors.iterator().next().getErrorTag().getStatusCode(); + final int status = errors.iterator().next().getErrorTag().getStatusCode(); - ControllerContext context = ControllerContext.getInstance(); - DataNodeContainer errorsSchemaNode = (DataNodeContainer)context.getRestconfModuleErrorsSchemaNode(); + final ControllerContext context = ControllerContext.getInstance(); + final DataNodeContainer errorsSchemaNode = (DataNodeContainer) context.getRestconfModuleErrorsSchemaNode(); - if( errorsSchemaNode == null ) { - return Response.status( status ) - .type( MediaType.TEXT_PLAIN_TYPE ) - .entity( exception.getMessage() ).build(); + if (errorsSchemaNode == null) { + return Response.status(status).type(MediaType.TEXT_PLAIN_TYPE).entity(exception.getMessage()).build(); } - ImmutableList.Builder> 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 ); + if (mediaType.getSubtype().endsWith("json")) { + responseBody = toJsonResponseBody(errContext, errorsSchemaNode); + } else { + responseBody = toXMLResponseBody(errContext, errorsSchemaNode); + } + + return Response.status(status).type(mediaType).entity(responseBody).build(); + } + + 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()); } - else { - responseBody = toXMLResponseBody( errorsNode, errorsSchemaNode ); + + 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())); } - return Response.status( status ).type( mediaType ).entity( responseBody ).build(); + // TODO : find how could we add possible "error-path" + + return errNodeValues.build(); } - private Object toJsonResponseBody( ImmutableCompositeNode errorsNode, - DataNodeContainer errorsSchemaNode ) { + private Object toJsonResponseBody(final NormalizedNodeContext errorsNode, final DataNodeContainer errorsSchemaNode) { - JsonMapper jsonMapper = new JsonMapper(); + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + NormalizedNode data = errorsNode.getData(); + final InstanceIdentifierContext context = errorsNode.getInstanceIdentifierContext(); + final DataSchemaNode schema = (DataSchemaNode) context.getSchemaNode(); - Object responseBody = null; - try { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - JsonWriter writer = new JsonWriter( new OutputStreamWriter( outStream, "UTF-8" ) ); - writer.setIndent( " " ); + SchemaPath path = context.getSchemaNode().getPath(); + final OutputStreamWriter outputWriter = new OutputStreamWriter(outStream, Charsets.UTF_8); + if (data == null) { + throw new RestconfDocumentedException(Response.Status.NOT_FOUND); + } - jsonMapper.write( writer, errorsNode, errorsSchemaNode, null ); - writer.flush(); + 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); + } + } + }; - responseBody = outStream.toString( "UTF-8" ); + 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( IOException e ) { - LOG.error( "Error writing error response body", e ); + catch (final IOException e) { + LOG.warn("Error writing error response body", e); } - return responseBody; + return outStream.toString(); + } - private Object toXMLResponseBody( ImmutableCompositeNode errorsNode, - 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 ); + xmlWriter = XML_FACTORY.createXMLStreamWriter(outStream, "UTF-8"); + } catch (final XMLStreamException e) { + throw new IllegalStateException(e); + } catch (final FactoryConfigurationError e) { + throw new IllegalStateException(e); } - catch( TransformerException | UnsupportedDataTypeException | UnsupportedEncodingException e ) { - LOG.error( "Error writing error response body", e ); + NormalizedNode data = errorsNode.getData(); + SchemaPath schemaPath = pathContext.getSchemaNode().getPath(); + + boolean isDataRoot = false; + if (SchemaPath.ROOT.equals(schemaPath)) { + isDataRoot = true; + } else { + schemaPath = schemaPath.getParent(); } - return responseBody; - } - - private String documentToString( 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( 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( 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 ) ) ); + @Override + public void startMapNode(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException { + delegate.startMapNode(name, childSizeHint); } - 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, \"" + errorInfo + "\", as XML: " + - e.toString() ); - 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( CompositeNodeBuilder builder, QName qname, - String value ) { - if( !Strings.isNullOrEmpty( value ) ) { - builder.addLeaf( qname, value ); + @Override + public void flush() throws IOException { + delegate.flush(); } } }