Merge "Add Karaf CLI for NETCONF CRUD commands"
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / LibraryModulesSchemas.java
1 /*
2  * Copyright (c) 2015 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 package org.opendaylight.netconf.sal.connect.netconf;
9
10 import static javax.xml.bind.DatatypeConverter.printBase64Binary;
11
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.gson.stream.JsonReader;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.InputStreamReader;
20 import java.net.HttpURLConnection;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.net.URLConnection;
24 import java.util.AbstractMap;
25 import java.util.Collections;
26 import java.util.Map;
27 import org.opendaylight.controller.config.util.xml.XmlUtil;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.ModulesState;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.module.list.Module;
30 import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
35 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
40 import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
41 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
42 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
43 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
44 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
45 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
47 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50 import org.xml.sax.SAXException;
51
52 /**
53  * Holds URLs with YANG schema resources for all yang modules reported in
54  * ietf-netconf-yang-library/modules-state/modules node
55  */
56 public class LibraryModulesSchemas {
57
58     private static final Logger LOG = LoggerFactory.getLogger(LibraryModulesSchemas.class);
59
60     private static SchemaContext libraryContext;
61
62     private final Map<SourceIdentifier, URL> availableModels;
63
64     static {
65         final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
66         moduleInfoBackedContext.registerModuleInfo(
67                 org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.
68                         $YangModuleInfoImpl.getInstance());
69         libraryContext = moduleInfoBackedContext.tryToCreateSchemaContext().get();
70     }
71
72     private LibraryModulesSchemas(final Map<SourceIdentifier, URL> availableModels) {
73         this.availableModels = availableModels;
74     }
75
76     public Map<SourceIdentifier, URL> getAvailableModels() {
77         return availableModels;
78     }
79
80
81     /**
82      * Resolves URLs with YANG schema resources from modules-state. Uses basic http authenticaiton
83      * @param url URL pointing to yang library
84      * @return Resolved URLs with YANG schema resources for all yang modules from yang library
85      */
86     public static LibraryModulesSchemas create(final String url, final String username, final String password) {
87         Preconditions.checkNotNull(url);
88         try {
89             final URL urlConnection = new URL(url);
90             final URLConnection connection = urlConnection.openConnection();
91
92             if(connection instanceof HttpURLConnection) {
93                 connection.setRequestProperty("Accept", "application/xml");
94                 String userpass = username + ":" + password;
95                 String basicAuth = "Basic " + printBase64Binary(userpass.getBytes());
96
97                 connection.setRequestProperty("Authorization", basicAuth);
98             }
99
100             return createFromURLConnection(connection);
101
102         } catch (IOException e) {
103             LOG.warn("Unable to download yang library from {}", url, e);
104             return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
105         }
106     }
107
108
109     private static LibraryModulesSchemas createFromURLConnection(URLConnection connection) {
110
111         String contentType = connection.getContentType();
112
113         // TODO try to guess Json also from intput stream
114         if (guessJsonFromFileName(connection.getURL().getFile())) {
115             contentType = "application/json";
116         }
117
118         Preconditions.checkNotNull(contentType, "Content type unknown");
119         Preconditions.checkState(contentType.equals("application/json") || contentType.equals("application/xml"),
120                 "Only XML and JSON types are supported.");
121         try (final InputStream in = connection.getInputStream()) {
122             final Optional<NormalizedNode<?, ?>> optionalModulesStateNode =
123                     contentType.equals("application/json") ? readJson(in) : readXml(in);
124
125             if (!optionalModulesStateNode.isPresent()) {
126                 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
127             }
128
129             final NormalizedNode<?, ?> modulesStateNode = optionalModulesStateNode.get();
130             Preconditions.checkState(modulesStateNode.getNodeType().equals(ModulesState.QNAME),
131                     "Wrong QName %s", modulesStateNode.getNodeType());
132             Preconditions.checkState(modulesStateNode instanceof ContainerNode,
133                     "Expecting container containing module list, but was %s", modulesStateNode);
134
135             final YangInstanceIdentifier.NodeIdentifier moduleListNodeId =
136                     new YangInstanceIdentifier.NodeIdentifier(Module.QNAME);
137             final Optional<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> moduleListNode =
138                     ((ContainerNode) modulesStateNode).getChild(moduleListNodeId);
139             Preconditions.checkState(moduleListNode.isPresent(),
140                     "Unable to find list: %s in %s", moduleListNodeId, modulesStateNode);
141             Preconditions.checkState(moduleListNode.get() instanceof MapNode,
142                     "Unexpected structure for container: %s in : %s. Expecting a list",
143                     moduleListNodeId, modulesStateNode);
144
145             final ImmutableMap.Builder<SourceIdentifier, URL> schemasMapping = new ImmutableMap.Builder<>();
146             for (final MapEntryNode moduleNode : ((MapNode) moduleListNode.get()).getValue()) {
147                 final Optional<Map.Entry<SourceIdentifier, URL>> schemaMappingEntry = createFromEntry(moduleNode);
148                 if (schemaMappingEntry.isPresent()) {
149                     schemasMapping.put(createFromEntry(moduleNode).get());
150                 }
151             }
152
153             return new LibraryModulesSchemas(schemasMapping.build());
154         } catch (IOException e) {
155             LOG.warn("Unable to download yang library from {}", connection.getURL(), e);
156             return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
157         }
158     }
159
160     /**
161      * Resolves URLs with YANG schema resources from modules-state
162      * @param url URL pointing to yang library
163      * @return Resolved URLs with YANG schema resources for all yang modules from yang library
164      */
165     public static LibraryModulesSchemas create(final String url) {
166         Preconditions.checkNotNull(url);
167         try {
168             final URL urlConnection = new URL(url);
169             final URLConnection connection = urlConnection.openConnection();
170
171             if(connection instanceof HttpURLConnection) {
172                 connection.setRequestProperty("Accept", "application/xml");
173             }
174
175             return createFromURLConnection(connection);
176
177         } catch (IOException e) {
178             LOG.warn("Unable to download yang library from {}", url, e);
179             return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
180         }
181     }
182
183     private static boolean guessJsonFromFileName(final String fileName) {
184         String extension = "";
185         final int i = fileName.lastIndexOf(46);
186         if(i != -1) {
187             extension = fileName.substring(i).toLowerCase();
188         }
189
190         return extension.equals(".json");
191     }
192
193     private static Optional<NormalizedNode<?, ?>> readJson(final InputStream in) {
194         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
195         final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
196
197         final JsonParserStream jsonParser = JsonParserStream.create(writer, libraryContext);
198         final JsonReader reader = new JsonReader(new InputStreamReader(in));
199
200         jsonParser.parse(reader);
201
202         return resultHolder.isFinished() ?
203                 Optional.of(resultHolder.getResult()) :
204                 Optional.<NormalizedNode<?, ?>>absent();
205     }
206
207     private static Optional<NormalizedNode<?, ?>> readXml(final InputStream in) {
208         final DomToNormalizedNodeParserFactory parserFactory =
209                 DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, libraryContext);
210
211         try {
212             final NormalizedNode<?, ?> parsed =
213                     parserFactory.getContainerNodeParser().parse(Collections.singleton(XmlUtil.readXmlToElement(in)),
214                             (ContainerSchemaNode) libraryContext.getDataChildByName(ModulesState.QNAME));
215             return Optional.of(parsed);
216         } catch (IOException|SAXException e) {
217             LOG.warn("Unable to parse yang library xml content", e);
218         }
219
220         return Optional.<NormalizedNode<?, ?>>absent();
221     }
222
223     private static Optional<Map.Entry<SourceIdentifier, URL>> createFromEntry(final MapEntryNode moduleNode) {
224         Preconditions.checkArgument(
225                 moduleNode.getNodeType().equals(Module.QNAME), "Wrong QName %s", moduleNode.getNodeType());
226
227         YangInstanceIdentifier.NodeIdentifier childNodeId =
228                 new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "name"));
229         final String moduleName = getSingleChildNodeValue(moduleNode, childNodeId).get();
230
231         childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "revision"));
232         final Optional<String> revision = getSingleChildNodeValue(moduleNode, childNodeId);
233         if(revision.isPresent()) {
234             if(!SourceIdentifier.REVISION_PATTERN.matcher(revision.get()).matches()) {
235                 LOG.warn("Skipping library schema for {}. Revision {} is in wrong format.", moduleNode, revision.get());
236                 return Optional.<Map.Entry<SourceIdentifier, URL>>absent();
237             }
238         }
239
240         // FIXME leaf schema with url that represents the yang schema resource for this module is not mandatory
241         // don't fail if schema node is not present, just skip the entry or add some default URL
242         childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "schema"));
243         final Optional<String> schemaUriAsString = getSingleChildNodeValue(moduleNode, childNodeId);
244
245
246         final SourceIdentifier sourceId = revision.isPresent()
247                 ? new SourceIdentifier(moduleName, revision.get())
248                 : new SourceIdentifier(moduleName);
249
250         try {
251             return Optional.<Map.Entry<SourceIdentifier, URL>>of(new AbstractMap.SimpleImmutableEntry<>(
252                     sourceId, new URL(schemaUriAsString.get())));
253         } catch (MalformedURLException e) {
254             LOG.warn("Skipping library schema for {}. URL {} representing yang schema resource is not valid",
255                     moduleNode, schemaUriAsString.get());
256             return Optional.<Map.Entry<SourceIdentifier, URL>>absent();
257         }
258     }
259
260     private static Optional<String> getSingleChildNodeValue(final DataContainerNode<?> schemaNode,
261                                                             final YangInstanceIdentifier.NodeIdentifier childNodeId) {
262         final Optional<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> node =
263                 schemaNode.getChild(childNodeId);
264         Preconditions.checkArgument(node.isPresent(), "Child node %s not present", childNodeId.getNodeType());
265         return getValueOfSimpleNode(node.get());
266     }
267
268     private static Optional<String> getValueOfSimpleNode(
269             final NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?> node) {
270         final Object value = node.getValue();
271         return value == null || Strings.isNullOrEmpty(value.toString())
272                 ? Optional.<String>absent() : Optional.of(value.toString().trim());
273     }
274
275 }