Use XMLStreamException for reporting parsing errors
[yangtools.git] / yang / yang-data-codec-xml / src / main / java / org / opendaylight / yangtools / yang / data / codec / xml / XmlParserStream.java
index 68039422c69653697463423be72f81eb2a0e7f23..c613ce7e9c8c4ae77c838fa600a351334059f536 100644 (file)
@@ -13,6 +13,7 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import java.io.Closeable;
 import java.io.Flushable;
@@ -25,10 +26,10 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
-import javax.xml.stream.Location;
 import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
@@ -40,7 +41,9 @@ 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.rfc7952.model.api.AnnotationSchemaNode;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
 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;
@@ -61,6 +64,7 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 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.Module;
 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
@@ -74,9 +78,26 @@ import org.xml.sax.SAXException;
  * This class provides functionality for parsing an XML source containing YANG-modeled data. It disallows multiple
  * instances of the same element except for leaf-list and list entries. It also expects that the YANG-modeled data in
  * the XML source are wrapped in a root element. This class is NOT thread-safe.
+ *
+ * <p>
+ * Due to backwards compatibility reasons, RFC7952 metadata emitted by this parser may include key QNames with empty URI
+ * (as exposed via {@link #LEGACY_ATTRIBUTE_NAMESPACE}) as their QNameModule. These indicate an unqualified XML
+ * attribute and their value can be assumed to be a String. Furthermore, this extends to qualified attributes, which
+ * uses the proper namespace, but will not bind to a proper module revision -- these need to be reconciled with a
+ * particular SchemaContext and are expected to either be fully decoded, or contain a String value. Handling of such
+ * annotations is at the discretion of the user encountering it: preferred way of handling is to either filter or
+ * normalize them to proper QNames/values when encountered. This caveat will be removed in a future version.
  */
 @Beta
 public final class XmlParserStream implements Closeable, Flushable {
+    /**
+     * {@link QNameModule} for use with legacy XML attributes.
+     * @deprecated The use on this namespace is discouraged and users are strongly encouraged to proper RFC7952 metadata
+     *             annotations.
+     */
+    @Deprecated
+    public static final QNameModule LEGACY_ATTRIBUTE_NAMESPACE = QNameModule.create(URI.create("")).intern();
+
     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 =
@@ -237,24 +258,44 @@ public final class XmlParserStream implements Closeable, Flushable {
         return parse(new DOMSourceXMLStreamReader(src));
     }
 
-    private static ImmutableMap<QName, String> getElementAttributes(final XMLStreamReader in) {
+    private ImmutableMap<QName, Object> getElementAttributes(final XMLStreamReader in) {
         checkState(in.isStartElement(), "Attributes can be extracted only from START_ELEMENT.");
-        final Map<QName, String> attributes = new LinkedHashMap<>();
+        final Map<QName, Object> attributes = new LinkedHashMap<>();
 
         for (int attrIndex = 0; attrIndex < in.getAttributeCount(); attrIndex++) {
-            String attributeNS = in.getAttributeNamespace(attrIndex);
-
-            if (attributeNS == null) {
-                attributeNS = "";
-            }
+            final String attributeNS = in.getAttributeNamespace(attrIndex);
 
             // 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));
+            final String localName = in.getAttributeLocalName(attrIndex);
+            final String attrValue = in.getAttributeValue(attrIndex);
+            if (Strings.isNullOrEmpty(attributeNS)) {
+                StreamWriterFacade.warnLegacyAttribute(localName);
+                attributes.put(QName.create(LEGACY_ATTRIBUTE_NAMESPACE, localName), attrValue);
+                continue;
+            }
+
+            // Cross-relate attribute namespace to the module
+            final URI uri = URI.create(attributeNS);
+            final SchemaContext schemaContext = codecs.getSchemaContext();
+            final Set<Module> modules = schemaContext.findModules(uri);
+            if (!modules.isEmpty()) {
+                final QName qname = QName.create(modules.iterator().next().getQNameModule(), localName);
+                final Optional<AnnotationSchemaNode> optAnnotation = AnnotationSchemaNode.find(schemaContext, qname);
+                if (optAnnotation.isPresent()) {
+                    final AnnotationSchemaNode schema = optAnnotation.get();
+                    final Object value = codecs.codecFor(schema).parseValue(in.getNamespaceContext(), attrValue);
+                    attributes.put(schema.getQName(), value);
+                    continue;
+                }
+
+                LOG.debug("Annotation for {} not found, using legacy QName", qname);
+            }
+
+            attributes.put(QName.create(uri, localName), attrValue);
         }
 
         return ImmutableMap.copyOf(attributes);
@@ -371,10 +412,9 @@ public final class XmlParserStream implements Closeable, Flushable {
 
                     final String xmlElementNamespace = in.getNamespaceURI();
                     if (!namesakes.add(new SimpleImmutableEntry<>(xmlElementNamespace, xmlElementName))) {
-                        final Location loc = in.getLocation();
-                        throw new IllegalStateException(String.format(
-                                "Duplicate namespace \"%s\" element \"%s\" in XML input at: line %s column %s",
-                                xmlElementNamespace, xmlElementName, loc.getLineNumber(), loc.getColumnNumber()));
+                        throw new XMLStreamException(String.format(
+                            "Duplicate namespace \"%s\" element \"%s\" in XML input", xmlElementNamespace,
+                            xmlElementName), in.getLocation());
                     }
 
                     final Deque<DataSchemaNode> childDataSchemaNodes =
@@ -382,10 +422,16 @@ public final class XmlParserStream implements Closeable, Flushable {
                                     new URI(xmlElementNamespace));
 
                     if (childDataSchemaNodes.isEmpty()) {
-                        checkState(!strictParsing, "Schema for node with name %s and namespace %s does not exist at %s",
-                            xmlElementName, xmlElementNamespace, parentSchema.getPath());
-                        skipUnknownNode(in);
-                        continue;
+                        if (!strictParsing) {
+                            LOG.debug("Skipping unknown node ns=\"{}\" localName=\"{}\" at path {}",
+                                xmlElementNamespace, xmlElementName, parentSchema.getPath());
+                            skipUnknownNode(in);
+                            continue;
+                        }
+
+                        throw new XMLStreamException(String.format(
+                            "Schema for node with name %s and namespace %s does not exist at %s",
+                            xmlElementName, xmlElementNamespace, parentSchema.getPath(), in.getLocation()));
                     }
 
                     read(in, ((CompositeNodeDataWithSchema<?>) parent).addChild(childDataSchemaNodes), rootElement);