NETCONF-557: Add support for URL capability
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / AbstractConfigOperation.java
1 /*
2  * Copyright (c) 2018 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.netconf.mdsal.connector.ops;
10
11 import static java.nio.charset.StandardCharsets.UTF_8;
12
13 import com.google.common.base.Optional;
14 import com.google.common.base.Strings;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.net.URLConnection;
20 import java.net.URLStreamHandler;
21 import java.util.Base64;
22 import org.opendaylight.netconf.api.DocumentedException;
23 import org.opendaylight.netconf.api.xml.XmlElement;
24 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
25 import org.opendaylight.netconf.api.xml.XmlUtil;
26 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
27 import org.w3c.dom.Document;
28 import org.w3c.dom.Element;
29 import org.w3c.dom.NodeList;
30 import org.xml.sax.SAXException;
31
32 abstract class AbstractConfigOperation extends AbstractSingletonNetconfOperation {
33     static final String URL_KEY = "url";
34     static final String CONFIG_KEY = "config";
35     private static final int TIMEOUT_MS = 5000;
36
37     protected AbstractConfigOperation(final String netconfSessionIdForReporting) {
38         super(netconfSessionIdForReporting);
39     }
40
41     protected static NodeList getElementsByTagName(final XmlElement parent, final String key) throws
42         DocumentedException {
43         final Element domParent = parent.getDomElement();
44         final NodeList elementsByTagName;
45
46         if (Strings.isNullOrEmpty(domParent.getPrefix())) {
47             elementsByTagName = domParent.getElementsByTagName(key);
48         } else {
49             elementsByTagName = domParent.getElementsByTagNameNS(parent.getNamespace(), key);
50         }
51
52         return elementsByTagName;
53     }
54
55     static XmlElement getConfigElement(final XmlElement parent) throws DocumentedException {
56         final Optional<XmlElement> configElement = parent.getOnlyChildElementOptionally(CONFIG_KEY);
57         if (configElement.isPresent()) {
58             return configElement.get();
59         } else {
60             final Optional<XmlElement> urlElement = parent.getOnlyChildElementOptionally(URL_KEY);
61             if (!urlElement.isPresent()) {
62                 throw new DocumentedException("Invalid RPC, neither <config> not <url> element is present",
63                     DocumentedException.ErrorType.PROTOCOL,
64                     DocumentedException.ErrorTag.MISSING_ELEMENT,
65                     DocumentedException.ErrorSeverity.ERROR);
66             }
67
68             final Document document = getDocumentFromUrl(urlElement.get().getTextContent());
69             return XmlElement.fromDomElementWithExpected(document.getDocumentElement(), CONFIG_KEY,
70                 XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
71         }
72     }
73
74     /**
75      * Parses XML Document available at given URL.
76      *
77      * <p>JDK8 supports URL schemes that include http, https, file, and jar, but {@link URLStreamHandler}s for other
78      * protocols (e.g. ftp) may be available.
79      *
80      * @param url URL as defined in RFC 2396
81      * @see URL#URL(String, String, int, String)
82      */
83     private static Document getDocumentFromUrl(final String url) throws DocumentedException {
84         try (InputStream input = openConnection(new URL(url))) {
85             return XmlUtil.readXmlToDocument(input);
86         } catch (MalformedURLException e) {
87             throw new DocumentedException(url + " URL is invalid or unsupported", e,
88                 DocumentedException.ErrorType.APPLICATION,
89                 DocumentedException.ErrorTag.INVALID_VALUE,
90                 DocumentedException.ErrorSeverity.ERROR);
91         } catch (IOException e) {
92             throw new DocumentedException("Could not open URL:" + url, e,
93                 DocumentedException.ErrorType.APPLICATION,
94                 DocumentedException.ErrorTag.OPERATION_FAILED,
95                 DocumentedException.ErrorSeverity.ERROR);
96         } catch (SAXException e) {
97             throw new DocumentedException("Could not parse XML at" + url, e,
98                 DocumentedException.ErrorType.APPLICATION,
99                 DocumentedException.ErrorTag.OPERATION_FAILED,
100                 DocumentedException.ErrorSeverity.ERROR);
101         }
102     }
103
104     private static InputStream openConnection(final URL url) throws IOException {
105         final URLConnection connection = url.openConnection();
106         connection.setConnectTimeout(TIMEOUT_MS);
107         connection.setReadTimeout(TIMEOUT_MS);
108
109         // Support Basic Authentication scheme, e.g. http://admin:admin@localhost:8000/config.conf
110         if (url.getUserInfo() != null) {
111             String basicAuth = "Basic " + Base64.getUrlEncoder().encodeToString(url.getUserInfo().getBytes(UTF_8));
112             connection.setRequestProperty("Authorization", basicAuth);
113         }
114         return connection.getInputStream();
115     }
116 }