* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
-
package org.opendaylight.yangtools.yang.data.codec.xml;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.Beta;
-import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
-import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Deque;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
+import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.util.StreamReaderDelegate;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
-import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import javax.xml.transform.stax.StAXSource;
+import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.util.AbstractNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.AnyXmlNodeDataWithSchema;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
-import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
@Beta
@NotThreadSafe
public final class XmlParserStream implements Closeable, Flushable {
+ private static final Logger LOG = LoggerFactory.getLogger(XmlParserStream.class);
+ private static final String XML_STANDARD_VERSION = "1.0";
+ private static final String COM_SUN_TRANSFORMER =
+ "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
+
+ private static final TransformerFactory TRANSFORMER_FACTORY;
+
+ static {
+ TransformerFactory fa = TransformerFactory.newInstance();
+ if (!fa.getFeature(StAXSource.FEATURE)) {
+ LOG.warn("Platform-default TransformerFactory {} does not support StAXSource, attempting fallback to {}",
+ fa, COM_SUN_TRANSFORMER);
+ fa = TransformerFactory.newInstance(COM_SUN_TRANSFORMER, null);
+ if (!fa.getFeature(StAXSource.FEATURE)) {
+ throw new TransformerFactoryConfigurationError("No TransformerFactory supporting StAXResult found.");
+ }
+ }
+
+ TRANSFORMER_FACTORY = fa;
+ }
+
private final NormalizedNodeStreamWriter writer;
private final XmlCodecFactory codecs;
private final DataSchemaNode parentNode;
private XmlParserStream(final NormalizedNodeStreamWriter writer, final XmlCodecFactory codecs,
final DataSchemaNode parentNode, final boolean strictParsing) {
- this.writer = Preconditions.checkNotNull(writer);
- this.codecs = Preconditions.checkNotNull(codecs);
+ this.writer = requireNonNull(writer);
+ this.codecs = requireNonNull(codecs);
this.parentNode = parentNode;
this.strictParsing = strictParsing;
}
if (parentNode instanceof RpcDefinition) {
return new XmlParserStream(writer, codecs, new RpcAsContainer((RpcDefinition) parentNode), strictParsing);
}
- Preconditions.checkArgument(parentNode instanceof DataSchemaNode, "Instance of DataSchemaNode class awaited.");
+ checkArgument(parentNode instanceof DataSchemaNode, "Instance of DataSchemaNode class awaited.");
return new XmlParserStream(writer, codecs, (DataSchemaNode) parentNode, strictParsing);
}
- /**
- * Construct a new {@link XmlParserStream}.
- *
- * @deprecated Use {@link #create(NormalizedNodeStreamWriter, SchemaContext, SchemaNode)} instead.
- */
- @Deprecated
- public static XmlParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) {
- return create(writer, schemaContext, schemaContext);
- }
-
/**
* Utility method for use when caching {@link XmlCodecFactory} is not feasible. Users with high performance
* requirements should use {@link #create(NormalizedNodeStreamWriter, XmlCodecFactory, SchemaNode)} instead and
* @throws SAXException
* if an error occurs while parsing the value of an anyxml node
*/
+ // FIXME: 3.0.0 remove ParserConfigurationException
public XmlParserStream parse(final XMLStreamReader reader) throws XMLStreamException, URISyntaxException,
IOException, ParserConfigurationException, SAXException {
if (reader.hasNext()) {
return this;
}
- private static String readAnyXmlValue(final XMLStreamReader in) throws XMLStreamException {
- final StringBuilder sb = new StringBuilder();
- final String anyXmlElementName = in.getLocalName();
- sb.append('<').append(anyXmlElementName).append(" xmlns=\"").append(in.getNamespaceURI()).append("\">");
+ /**
+ * This method traverses a {@link DOMSource} and emits node events into a NormalizedNodeStreamWriter based on the
+ * YANG-modeled data contained in the source.
+ *
+ * @param src
+ * {@link DOMSource} to be traversed
+ * @return
+ * instance of XmlParserStream
+ * @throws XMLStreamException
+ * if a well-formedness error or an unexpected processing condition occurs while parsing the XML
+ * @throws URISyntaxException
+ * if the namespace URI of an XML element contains a syntax error
+ * @throws IOException
+ * if an error occurs while parsing the value of an anyxml node
+ * @throws ParserConfigurationException
+ * if an error occurs while parsing the value of an anyxml node
+ * @throws SAXException
+ * if an error occurs while parsing the value of an anyxml node
+ */
+ @Beta
+ // FIXME: 3.0.0 remove ParserConfigurationException
+ public XmlParserStream traverse(final DOMSource src) throws XMLStreamException, URISyntaxException,
+ IOException, ParserConfigurationException, SAXException {
+ return parse(new DOMSourceXMLStreamReader(src));
+ }
- while (in.hasNext()) {
- final int eventType = in.next();
+ private static ImmutableMap<QName, String> getElementAttributes(final XMLStreamReader in) {
+ checkState(in.isStartElement(), "Attributes can be extracted only from START_ELEMENT.");
+ final Map<QName, String> attributes = new LinkedHashMap<>();
- if (eventType == XMLStreamConstants.START_ELEMENT) {
- sb.append('<').append(in.getLocalName()).append('>');
- } else if (eventType == XMLStreamConstants.END_ELEMENT) {
- sb.append("</").append(in.getLocalName()).append('>');
+ for (int attrIndex = 0; attrIndex < in.getAttributeCount(); attrIndex++) {
+ String attributeNS = in.getAttributeNamespace(attrIndex);
- if (in.getLocalName().equals(anyXmlElementName)) {
- break;
- }
+ if (attributeNS == null) {
+ attributeNS = "";
+ }
- } else if (eventType == XMLStreamConstants.CHARACTERS) {
- sb.append(in.getText());
+ // Skip namespace definitions
+ if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attributeNS)) {
+ continue;
}
+
+ final QName qName = QName.create(URI.create(attributeNS), in.getAttributeLocalName(attrIndex));
+ attributes.put(qName, in.getAttributeValue(attrIndex));
}
- return sb.toString();
+ return ImmutableMap.copyOf(attributes);
+ }
+
+ private static Document readAnyXmlValue(final XMLStreamReader in) throws XMLStreamException {
+ // Underlying reader might return null when asked for version, however when such reader is plugged into
+ // Stax -> DOM transformer, it fails with NPE due to null version. Use default xml version in such case.
+ final XMLStreamReader inWrapper;
+ if (in.getVersion() == null) {
+ inWrapper = new StreamReaderDelegate(in) {
+ @Override
+ public String getVersion() {
+ final String ver = super.getVersion();
+ return ver != null ? ver : XML_STANDARD_VERSION;
+ }
+ };
+ } else {
+ inWrapper = in;
+ }
+
+ final DOMResult result = new DOMResult();
+ try {
+ TRANSFORMER_FACTORY.newTransformer().transform(new StAXSource(inWrapper), result);
+ } catch (final TransformerException e) {
+ throw new XMLStreamException("Unable to read anyxml value", e);
+ }
+ return (Document) result.getNode();
}
private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema parent, final String rootElement)
- throws XMLStreamException, URISyntaxException, ParserConfigurationException, SAXException, IOException {
+ throws XMLStreamException, URISyntaxException {
if (!in.hasNext()) {
return;
}
if (parent instanceof LeafNodeDataWithSchema || parent instanceof LeafListEntryNodeDataWithSchema) {
+ parent.setAttributes(getElementAttributes(in));
setValue(parent, in.getElementText().trim(), in.getNamespaceContext());
if (isNextEndDocument(in)) {
return;
return;
}
+ if (parent instanceof ListEntryNodeDataWithSchema || parent instanceof ContainerNodeDataWithSchema) {
+ parent.setAttributes(getElementAttributes(in));
+ }
+
if (parent instanceof LeafListNodeDataWithSchema || parent instanceof ListNodeDataWithSchema) {
String xmlElementName = in.getLocalName();
while (xmlElementName.equals(parent.getSchema().getQName().getLocalName())) {
read(in, newEntryNode(parent), rootElement);
- if (in.getEventType() == XMLStreamConstants.END_DOCUMENT) {
+ if (in.getEventType() == XMLStreamConstants.END_DOCUMENT
+ || in.getEventType() == XMLStreamConstants.END_ELEMENT) {
break;
}
xmlElementName = in.getLocalName();
return;
}
+ if (parent instanceof YangModeledAnyXmlSchemaNode) {
+ parent.setAttributes(getElementAttributes(in));
+ }
+
switch (in.nextTag()) {
case XMLStreamConstants.START_ELEMENT:
- final Set<String> namesakes = new HashSet<>();
+ // FIXME: why do we even need this tracker? either document it or remove it
+ final Set<Entry<String, String>> namesakes = new HashSet<>();
while (in.hasNext()) {
final String xmlElementName = in.getLocalName();
parentSchema = ((YangModeledAnyXmlSchemaNode) parentSchema).getSchemaOfAnyXmlData();
}
- if (!namesakes.add(xmlElementName)) {
+ final String xmlElementNamespace = in.getNamespaceURI();
+ if (!namesakes.add(new SimpleImmutableEntry<>(xmlElementNamespace, xmlElementName))) {
final Location loc = in.getLocation();
throw new IllegalStateException(String.format(
- "Duplicate element \"%s\" in XML input at: line %s column %s", xmlElementName,
- loc.getLineNumber(), loc.getColumnNumber()));
+ "Duplicate namespace \"%s\" element \"%s\" in XML input at: line %s column %s",
+ xmlElementNamespace, xmlElementName, loc.getLineNumber(), loc.getColumnNumber()));
}
- final String xmlElementNamespace = in.getNamespaceURI();
final Deque<DataSchemaNode> childDataSchemaNodes =
ParserStreamUtils.findSchemaNodeByNameAndNamespace(parentSchema, xmlElementName,
new URI(xmlElementNamespace));
if (childDataSchemaNodes.isEmpty()) {
- Preconditions.checkState(!strictParsing,
- "Schema for node with name %s and namespace %s doesn't exist.", xmlElementName,
- xmlElementNamespace);
+ checkState(!strictParsing, "Schema for node with name %s and namespace %s does not exist at %s",
+ xmlElementName, xmlElementNamespace, parentSchema.getPath());
skipUnknownNode(in);
continue;
}
}
private static boolean isNextEndDocument(final XMLStreamReader in) throws XMLStreamException {
- return in.next() == XMLStreamConstants.END_DOCUMENT;
+ return !in.hasNext() || in.next() == XMLStreamConstants.END_DOCUMENT;
}
private static boolean isAtElement(final XMLStreamReader in) {
in.nextTag();
}
- private void setValue(final AbstractNodeDataWithSchema parent, final String value, final NamespaceContext nsContext)
- throws ParserConfigurationException, SAXException, IOException {
- Preconditions.checkArgument(parent instanceof SimpleNodeDataWithSchema, "Node %s is not a simple type",
+
+ private void setValue(final AbstractNodeDataWithSchema parent, final Object value,
+ final NamespaceContext nsContext) {
+ checkArgument(parent instanceof SimpleNodeDataWithSchema, "Node %s is not a simple type",
parent.getSchema().getQName());
final SimpleNodeDataWithSchema parentSimpleNode = (SimpleNodeDataWithSchema) parent;
- Preconditions.checkArgument(parentSimpleNode.getValue() == null, "Node '%s' has already set its value to '%s'",
+ checkArgument(parentSimpleNode.getValue() == null, "Node '%s' has already set its value to '%s'",
parentSimpleNode.getSchema().getQName(), parentSimpleNode.getValue());
parentSimpleNode.setValue(translateValueByType(value, parentSimpleNode.getSchema(), nsContext));
}
- private Object translateValueByType(final String value, final DataSchemaNode node,
- final NamespaceContext namespaceCtx) throws IOException, SAXException, ParserConfigurationException {
+ private Object translateValueByType(final Object value, final DataSchemaNode node,
+ final NamespaceContext namespaceCtx) {
if (node instanceof AnyXmlSchemaNode) {
+
+ checkArgument(value instanceof Document);
/*
* FIXME: Figure out some YANG extension dispatch, which will
* reuse JSON parsing or XML parsing - anyxml is not well-defined in
* JSON.
*/
- final Document doc = UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(value)));
- doc.normalize();
-
- return new DOMSource(doc.getDocumentElement());
+ return new DOMSource(((Document) value).getDocumentElement());
}
- Preconditions.checkArgument(node instanceof TypedSchemaNode);
- return codecs.codecFor((TypedSchemaNode) node).parseValue(namespaceCtx, value);
+ checkArgument(node instanceof TypedDataSchemaNode);
+ checkArgument(value instanceof String);
+ return codecs.codecFor((TypedDataSchemaNode) node).parseValue(namespaceCtx, (String) value);
}
private static AbstractNodeDataWithSchema newEntryNode(final AbstractNodeDataWithSchema parent) {