* 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 com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableMap;
-import com.google.common.xml.XmlEscapers;
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 javax.xml.transform.stax.StAXSource;
import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode;
-import org.opendaylight.yangtools.util.xml.UntrustedXML;
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.LeafNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.ListEntryNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.ListNodeDataWithSchema;
+import org.opendaylight.yangtools.yang.data.util.OperationAsContainer;
import org.opendaylight.yangtools.yang.data.util.ParserStreamUtils;
-import org.opendaylight.yangtools.yang.data.util.RpcAsContainer;
import org.opendaylight.yangtools.yang.data.util.SimpleNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.YangModeledAnyXmlNodeDataWithSchema;
import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
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;
*/
public static XmlParserStream create(final NormalizedNodeStreamWriter writer, final XmlCodecFactory codecs,
final SchemaNode parentNode, final boolean strictParsing) {
- if (parentNode instanceof RpcDefinition) {
- return new XmlParserStream(writer, codecs, new RpcAsContainer((RpcDefinition) parentNode), strictParsing);
+ final DataSchemaNode parent;
+ if (parentNode instanceof DataSchemaNode) {
+ parent = (DataSchemaNode) parentNode;
+ } else if (parentNode instanceof OperationDefinition) {
+ parent = OperationAsContainer.of((OperationDefinition) parentNode);
+ } else {
+ throw new IllegalArgumentException("Illegal parent node " + parentNode);
}
- checkArgument(parentNode instanceof DataSchemaNode, "Instance of DataSchemaNode class awaited.");
- return new XmlParserStream(writer, codecs, (DataSchemaNode) parentNode, strictParsing);
+ return new XmlParserStream(writer, codecs, parent, strictParsing);
}
/**
* 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
*/
public XmlParserStream parse(final XMLStreamReader reader) throws XMLStreamException, URISyntaxException,
- IOException, ParserConfigurationException, SAXException {
+ IOException, SAXException {
if (reader.hasNext()) {
reader.nextTag();
- final AbstractNodeDataWithSchema nodeDataWithSchema;
+ final AbstractNodeDataWithSchema<?> nodeDataWithSchema;
if (parentNode instanceof ContainerSchemaNode) {
- nodeDataWithSchema = new ContainerNodeDataWithSchema(parentNode);
+ nodeDataWithSchema = new ContainerNodeDataWithSchema((ContainerSchemaNode) parentNode);
} else if (parentNode instanceof ListSchemaNode) {
- nodeDataWithSchema = new ListNodeDataWithSchema(parentNode);
+ nodeDataWithSchema = new ListNodeDataWithSchema((ListSchemaNode) parentNode);
} else if (parentNode instanceof YangModeledAnyXmlSchemaNode) {
nodeDataWithSchema = new YangModeledAnyXmlNodeDataWithSchema((YangModeledAnyXmlSchemaNode) parentNode);
} else if (parentNode instanceof AnyXmlSchemaNode) {
- nodeDataWithSchema = new AnyXmlNodeDataWithSchema(parentNode);
+ nodeDataWithSchema = new AnyXmlNodeDataWithSchema((AnyXmlSchemaNode) parentNode);
} else if (parentNode instanceof LeafSchemaNode) {
- nodeDataWithSchema = new LeafNodeDataWithSchema(parentNode);
+ nodeDataWithSchema = new LeafNodeDataWithSchema((LeafSchemaNode) parentNode);
} else if (parentNode instanceof LeafListSchemaNode) {
- nodeDataWithSchema = new LeafListNodeDataWithSchema(parentNode);
+ nodeDataWithSchema = new LeafListNodeDataWithSchema((LeafListSchemaNode) parentNode);
} else {
throw new IllegalStateException("Unsupported schema node type " + parentNode.getClass() + ".");
}
* 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
- public XmlParserStream traverse(final DOMSource src) throws XMLStreamException, URISyntaxException,
- IOException, ParserConfigurationException, SAXException {
+ public XmlParserStream traverse(final DOMSource src) throws XMLStreamException, URISyntaxException, IOException,
+ SAXException {
return parse(new DOMSourceXMLStreamReader(src));
}
- private static Map<QName, String> getElementAttributes(final XMLStreamReader in) {
+ 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<>();
return ImmutableMap.copyOf(attributes);
}
- 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("\">");
-
- while (in.hasNext()) {
- final int eventType = in.next();
-
- if (eventType == XMLStreamConstants.START_ELEMENT) {
- sb.append('<').append(in.getLocalName()).append('>');
- } else if (eventType == XMLStreamConstants.END_ELEMENT) {
- sb.append("</").append(in.getLocalName()).append('>');
-
- if (in.getLocalName().equals(anyXmlElementName)) {
- break;
+ 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 if (eventType == XMLStreamConstants.CHARACTERS) {
- sb.append(XmlEscapers.xmlContentEscaper().escape(in.getText()));
- }
+ };
+ } else {
+ inWrapper = in;
}
- return sb.toString();
+ 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 {
+ private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema<?> parent, final String rootElement)
+ throws XMLStreamException, URISyntaxException {
if (!in.hasNext()) {
return;
}
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();
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()) {
- checkState(!strictParsing, "Schema for node with name %s and namespace %s doesn't exist at %s",
+ checkState(!strictParsing, "Schema for node with name %s and namespace %s does not exist at %s",
xmlElementName, xmlElementNamespace, parentSchema.getPath());
skipUnknownNode(in);
continue;
}
- read(in, ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes), rootElement);
+ read(in, ((CompositeNodeDataWithSchema<?>) parent).addChild(childDataSchemaNodes), rootElement);
}
break;
case XMLStreamConstants.END_ELEMENT:
}
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 {
+
+ 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;
+ final SimpleNodeDataWithSchema<?> parentSimpleNode = (SimpleNodeDataWithSchema<?>) parent;
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());
}
checkArgument(node instanceof TypedDataSchemaNode);
- return codecs.codecFor((TypedDataSchemaNode) node).parseValue(namespaceCtx, value);
+ checkArgument(value instanceof String);
+ return codecs.codecFor((TypedDataSchemaNode) node).parseValue(namespaceCtx, (String) value);
}
- private static AbstractNodeDataWithSchema newEntryNode(final AbstractNodeDataWithSchema parent) {
- final AbstractNodeDataWithSchema newChild;
+ private static AbstractNodeDataWithSchema<?> newEntryNode(final AbstractNodeDataWithSchema<?> parent) {
+ final AbstractNodeDataWithSchema<?> newChild;
if (parent instanceof ListNodeDataWithSchema) {
- newChild = new ListEntryNodeDataWithSchema(parent.getSchema());
+ newChild = ListEntryNodeDataWithSchema.forSchema(((ListNodeDataWithSchema) parent).getSchema());
} else {
- newChild = new LeafListEntryNodeDataWithSchema(parent.getSchema());
+ verify(parent instanceof LeafListNodeDataWithSchema, "Unexpected parent %s", parent);
+ newChild = new LeafListEntryNodeDataWithSchema(((LeafListNodeDataWithSchema) parent).getSchema());
}
- ((CompositeNodeDataWithSchema) parent).addChild(newChild);
+ ((CompositeNodeDataWithSchema<?>) parent).addChild(newChild);
return newChild;
}