*/
package org.opendaylight.yangtools.yang.data.codec.xml;
-import com.google.common.base.Preconditions;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Strings;
import java.io.IOException;
import java.io.StringWriter;
+import java.net.URI;
import java.util.Map;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stream.StreamResult;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.common.QName;
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.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.slf4j.Logger;
*/
public abstract class XMLStreamNormalizedNodeStreamWriter<T> implements NormalizedNodeStreamAttributeWriter {
private static final Logger LOG = LoggerFactory.getLogger(XMLStreamNormalizedNodeStreamWriter.class);
- private static final String COM_SUN_TRANSFORMER = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
+ private static final String COM_SUN_TRANSFORMER =
+ "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
+
+ static final TransformerFactory TRANSFORMER_FACTORY;
- private static final TransformerFactory TRANSFORMER_FACTORY;
static {
- TransformerFactory f = TransformerFactory.newInstance();
- if (!f.getFeature(StAXResult.FEATURE)) {
+ TransformerFactory fa = TransformerFactory.newInstance();
+ if (!fa.getFeature(StAXResult.FEATURE)) {
LOG.warn("Platform-default TransformerFactory {} does not support StAXResult, attempting fallback to {}",
- f, COM_SUN_TRANSFORMER);
- f = TransformerFactory.newInstance(COM_SUN_TRANSFORMER, null);
- if (!f.getFeature(StAXResult.FEATURE)) {
+ fa, COM_SUN_TRANSFORMER);
+ fa = TransformerFactory.newInstance(COM_SUN_TRANSFORMER, null);
+ if (!fa.getFeature(StAXResult.FEATURE)) {
throw new TransformerFactoryConfigurationError("No TransformerFactory supporting StAXResult found.");
}
}
- TRANSFORMER_FACTORY = f;
+ TRANSFORMER_FACTORY = fa;
}
- final XMLStreamWriter writer;
+ private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
+
+ private final @NonNull XMLStreamWriter writer;
+ private final RandomPrefix prefixes;
XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer) {
- this.writer = Preconditions.checkNotNull(writer);
+ this.writer = requireNonNull(writer);
+ this.prefixes = new RandomPrefix(writer.getNamespaceContext());
}
/**
* @param context Associated {@link SchemaContext}.
* @return A new {@link NormalizedNodeStreamWriter}
*/
- public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context) {
- return create( writer, context, SchemaPath.ROOT);
+ public static @NonNull NormalizedNodeStreamWriter create(final XMLStreamWriter writer,
+ final SchemaContext context) {
+ return create(writer, context, context);
}
/**
- * Create a new writer with the specified context and rooted in the specified schema path
+ * Create a new writer with the specified context and rooted at the specified node.
*
* @param writer Output {@link XMLStreamWriter}
* @param context Associated {@link SchemaContext}.
- * @param path path
+ * @param rootNode Root node
+ * @return A new {@link NormalizedNodeStreamWriter}
+ */
+ public static @NonNull NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context,
+ final DataNodeContainer rootNode) {
+ return new SchemaAwareXMLStreamNormalizedNodeStreamWriter(writer, context, SchemaTracker.create(rootNode));
+ }
+
+ /**
+ * Create a new writer with the specified context and rooted in the specified schema path.
*
+ * @param writer Output {@link XMLStreamWriter}
+ * @param context Associated {@link SchemaContext}.
+ * @param path path
* @return A new {@link NormalizedNodeStreamWriter}
*/
- public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context,
+ public static @NonNull NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context,
final SchemaPath path) {
- return SchemaAwareXMLStreamNormalizedNodeStreamWriter.newInstance(writer, context, path);
+ return new SchemaAwareXMLStreamNormalizedNodeStreamWriter(writer, context, SchemaTracker.create(context, path));
}
/**
*
* @return A new {@link NormalizedNodeStreamWriter}
*/
- public static NormalizedNodeStreamWriter createSchemaless(final XMLStreamWriter writer) {
- return SchemalessXMLStreamNormalizedNodeStreamWriter.newInstance(writer);
+ public static @NonNull NormalizedNodeStreamWriter createSchemaless(final XMLStreamWriter writer) {
+ return new SchemalessXMLStreamNormalizedNodeStreamWriter(writer);
}
- abstract void writeAttributes(@Nonnull final Map<QName, String> attributes) throws IOException;
+ abstract void writeValue(@NonNull XMLStreamWriter xmlWriter, QName qname, @NonNull Object value, T context)
+ throws IOException, XMLStreamException;
- abstract void writeValue(final XMLStreamWriter xmlWriter, final QName qname,
- @Nonnull final Object value, T context) throws IOException, XMLStreamException;
+ abstract void startList(NodeIdentifier name);
+
+ abstract void startListItem(PathArgument name) throws IOException;
+
+ private void writeAttributes(final @NonNull Map<QName, String> attributes) throws IOException {
+ for (final Entry<QName, String> entry : attributes.entrySet()) {
+ try {
+ final QName qname = entry.getKey();
+ final String namespace = qname.getNamespace().toString();
+
+ if (!Strings.isNullOrEmpty(namespace)) {
+ final String prefix = getPrefix(qname.getNamespace(), namespace);
+ writer.writeAttribute(prefix, namespace, qname.getLocalName(), entry.getValue());
+ } else {
+ writer.writeAttribute(qname.getLocalName(), entry.getValue());
+ }
+ } catch (final XMLStreamException e) {
+ throw new IOException("Unable to emit attribute " + entry, e);
+ }
+ }
+ }
- abstract void startList(final NodeIdentifier name);
+ private String getPrefix(final URI uri, final String str) throws XMLStreamException {
+ final String prefix = writer.getPrefix(str);
+ if (prefix != null) {
+ return prefix;
+ }
- abstract void startListItem(final PathArgument name) throws IOException;
+ // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
+ // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
+ // on that having been done.
+ if (BROKEN_NAMESPACES.add(str)) {
+ LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
+ }
- abstract void endNode(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException;
+ return prefixes.encodePrefix(uri);
+ }
private void writeStartElement(final QName qname) throws XMLStreamException {
- String ns = qname.getNamespace().toString();
+ final String ns = qname.getNamespace().toString();
+ final NamespaceContext context = writer.getNamespaceContext();
+ final boolean needDefaultNs;
+ if (context != null) {
+ final String parentNs = context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
+ needDefaultNs = !ns.equals(parentNs);
+ } else {
+ needDefaultNs = false;
+ }
+
writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), ns);
- if (writer.getNamespaceContext() != null) {
- String parentNs = writer.getNamespaceContext().getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
- if (!ns.equals(parentNs)) {
- writer.writeDefaultNamespace(ns);
- }
+ if (needDefaultNs) {
+ writer.writeDefaultNamespace(ns);
}
}
- void writeElement(final QName qname, final Object value, @Nullable final Map<QName, String> attributes,
+ final void writeElement(final QName qname, final Object value, final @Nullable Map<QName, String> attributes,
final T context) throws IOException {
try {
writeStartElement(qname);
- writeAttributes(attributes);
+ if (attributes != null) {
+ writeAttributes(attributes);
+ }
if (value != null) {
writeValue(writer, qname, value, context);
}
}
}
- void startElement(final QName qname) throws IOException {
+ final void startElement(final QName qname) throws IOException {
try {
writeStartElement(qname);
} catch (XMLStreamException e) {
}
}
- void anyxmlNode(final QName qname, final Object value) throws IOException {
+ final void endElement() throws IOException {
+ try {
+ writer.writeEndElement();
+ } catch (XMLStreamException e) {
+ throw new IOException("Failed to end element", e);
+ }
+ }
+
+ final void anyxmlNode(final QName qname, final Object value) throws IOException {
if (value != null) {
- Preconditions.checkArgument(value instanceof DOMSource, "AnyXML value must be DOMSource, not %s", value);
+ checkArgument(value instanceof DOMSource, "AnyXML value must be DOMSource, not %s", value);
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()));
+ requireNonNull(domSource.getNode());
+ checkArgument(domSource.getNode().getNodeName().equals(qname.getLocalName()));
+ 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
+ // 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(" + qname + ") value: " + value, e);
writeAttributes(attributes);
}
+ @Override
+ public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
+ startListItem(name);
+ }
+
@Override
public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint,
final Map<QName, String> attributes) throws IOException {
}
@Override
- public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) {
- startList(name);
+ public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
+ throws IOException {
+ startListItem(identifier);
}
@Override
- public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
- startListItem(name);
+ public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) {
+ startList(name);
}
@Override
startList(name);
}
- @Override
- public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
- throws IOException {
- startListItem(identifier);
- }
-
@Override
public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) {
startList(name);
public static String toString(final Element xml) {
try {
- final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ final Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
final StreamResult result = new StreamResult(new StringWriter());
transformer.transform(source, result);
return result.getWriter().toString();
- } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
- throw new RuntimeException("Unable to serialize xml element " + xml, e);
- }
- }
-
- @Override
- public final void endNode() throws IOException {
- try {
- endNode(writer);
- } catch (XMLStreamException e) {
- throw new IOException("Failed to end element", e);
+ } catch (IllegalArgumentException | TransformerException e) {
+ throw new IllegalStateException("Unable to serialize xml element " + xml, e);
}
}
private static final class DelegateWriterNoEndDoc implements XMLStreamWriter {
private final XMLStreamWriter writer;
- public DelegateWriterNoEndDoc(final XMLStreamWriter writer) {
+ DelegateWriterNoEndDoc(final XMLStreamWriter writer) {
this.writer = writer;
}