X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fimpl%2Fcodec%2Fxml%2FXMLStreamNormalizedNodeStreamWriter.java;h=4cfe0c3c39ade04f3611c7306ce929953e03c829;hb=refs%2Fchanges%2F11%2F33611%2F2;hp=ddc8076d612cd2b4af752920655b9fed58729a4c;hpb=21afbfc198fae8785effc7db97debf5f91f8237d;p=yangtools.git diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XMLStreamNormalizedNodeStreamWriter.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XMLStreamNormalizedNodeStreamWriter.java index ddc8076d61..4cfe0c3c39 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XMLStreamNormalizedNodeStreamWriter.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/xml/XMLStreamNormalizedNodeStreamWriter.java @@ -7,18 +7,29 @@ */ package org.opendaylight.yangtools.yang.data.impl.codec.xml; -import static javax.xml.XMLConstants.DEFAULT_NS_PREFIX; - import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +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.stax.StAXResult; +import javax.xml.transform.stream.StreamResult; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.data.api.Node; 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.stream.NormalizedNodeStreamAttributeWriter; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker; import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; @@ -29,22 +40,26 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; -import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.w3c.dom.Element; /** * A {@link NormalizedNodeStreamWriter} which translates the events into an * {@link XMLStreamWriter}, resulting in a RFC 6020 XML encoding. */ -public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter { +public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNodeStreamAttributeWriter { + + private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance(); private final XMLStreamWriter writer; private final SchemaTracker tracker; private final XmlStreamUtils streamUtils; + private final RandomPrefix randomPrefix; private XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer, final SchemaContext context, final SchemaPath path) { this.writer = Preconditions.checkNotNull(writer); this.tracker = SchemaTracker.create(context, path); this.streamUtils = XmlStreamUtils.create(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, context); + randomPrefix = new RandomPrefix(); } /** @@ -63,6 +78,7 @@ public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNode * * @param writer Output {@link XMLStreamWriter} * @param context Associated {@link SchemaContext}. + * @param path path * * @return A new {@link NormalizedNodeStreamWriter} */ @@ -70,22 +86,22 @@ public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNode return new XMLStreamNormalizedNodeStreamWriter(writer, context, path); } - private void writeStartElement( QName qname) throws XMLStreamException { + private void writeStartElement(final QName qname) throws XMLStreamException { String ns = qname.getNamespace().toString(); - writer.writeStartElement(DEFAULT_NS_PREFIX, qname.getLocalName(), ns); + writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), ns); if(writer.getNamespaceContext() != null) { - String parentNs = writer.getNamespaceContext().getNamespaceURI(DEFAULT_NS_PREFIX); + String parentNs = writer.getNamespaceContext().getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX); if (!ns.equals(parentNs)) { writer.writeDefaultNamespace(ns); } } } - private void writeElement(final QName qname, final TypeDefinition type, final Object value) throws IOException { + private void writeElement(final QName qname, final SchemaNode schemaNode, final Object value) throws IOException { try { writeStartElement(qname); if (value != null) { - streamUtils.writeValue(writer, type, value); + streamUtils.writeValue(writer, schemaNode, value, qname.getModule()); } writer.writeEndElement(); } catch (XMLStreamException e) { @@ -93,11 +109,13 @@ public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNode } } - private void writeElement(final QName qname, final SchemaNode schemaNode, final Object value) throws IOException { + private void writeElement(final QName qname, final SchemaNode schemaNode, final Object value, final Map attributes) throws IOException { try { writeStartElement(qname); + + writeAttributes(attributes); if (value != null) { - streamUtils.writeValue(writer, schemaNode, value); + streamUtils.writeValue(writer, schemaNode, value, qname.getModule()); } writer.writeEndElement(); } catch (XMLStreamException e) { @@ -128,17 +146,75 @@ public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNode writeElement(schema.getQName(), schema, value); } + @Override + public void leafNode(final NodeIdentifier name, final Object value, final Map attributes) throws IOException { + final LeafSchemaNode schema = tracker.leafNode(name); + writeElement(schema.getQName(), schema, value, attributes); + } + + @Override + public void leafSetEntryNode(final QName name, final Object value, final Map attributes) throws IOException { + final LeafListSchemaNode schema = tracker.leafSetEntryNode(); + writeElement(schema.getQName(), schema, value, attributes); + } + + @Override + public void startContainerNode(final NodeIdentifier name, final int childSizeHint, final Map attributes) throws IOException { + startContainerNode(name, childSizeHint); + writeAttributes(attributes); + } + + @Override + public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint, final Map attributes) throws IOException { + startYangModeledAnyXmlNode(name, childSizeHint); + writeAttributes(attributes); + } + + @Override + public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint, final Map attributes) throws IOException { + startUnkeyedListItem(name, childSizeHint); + writeAttributes(attributes); + } + + @Override + public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint, final Map attributes) throws IOException { + startMapEntryNode(identifier, childSizeHint); + writeAttributes(attributes); + } + + private void writeAttributes(final Map attributes) throws IOException { + for (final Map.Entry qNameStringEntry : attributes.entrySet()) { + try { + final String namespace = qNameStringEntry.getKey().getNamespace().toString(); + + if(Strings.isNullOrEmpty(namespace)) { + writer.writeAttribute(qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue()); + } else { + final String prefix = randomPrefix.encodePrefix(qNameStringEntry.getKey().getNamespace()); + writer.writeAttribute(prefix, namespace, qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue()); + } + } catch (final XMLStreamException e) { + throw new IOException("Unable to emit attribute " + qNameStringEntry, e); + } + } + } + @Override public void startLeafSet(final NodeIdentifier name, final int childSizeHint) { tracker.startLeafSet(name); } @Override - public void leafSetEntryNode(final Object value) throws IOException { + public void leafSetEntryNode(final QName name, final Object value) throws IOException { final LeafListSchemaNode schema = tracker.leafSetEntryNode(); writeElement(schema.getQName(), schema, value); } + @Override + public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) { + tracker.startLeafSet(name); + } + @Override public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException { final SchemaNode schema = tracker.startContainerNode(name); @@ -183,15 +259,43 @@ public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNode @Override public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException { final AnyXmlSchemaNode schema = tracker.anyxmlNode(name); - final QName qname = schema.getQName(); - try { - writeStartElement(qname); - if (value != null) { - streamUtils.writeValue(writer, (Node)value, schema); + if (value != null) { + Preconditions.checkArgument(value instanceof DOMSource, "AnyXML value must be DOMSource, not %s", value); + final QName qname = schema.getQName(); + final DOMSource domSource = (DOMSource) value; + Preconditions.checkNotNull(domSource.getNode()); + Preconditions.checkArgument(domSource.getNode().getNodeName().equals(qname.getLocalName())); + Preconditions.checkArgument(domSource.getNode().getNamespaceURI().equals(qname.getNamespace().toString())); + try { + // TODO can the transformer be a constant ? is it thread safe ? + final Transformer transformer = TRANSFORMER_FACTORY.newTransformer(); + // Writer has to be wrapped in a wrapper that ignores endDocument event + // EndDocument event forbids any other modification to the writer so a nested anyXml breaks serialization + transformer.transform(domSource, new StAXResult(new DelegateWriterNoEndDoc(writer))); + } catch (final TransformerException e) { + throw new IOException("Unable to transform anyXml(" + name + ") value: " + value, e); } - writer.writeEndElement(); - } catch (XMLStreamException e) { - throw new IOException("Failed to emit element", e); + } + } + + @Override + public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint) throws IOException { + final SchemaNode schema = tracker.startYangModeledAnyXmlNode(name); + startElement(schema.getQName()); + } + + public static String toString(final Element xml) { + try { + final Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + final StreamResult result = new StreamResult(new StringWriter()); + final DOMSource source = new DOMSource(xml); + transformer.transform(source, result); + + return result.getWriter().toString(); + } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) { + throw new RuntimeException("Unable to serialize xml element " + xml, e); } } @@ -232,4 +336,181 @@ public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNode throw new IOException("Failed to flush writer", e); } } + + /** + * Delegate writer that ignores writeEndDocument event. Used for AnyXml serialization. + */ + private static final class DelegateWriterNoEndDoc implements XMLStreamWriter { + private final XMLStreamWriter writer; + + public DelegateWriterNoEndDoc(final XMLStreamWriter writer) { + this.writer = writer; + } + + @Override + public void writeStartElement(final String localName) throws XMLStreamException { + writer.writeStartElement(localName); + } + + @Override + public void writeStartElement(final String namespaceURI, final String localName) throws XMLStreamException { + writer.writeStartElement(namespaceURI, localName); + } + + @Override + public void writeStartElement(final String prefix, final String localName, final String namespaceURI) throws XMLStreamException { + writer.writeStartElement(prefix, localName, namespaceURI); + } + + @Override + public void writeEmptyElement(final String namespaceURI, final String localName) throws XMLStreamException { + writer.writeEmptyElement(namespaceURI, localName); + } + + @Override + public void writeEmptyElement(final String prefix, final String localName, final String namespaceURI) throws XMLStreamException { + writer.writeEmptyElement(prefix, localName, namespaceURI); + } + + @Override + public void writeEmptyElement(final String localName) throws XMLStreamException { + writer.writeEmptyElement(localName); + } + + @Override + public void writeEndElement() throws XMLStreamException { + writer.writeEndElement(); + + } + + @Override + public void writeEndDocument() throws XMLStreamException { + // End document is disabled + } + + @Override + public void close() throws XMLStreamException { + writer.close(); + } + + @Override + public void flush() throws XMLStreamException { + writer.flush(); + } + + @Override + public void writeAttribute(final String localName, final String value) throws XMLStreamException { + writer.writeAttribute(localName, value); + } + + @Override + public void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value) throws XMLStreamException { + writer.writeAttribute(prefix, namespaceURI, localName, value); + } + + @Override + public void writeAttribute(final String namespaceURI, final String localName, final String value) throws XMLStreamException { + writer.writeAttribute(namespaceURI, localName, value); + } + + @Override + public void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException { + // Workaround for default namespace + // If a namespace is not prefixed, it is is still treated as prefix namespace. This results in the NamespaceSupport class ignoring the namespace since xmlns is not a valid prefix + // Write the namespace at least as an attribute + // TODO this is a hotfix, the transformer itself should write namespaces passing the namespace in writeStartElement method + if (prefix.equals("xml") || prefix.equals("xmlns")) { + writer.writeAttribute(prefix, namespaceURI); + } else { + writer.writeNamespace(prefix, namespaceURI); + } + } + + @Override + public void writeDefaultNamespace(final String namespaceURI) throws XMLStreamException { + writer.writeDefaultNamespace(namespaceURI); + } + + @Override + public void writeComment(final String data) throws XMLStreamException { + writer.writeComment(data); + } + + @Override + public void writeProcessingInstruction(final String target) throws XMLStreamException { + writer.writeProcessingInstruction(target); + } + + @Override + public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException { + writer.writeProcessingInstruction(target, data); + } + + @Override + public void writeCData(final String data) throws XMLStreamException { + writer.writeCData(data); + } + + @Override + public void writeDTD(final String dtd) throws XMLStreamException { + writer.writeDTD(dtd); + } + + @Override + public void writeEntityRef(final String name) throws XMLStreamException { + writer.writeEntityRef(name); + } + + @Override + public void writeStartDocument() throws XMLStreamException { + } + + @Override + public void writeStartDocument(final String version) throws XMLStreamException { + } + + @Override + public void writeStartDocument(final String encoding, final String version) throws XMLStreamException { + } + + @Override + public void writeCharacters(final String text) throws XMLStreamException { + writer.writeCharacters(text); + } + + @Override + public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException { + writer.writeCharacters(text, start, len); + } + + @Override + public String getPrefix(final String uri) throws XMLStreamException { + return writer.getPrefix(uri); + } + + @Override + public void setPrefix(final String prefix, final String uri) throws XMLStreamException { + // Disabled since it causes exceptions in the underlying writer + } + + @Override + public void setDefaultNamespace(final String uri) throws XMLStreamException { + writer.setDefaultNamespace(uri); + } + + @Override + public void setNamespaceContext(final NamespaceContext context) throws XMLStreamException { + writer.setNamespaceContext(context); + } + + @Override + public NamespaceContext getNamespaceContext() { + return writer.getNamespaceContext(); + } + + @Override + public Object getProperty(final String name) { + return writer.getProperty(name); + } + } }