Replace deprecated Futures.addCallback by the newer version
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / LibraryModulesSchemas.java
index 9924d6fc75f2743928d8e468f242b7e50ec36418..a3e258248d8808ccb93dadaca3b309459eec5618 100644 (file)
@@ -32,7 +32,9 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import org.opendaylight.controller.config.util.xml.XmlUtil;
+import java.util.regex.Pattern;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.dom.DOMSource;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.mdsal.binding.generator.impl.ModuleInfoBackedContext;
 import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemas;
@@ -41,6 +43,7 @@ import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransform
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.ModulesState;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.module.list.Module;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@@ -51,34 +54,33 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
-import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
-import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.xml.sax.SAXException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 
 /**
  * Holds URLs with YANG schema resources for all yang modules reported in
- * ietf-netconf-yang-library/modules-state/modules node
+ * ietf-netconf-yang-library/modules-state/modules node.
  */
 public class LibraryModulesSchemas implements NetconfDeviceSchemas {
 
     private static final Logger LOG = LoggerFactory.getLogger(LibraryModulesSchemas.class);
-
+    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
     private static final SchemaContext LIBRARY_CONTEXT;
 
     static {
         final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
-        moduleInfoBackedContext.registerModuleInfo(
-                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.
-                        $YangModuleInfoImpl.getInstance());
+        moduleInfoBackedContext.registerModuleInfo(org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang
+                .library.rev160409.$YangModuleInfoImpl.getInstance());
         LIBRARY_CONTEXT = moduleInfoBackedContext.tryToCreateSchemaContext().get();
     }
 
@@ -104,7 +106,7 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
         final Map<SourceIdentifier, URL> result = Maps.newHashMap();
         for (final Map.Entry<QName, URL> entry : availableModels.entrySet()) {
             final SourceIdentifier sId = RevisionSourceIdentifier
-                    .create(entry.getKey().getLocalName(), Optional.fromNullable(entry.getKey().getFormattedRevision()));
+                .create(entry.getKey().getLocalName(), Optional.fromNullable(entry.getKey().getFormattedRevision()));
             result.put(sId, entry.getValue());
         }
 
@@ -114,6 +116,7 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
 
     /**
      * Resolves URLs with YANG schema resources from modules-state. Uses basic http authenticaiton
+     *
      * @param url URL pointing to yang library
      * @return Resolved URLs with YANG schema resources for all yang modules from yang library
      */
@@ -123,7 +126,7 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
             final URL urlConnection = new URL(url);
             final URLConnection connection = urlConnection.openConnection();
 
-            if(connection instanceof HttpURLConnection) {
+            if (connection instanceof HttpURLConnection) {
                 connection.setRequestProperty("Accept", "application/xml");
                 final String userpass = username + ":" + password;
                 final String basicAuth = "Basic " + printBase64Binary(userpass.getBytes());
@@ -150,11 +153,12 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
             throw new RuntimeException(deviceId + ": Interrupted while waiting for response to "
                     + MODULES_STATE_MODULE_LIST, e);
         } catch (final ExecutionException e) {
-            LOG.warn("{}: Unable to detect available schemas, get to {} failed", deviceId, MODULES_STATE_MODULE_LIST, e);
+            LOG.warn("{}: Unable to detect available schemas, get to {} failed", deviceId,
+                    MODULES_STATE_MODULE_LIST, e);
             return new LibraryModulesSchemas(Collections.<QName, URL>emptyMap());
         }
 
-        if(moduleListNodeResult.getErrors().isEmpty() == false) {
+        if (moduleListNodeResult.getErrors().isEmpty() == false) {
             LOG.warn("{}: Unable to detect available schemas, get to {} failed, {}",
                     deviceId, MODULES_STATE_MODULE_LIST, moduleListNodeResult.getErrors());
             return new LibraryModulesSchemas(Collections.<QName, URL>emptyMap());
@@ -163,7 +167,7 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
 
         final Optional<? extends NormalizedNode<?, ?>> modulesStateNode =
                 findModulesStateNode(moduleListNodeResult.getResult());
-        if(modulesStateNode.isPresent()) {
+        if (modulesStateNode.isPresent()) {
             Preconditions.checkState(modulesStateNode.get() instanceof ContainerNode,
                     "Expecting container containing schemas, but was %s", modulesStateNode.get());
             return create(((ContainerNode) modulesStateNode.get()));
@@ -173,18 +177,6 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
         }
     }
 
-    private static Optional<? extends NormalizedNode<?, ?>> findModulesStateNode(final NormalizedNode<?, ?> result) {
-        if(result == null) {
-            return Optional.absent();
-        }
-        final Optional<DataContainerChild<?, ?>> dataNode = ((DataContainerNode<?>) result).getChild(toId(NETCONF_DATA_QNAME));
-        if(dataNode.isPresent() == false) {
-            return Optional.absent();
-        }
-
-       return ((DataContainerNode<?>) dataNode.get()).getChild(toId(ModulesState.QNAME));
-    }
-
     private static LibraryModulesSchemas create(final ContainerNode modulesStateNode) {
         final YangInstanceIdentifier.NodeIdentifier moduleListNodeId =
                 new YangInstanceIdentifier.NodeIdentifier(Module.QNAME);
@@ -207,6 +199,42 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
         return new LibraryModulesSchemas(schemasMapping.build());
     }
 
+    /**
+     * Resolves URLs with YANG schema resources from modules-state.
+     * @param url URL pointing to yang library
+     * @return Resolved URLs with YANG schema resources for all yang modules from yang library
+     */
+    public static LibraryModulesSchemas create(final String url) {
+        Preconditions.checkNotNull(url);
+        try {
+            final URL urlConnection = new URL(url);
+            final URLConnection connection = urlConnection.openConnection();
+
+            if (connection instanceof HttpURLConnection) {
+                connection.setRequestProperty("Accept", "application/xml");
+            }
+
+            return createFromURLConnection(connection);
+
+        } catch (final IOException e) {
+            LOG.warn("Unable to download yang library from {}", url, e);
+            return new LibraryModulesSchemas(Collections.<QName, URL>emptyMap());
+        }
+    }
+
+    private static Optional<? extends NormalizedNode<?, ?>> findModulesStateNode(final NormalizedNode<?, ?> result) {
+        if (result == null) {
+            return Optional.absent();
+        }
+        final Optional<DataContainerChild<?, ?>> dataNode =
+                ((DataContainerNode<?>) result).getChild(toId(NETCONF_DATA_QNAME));
+        if (dataNode.isPresent() == false) {
+            return Optional.absent();
+        }
+
+        return ((DataContainerNode<?>) dataNode.get()).getChild(toId(ModulesState.QNAME));
+    }
+
     private static LibraryModulesSchemas createFromURLConnection(final URLConnection connection) {
 
         String contentType = connection.getContentType();
@@ -219,7 +247,7 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
         Preconditions.checkNotNull(contentType, "Content type unknown");
         Preconditions.checkState(contentType.equals("application/json") || contentType.equals("application/xml"),
                 "Only XML and JSON types are supported.");
-        try (final InputStream in = connection.getInputStream()) {
+        try (InputStream in = connection.getInputStream()) {
             final Optional<NormalizedNode<?, ?>> optionalModulesStateNode =
                     contentType.equals("application/json") ? readJson(in) : readXml(in);
 
@@ -258,33 +286,10 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
         }
     }
 
-    /**
-     * Resolves URLs with YANG schema resources from modules-state
-     * @param url URL pointing to yang library
-     * @return Resolved URLs with YANG schema resources for all yang modules from yang library
-     */
-    public static LibraryModulesSchemas create(final String url) {
-        Preconditions.checkNotNull(url);
-        try {
-            final URL urlConnection = new URL(url);
-            final URLConnection connection = urlConnection.openConnection();
-
-            if(connection instanceof HttpURLConnection) {
-                connection.setRequestProperty("Accept", "application/xml");
-            }
-
-            return createFromURLConnection(connection);
-
-        } catch (final IOException e) {
-            LOG.warn("Unable to download yang library from {}", url, e);
-            return new LibraryModulesSchemas(Collections.<QName, URL>emptyMap());
-        }
-    }
-
     private static boolean guessJsonFromFileName(final String fileName) {
         String extension = "";
         final int i = fileName.lastIndexOf(46);
-        if(i != -1) {
+        if (i != -1) {
             extension = fileName.substring(i).toLowerCase();
         }
 
@@ -300,24 +305,40 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
 
         jsonParser.parse(reader);
 
-        return resultHolder.isFinished() ?
-                Optional.of(resultHolder.getResult()) :
-                Optional.<NormalizedNode<?, ?>>absent();
+        return resultHolder.isFinished()
+                ? Optional.of(resultHolder.getResult()) : Optional.<NormalizedNode<?, ?>>absent();
     }
 
+    @SuppressWarnings("checkstyle:IllegalCatch")
     private static Optional<NormalizedNode<?, ?>> readXml(final InputStream in) {
-        // TODO one module node with bad revision will fail parsing of whole modules-state node
-        // we have to parse them one by one and just ignore modules with bad revisions
-        // See BUG 8071 https://bugs.opendaylight.org/show_bug.cgi?id=8071
-        final DomToNormalizedNodeParserFactory parserFactory =
-                DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, LIBRARY_CONTEXT);
-
         try {
-            final NormalizedNode<?, ?> parsed =
-                    parserFactory.getContainerNodeParser().parse(Collections.singleton(XmlUtil.readXmlToElement(in)),
-                            (ContainerSchemaNode) LIBRARY_CONTEXT.getDataChildByName(ModulesState.QNAME));
+            final DocumentBuilder docBuilder = UntrustedXML.newDocumentBuilder();
+
+            final Document read = docBuilder.parse(in);
+            final Document doc = docBuilder.newDocument();
+            final Element rootElement = doc.createElementNS("urn:ietf:params:xml:ns:yang:ietf-yang-library",
+                    "modules");
+            doc.appendChild(rootElement);
+
+            for (int i = 0; i < read.getElementsByTagName("revision").getLength(); i++) {
+                final String revision = read.getElementsByTagName("revision").item(i).getTextContent();
+                if (DATE_PATTERN.matcher(revision).find() || revision.isEmpty()) {
+                    final Node module = doc.importNode(read.getElementsByTagName("module").item(i), true);
+                    rootElement.appendChild(module);
+                } else {
+                    LOG.warn("Xml contains wrong revision - {} - on module {}", revision,
+                            read.getElementsByTagName("module").item(i).getTextContent());
+                }
+            }
+
+            final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+            final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
+            final XmlParserStream xmlParser = XmlParserStream.create(writer, LIBRARY_CONTEXT,
+                    LIBRARY_CONTEXT.getDataChildByName(ModulesState.QNAME));
+            xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
+            final NormalizedNode<?, ?> parsed = resultHolder.getResult();
             return Optional.of(parsed);
-        } catch (IOException|SAXException e) {
+        } catch (final Exception e) {
             LOG.warn("Unable to parse yang library xml content", e);
         }
 
@@ -334,8 +355,8 @@ public class LibraryModulesSchemas implements NetconfDeviceSchemas {
 
         childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "revision"));
         final Optional<String> revision = getSingleChildNodeValue(moduleNode, childNodeId);
-        if(revision.isPresent()) {
-            if(!SourceIdentifier.REVISION_PATTERN.matcher(revision.get()).matches()) {
+        if (revision.isPresent()) {
+            if (!SourceIdentifier.REVISION_PATTERN.matcher(revision.get()).matches()) {
                 LOG.warn("Skipping library schema for {}. Revision {} is in wrong format.", moduleNode, revision.get());
                 return Optional.<Map.Entry<QName, URL>>absent();
             }