Bump upstreams
[netconf.git] / netconf / yanglib / src / main / java / org / opendaylight / yanglib / impl / YangLibProvider.java
1 /*
2  * Copyright (c) 2016 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.yanglib.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.base.Predicate;
15 import com.google.common.collect.Iterables;
16 import com.google.common.util.concurrent.FutureCallback;
17 import com.google.common.util.concurrent.MoreExecutors;
18 import java.io.File;
19 import java.io.IOException;
20 import java.util.HashMap;
21 import java.util.Optional;
22 import java.util.concurrent.ExecutionException;
23 import javax.annotation.PreDestroy;
24 import javax.inject.Inject;
25 import javax.inject.Singleton;
26 import javax.ws.rs.NotFoundException;
27 import javax.ws.rs.WebApplicationException;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.opendaylight.mdsal.binding.api.DataBroker;
30 import org.opendaylight.mdsal.binding.api.WriteTransaction;
31 import org.opendaylight.mdsal.common.api.CommitInfo;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.LegacyRevisionUtils;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesStateBuilder;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.ModuleBuilder;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.ModuleKey;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.YangIdentifier;
41 import org.opendaylight.yanglib.api.YangLibService;
42 import org.opendaylight.yangtools.concepts.Registration;
43 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
44 import org.opendaylight.yangtools.yang.common.Uint32;
45 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
46 import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation;
47 import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
48 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
49 import org.opendaylight.yangtools.yang.model.repo.fs.FilesystemSchemaSourceCache;
50 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
51 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceListener;
52 import org.opendaylight.yangtools.yang.parser.api.YangParserFactory;
53 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
54 import org.osgi.service.component.annotations.Activate;
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.Deactivate;
57 import org.osgi.service.component.annotations.Reference;
58 import org.osgi.service.metatype.annotations.AttributeDefinition;
59 import org.osgi.service.metatype.annotations.Designate;
60 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 /**
65  * Listens on new schema sources registered event. For each new source
66  * registered generates URL representing its schema source and write this URL
67  * along with source identifier to
68  * ietf-netconf-yang-library/modules-state/module list.
69  */
70 @Singleton
71 @Component(service = YangLibService.class, configurationPid = "org.opendaylight.netconf.yanglib")
72 @Designate(ocd = YangLibProvider.Configuration.class)
73 public final class YangLibProvider implements YangLibService, SchemaSourceListener, AutoCloseable {
74     @ObjectClassDefinition
75     public @interface Configuration {
76         @AttributeDefinition(min = "1",
77             description = "Local filesystem folder to use as cache + to load yang models from")
78         @NonNull String cache$_$folder() default "cache/schema";
79
80         @AttributeDefinition(
81             description = "Binding address is necessary for generating proper URLS (accessible from the outside world) "
82                 + "for models present directly in the library")
83         @NonNull String binding$_$address() default "localhost";
84
85         @AttributeDefinition(required = true, min = "1", max = "65535",
86             description = "binding port is necessary for generating proper URLS (accessible from the outside world) "
87                 + "for models present directly in the library")
88         int binding$_$port() default 8181;
89     }
90
91     private static final Logger LOG = LoggerFactory.getLogger(YangLibProvider.class);
92
93     private static final Predicate<PotentialSchemaSource<?>> YANG_SCHEMA_SOURCE =
94         input -> YangTextSource.class.isAssignableFrom(input.getRepresentation());
95
96     private final DataBroker dataBroker;
97     private final String bindingAddress;
98     private final Uint32 bindingPort;
99     private final SharedSchemaRepository schemaRepository;
100     private final Registration schemaListenerRegistration;
101
102     @Inject
103     @Activate
104     public YangLibProvider(@Reference final @NonNull DataBroker dataBroker,
105             @Reference final @NonNull YangParserFactory parserFactory, final @NonNull Configuration configuration) {
106         this(dataBroker, parserFactory, configuration.cache$_$folder(), configuration.binding$_$address(),
107                 Uint32.valueOf(configuration.binding$_$port()));
108     }
109
110     @VisibleForTesting
111     YangLibProvider(final @NonNull DataBroker dataBroker, final @NonNull YangParserFactory parserFactory,
112                     final @NonNull String cacheFolder, final @NonNull String bindingAddress,
113                     final @NonNull Uint32 bindingPort) {
114         this.bindingAddress = bindingAddress;
115         this.bindingPort = bindingPort;
116         this.dataBroker = requireNonNull(dataBroker);
117
118         final File cacheFolderFile = new File(cacheFolder);
119         if (cacheFolderFile.exists()) {
120             LOG.info("cache-folder {} already exists", cacheFolderFile);
121         } else {
122             checkArgument(cacheFolderFile.mkdirs(), "cache-folder %s cannot be created", cacheFolderFile);
123             LOG.info("cache-folder {} was created", cacheFolderFile);
124         }
125         checkArgument(cacheFolderFile.isDirectory(), "cache-folder %s is not a directory", cacheFolderFile);
126
127         schemaRepository = new SharedSchemaRepository("yang-library", parserFactory);
128         final var cache = new FilesystemSchemaSourceCache<>(schemaRepository, YangTextSource.class, cacheFolderFile);
129         schemaRepository.registerSchemaSourceListener(cache);
130
131         schemaListenerRegistration = schemaRepository.registerSchemaSourceListener(this);
132
133         LOG.info("Started yang library with sources from {}", cacheFolderFile);
134     }
135
136     @PreDestroy
137     @Deactivate
138     @Override
139     public void close() {
140         schemaListenerRegistration.close();
141     }
142
143     @Override
144     public void schemaSourceEncountered(final SourceRepresentation source) {
145         // NOOP
146     }
147
148     @Override
149     public void schemaSourceRegistered(final Iterable<PotentialSchemaSource<?>> sources) {
150         final var newModules = new HashMap<ModuleKey, Module>();
151
152         for (var potentialYangSource : Iterables.filter(sources, YANG_SCHEMA_SOURCE::test)) {
153             final var moduleName = new YangIdentifier(potentialYangSource.getSourceIdentifier().name().getLocalName());
154
155             final var newModule = new ModuleBuilder()
156                     .setName(moduleName)
157                     .setRevision(LegacyRevisionUtils.fromYangCommon(
158                         Optional.ofNullable(potentialYangSource.getSourceIdentifier().revision())))
159                     .setSchema(getUrlForModule(potentialYangSource.getSourceIdentifier()))
160                     .build();
161
162             newModules.put(newModule.key(), newModule);
163         }
164
165         if (newModules.isEmpty()) {
166             // If no new yang modules then do nothing
167             return;
168         }
169
170         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
171         tx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.create(ModulesState.class),
172                 new ModulesStateBuilder().setModule(newModules).build());
173
174         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
175             @Override
176             public void onSuccess(final CommitInfo result) {
177                 LOG.debug("Modules state successfully populated with new modules");
178             }
179
180             @Override
181             public void onFailure(final Throwable throwable) {
182                 LOG.warn("Unable to update modules state", throwable);
183             }
184         }, MoreExecutors.directExecutor());
185     }
186
187     @Override
188     public void schemaSourceUnregistered(final PotentialSchemaSource<?> source) {
189         if (!YANG_SCHEMA_SOURCE.test(source)) {
190             // if representation of potential schema source is not yang text schema source do nothing
191             // we do not want to delete this module entry from module list
192             return;
193         }
194
195         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
196         tx.delete(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.create(ModulesState.class)
197             .child(Module.class, new ModuleKey(new YangIdentifier(source.getSourceIdentifier().name().getLocalName()),
198                 LegacyRevisionUtils.fromYangCommon(Optional.ofNullable(source.getSourceIdentifier().revision())))));
199
200         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
201             @Override
202             public void onSuccess(final CommitInfo result) {
203                 LOG.debug("Modules state successfully updated.");
204             }
205
206             @Override
207             public void onFailure(final Throwable throwable) {
208                 LOG.warn("Unable to update modules state", throwable);
209             }
210         }, MoreExecutors.directExecutor());
211     }
212
213     @Override
214     public String getSchema(final String name, final String revision) {
215         LOG.debug("Attempting load for schema source {}:{}", name, revision);
216         return getYangModel(name, revision.isEmpty() ? null : revision);
217     }
218
219     @Override
220     public String getSchema(final String name) {
221         LOG.debug("Attempting load for schema source {}: no-revision", name);
222         return getYangModel(name, null);
223     }
224
225     private String getYangModel(final String name, final String revision) {
226         final var sourceId = new SourceIdentifier(name, revision);
227         final var yangTextSchemaFuture = schemaRepository.getSchemaSource(sourceId, YangTextSource.class);
228         try {
229             final var yangTextSchemaSource = yangTextSchemaFuture.get();
230             return yangTextSchemaSource.read();
231         } catch (ExecutionException e) {
232             if (e.getCause() instanceof MissingSchemaSourceException) {
233                 throw new NotFoundException("Schema source " + sourceId + " not found", e);
234             }
235             throw new WebApplicationException("Unable to retrieve schema source " + sourceId, e);
236         } catch (IOException e) {
237             throw new WebApplicationException("Unable to read schema " + sourceId, e);
238         } catch (InterruptedException e) {
239             Thread.currentThread().interrupt();
240             throw new WebApplicationException("Retrieving schema source " + sourceId + " has been interrupted", e);
241         }
242     }
243
244     private Uri getUrlForModule(final SourceIdentifier sourceIdentifier) {
245         return new Uri("http://" + bindingAddress + ':' + bindingPort + "/yanglib/schemas/"
246                 + sourceIdentifier.name().getLocalName() + revString(sourceIdentifier));
247     }
248
249     private static String revString(final SourceIdentifier id) {
250         final var rev = id.revision();
251         return rev != null ? "/" + rev : "";
252     }
253 }