2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.netconf.sal.connect.netconf;
10 import static javax.xml.bind.DatatypeConverter.printBase64Binary;
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;
23 import java.net.URLConnection;
24 import java.util.AbstractMap;
25 import java.util.Collections;
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.rev160201.ModulesState;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160201.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;
53 * Holds URLs with YANG schema resources for all yang modules reported in
54 * ietf-netconf-yang-library/modules-state/modules node
56 public class LibraryModulesSchemas {
58 private static final Logger LOG = LoggerFactory.getLogger(LibraryModulesSchemas.class);
60 private static SchemaContext libraryContext;
62 private final Map<SourceIdentifier, URL> availableModels;
65 final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
66 moduleInfoBackedContext.registerModuleInfo(
67 org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160201.
68 $YangModuleInfoImpl.getInstance());
69 libraryContext = moduleInfoBackedContext.tryToCreateSchemaContext().get();
72 private LibraryModulesSchemas(final Map<SourceIdentifier, URL> availableModels) {
73 this.availableModels = availableModels;
76 public Map<SourceIdentifier, URL> getAvailableModels() {
77 return availableModels;
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
86 public static LibraryModulesSchemas create(final String url, final String username, final String password) {
87 Preconditions.checkNotNull(url);
89 final URL urlConnection = new URL(url);
90 final URLConnection connection = urlConnection.openConnection();
92 if(connection instanceof HttpURLConnection) {
93 connection.setRequestProperty("Accept", "application/xml");
94 String userpass = username + ":" + password;
95 String basicAuth = "Basic " + printBase64Binary(userpass.getBytes());
97 connection.setRequestProperty("Authorization", basicAuth);
100 return createFromURLConnection(connection);
102 } catch (IOException e) {
103 LOG.warn("Unable to download yang library from {}", url, e);
104 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
109 private static LibraryModulesSchemas createFromURLConnection(URLConnection connection) {
111 String contentType = connection.getContentType();
113 // TODO try to guess Json also from intput stream
114 if (guessJsonFromFileName(connection.getURL().getFile())) {
115 contentType = "application/json";
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);
125 if (!optionalModulesStateNode.isPresent()) {
126 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
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);
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);
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());
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());
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
165 public static LibraryModulesSchemas create(final String url) {
166 Preconditions.checkNotNull(url);
168 final URL urlConnection = new URL(url);
169 final URLConnection connection = urlConnection.openConnection();
171 if(connection instanceof HttpURLConnection) {
172 connection.setRequestProperty("Accept", "application/xml");
175 return createFromURLConnection(connection);
177 } catch (IOException e) {
178 LOG.warn("Unable to download yang library from {}", url, e);
179 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
183 private static boolean guessJsonFromFileName(final String fileName) {
184 String extension = "";
185 final int i = fileName.lastIndexOf(46);
187 extension = fileName.substring(i).toLowerCase();
190 return extension.equals(".json");
193 private static Optional<NormalizedNode<?, ?>> readJson(final InputStream in) {
194 final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
195 final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
197 final JsonParserStream jsonParser = JsonParserStream.create(writer, libraryContext);
198 final JsonReader reader = new JsonReader(new InputStreamReader(in));
200 jsonParser.parse(reader);
202 return resultHolder.isFinished() ?
203 Optional.of(resultHolder.getResult()) :
204 Optional.<NormalizedNode<?, ?>>absent();
207 private static Optional<NormalizedNode<?, ?>> readXml(final InputStream in) {
208 final DomToNormalizedNodeParserFactory parserFactory =
209 DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, libraryContext);
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);
220 return Optional.<NormalizedNode<?, ?>>absent();
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());
227 YangInstanceIdentifier.NodeIdentifier childNodeId =
228 new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "name"));
229 final String moduleName = getSingleChildNodeValue(moduleNode, childNodeId).get();
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();
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);
246 final SourceIdentifier sourceId = revision.isPresent()
247 ? new SourceIdentifier(moduleName, revision.get())
248 : new SourceIdentifier(moduleName);
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();
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());
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());