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.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.RevisionSourceIdentifier;
48 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.xml.sax.SAXException;
54 * Holds URLs with YANG schema resources for all yang modules reported in
55 * ietf-netconf-yang-library/modules-state/modules node
57 public class LibraryModulesSchemas {
59 private static final Logger LOG = LoggerFactory.getLogger(LibraryModulesSchemas.class);
61 private static SchemaContext libraryContext;
63 private final Map<SourceIdentifier, URL> availableModels;
66 final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
67 moduleInfoBackedContext.registerModuleInfo(
68 org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev160409.
69 $YangModuleInfoImpl.getInstance());
70 libraryContext = moduleInfoBackedContext.tryToCreateSchemaContext().get();
73 private LibraryModulesSchemas(final Map<SourceIdentifier, URL> availableModels) {
74 this.availableModels = availableModels;
77 public Map<SourceIdentifier, URL> getAvailableModels() {
78 return availableModels;
83 * Resolves URLs with YANG schema resources from modules-state. Uses basic http authenticaiton
84 * @param url URL pointing to yang library
85 * @return Resolved URLs with YANG schema resources for all yang modules from yang library
87 public static LibraryModulesSchemas create(final String url, final String username, final String password) {
88 Preconditions.checkNotNull(url);
90 final URL urlConnection = new URL(url);
91 final URLConnection connection = urlConnection.openConnection();
93 if(connection instanceof HttpURLConnection) {
94 connection.setRequestProperty("Accept", "application/xml");
95 String userpass = username + ":" + password;
96 String basicAuth = "Basic " + printBase64Binary(userpass.getBytes());
98 connection.setRequestProperty("Authorization", basicAuth);
101 return createFromURLConnection(connection);
103 } catch (IOException e) {
104 LOG.warn("Unable to download yang library from {}", url, e);
105 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
110 private static LibraryModulesSchemas createFromURLConnection(URLConnection connection) {
112 String contentType = connection.getContentType();
114 // TODO try to guess Json also from intput stream
115 if (guessJsonFromFileName(connection.getURL().getFile())) {
116 contentType = "application/json";
119 Preconditions.checkNotNull(contentType, "Content type unknown");
120 Preconditions.checkState(contentType.equals("application/json") || contentType.equals("application/xml"),
121 "Only XML and JSON types are supported.");
122 try (final InputStream in = connection.getInputStream()) {
123 final Optional<NormalizedNode<?, ?>> optionalModulesStateNode =
124 contentType.equals("application/json") ? readJson(in) : readXml(in);
126 if (!optionalModulesStateNode.isPresent()) {
127 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
130 final NormalizedNode<?, ?> modulesStateNode = optionalModulesStateNode.get();
131 Preconditions.checkState(modulesStateNode.getNodeType().equals(ModulesState.QNAME),
132 "Wrong QName %s", modulesStateNode.getNodeType());
133 Preconditions.checkState(modulesStateNode instanceof ContainerNode,
134 "Expecting container containing module list, but was %s", modulesStateNode);
136 final YangInstanceIdentifier.NodeIdentifier moduleListNodeId =
137 new YangInstanceIdentifier.NodeIdentifier(Module.QNAME);
138 final Optional<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> moduleListNode =
139 ((ContainerNode) modulesStateNode).getChild(moduleListNodeId);
140 Preconditions.checkState(moduleListNode.isPresent(),
141 "Unable to find list: %s in %s", moduleListNodeId, modulesStateNode);
142 Preconditions.checkState(moduleListNode.get() instanceof MapNode,
143 "Unexpected structure for container: %s in : %s. Expecting a list",
144 moduleListNodeId, modulesStateNode);
146 final ImmutableMap.Builder<SourceIdentifier, URL> schemasMapping = new ImmutableMap.Builder<>();
147 for (final MapEntryNode moduleNode : ((MapNode) moduleListNode.get()).getValue()) {
148 final Optional<Map.Entry<SourceIdentifier, URL>> schemaMappingEntry = createFromEntry(moduleNode);
149 if (schemaMappingEntry.isPresent()) {
150 schemasMapping.put(createFromEntry(moduleNode).get());
154 return new LibraryModulesSchemas(schemasMapping.build());
155 } catch (IOException e) {
156 LOG.warn("Unable to download yang library from {}", connection.getURL(), e);
157 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
162 * Resolves URLs with YANG schema resources from modules-state
163 * @param url URL pointing to yang library
164 * @return Resolved URLs with YANG schema resources for all yang modules from yang library
166 public static LibraryModulesSchemas create(final String url) {
167 Preconditions.checkNotNull(url);
169 final URL urlConnection = new URL(url);
170 final URLConnection connection = urlConnection.openConnection();
172 if(connection instanceof HttpURLConnection) {
173 connection.setRequestProperty("Accept", "application/xml");
176 return createFromURLConnection(connection);
178 } catch (IOException e) {
179 LOG.warn("Unable to download yang library from {}", url, e);
180 return new LibraryModulesSchemas(Collections.<SourceIdentifier, URL>emptyMap());
184 private static boolean guessJsonFromFileName(final String fileName) {
185 String extension = "";
186 final int i = fileName.lastIndexOf(46);
188 extension = fileName.substring(i).toLowerCase();
191 return extension.equals(".json");
194 private static Optional<NormalizedNode<?, ?>> readJson(final InputStream in) {
195 final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
196 final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
198 final JsonParserStream jsonParser = JsonParserStream.create(writer, libraryContext);
199 final JsonReader reader = new JsonReader(new InputStreamReader(in));
201 jsonParser.parse(reader);
203 return resultHolder.isFinished() ?
204 Optional.of(resultHolder.getResult()) :
205 Optional.<NormalizedNode<?, ?>>absent();
208 private static Optional<NormalizedNode<?, ?>> readXml(final InputStream in) {
209 final DomToNormalizedNodeParserFactory parserFactory =
210 DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, libraryContext);
213 final NormalizedNode<?, ?> parsed =
214 parserFactory.getContainerNodeParser().parse(Collections.singleton(XmlUtil.readXmlToElement(in)),
215 (ContainerSchemaNode) libraryContext.getDataChildByName(ModulesState.QNAME));
216 return Optional.of(parsed);
217 } catch (IOException|SAXException e) {
218 LOG.warn("Unable to parse yang library xml content", e);
221 return Optional.<NormalizedNode<?, ?>>absent();
224 private static Optional<Map.Entry<SourceIdentifier, URL>> createFromEntry(final MapEntryNode moduleNode) {
225 Preconditions.checkArgument(
226 moduleNode.getNodeType().equals(Module.QNAME), "Wrong QName %s", moduleNode.getNodeType());
228 YangInstanceIdentifier.NodeIdentifier childNodeId =
229 new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "name"));
230 final String moduleName = getSingleChildNodeValue(moduleNode, childNodeId).get();
232 childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "revision"));
233 final Optional<String> revision = getSingleChildNodeValue(moduleNode, childNodeId);
234 if(revision.isPresent()) {
235 if(!SourceIdentifier.REVISION_PATTERN.matcher(revision.get()).matches()) {
236 LOG.warn("Skipping library schema for {}. Revision {} is in wrong format.", moduleNode, revision.get());
237 return Optional.<Map.Entry<SourceIdentifier, URL>>absent();
241 // FIXME leaf schema with url that represents the yang schema resource for this module is not mandatory
242 // don't fail if schema node is not present, just skip the entry or add some default URL
243 childNodeId = new YangInstanceIdentifier.NodeIdentifier(QName.create(Module.QNAME, "schema"));
244 final Optional<String> schemaUriAsString = getSingleChildNodeValue(moduleNode, childNodeId);
247 final SourceIdentifier sourceId = revision.isPresent()
248 ? RevisionSourceIdentifier.create(moduleName, revision.get())
249 : RevisionSourceIdentifier.create(moduleName);
252 return Optional.<Map.Entry<SourceIdentifier, URL>>of(new AbstractMap.SimpleImmutableEntry<>(
253 sourceId, new URL(schemaUriAsString.get())));
254 } catch (MalformedURLException e) {
255 LOG.warn("Skipping library schema for {}. URL {} representing yang schema resource is not valid",
256 moduleNode, schemaUriAsString.get());
257 return Optional.<Map.Entry<SourceIdentifier, URL>>absent();
261 private static Optional<String> getSingleChildNodeValue(final DataContainerNode<?> schemaNode,
262 final YangInstanceIdentifier.NodeIdentifier childNodeId) {
263 final Optional<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> node =
264 schemaNode.getChild(childNodeId);
265 Preconditions.checkArgument(node.isPresent(), "Child node %s not present", childNodeId.getNodeType());
266 return getValueOfSimpleNode(node.get());
269 private static Optional<String> getValueOfSimpleNode(
270 final NormalizedNode<? extends YangInstanceIdentifier.PathArgument, ?> node) {
271 final Object value = node.getValue();
272 return value == null || Strings.isNullOrEmpty(value.toString())
273 ? Optional.<String>absent() : Optional.of(value.toString().trim());