Activate YangLibrarySupport lazily
[mdsal.git] / yanglib / mdsal-yanglib-rfc8525 / src / main / java / org / opendaylight / mdsal / yanglib / rfc8525 / MountPointContextFactoryImpl.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech s.r.o. 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.mdsal.yanglib.rfc8525;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import java.net.MalformedURLException;
15 import java.net.URL;
16 import java.util.ArrayList;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Optional;
21 import java.util.Set;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
25 import org.opendaylight.mdsal.binding.dom.codec.api.BindingIdentityCodec;
26 import org.opendaylight.mdsal.yanglib.api.SchemaContextResolver;
27 import org.opendaylight.mdsal.yanglib.api.SourceReference;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.datastores.rev180214.Operational;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.LegacyRevisionUtils;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.RevisionIdentifier;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.RevisionUtils;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangLibrary;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.CommonLeafs;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module.ConformanceType;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.yang.library.parameters.Datastore;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.yang.library.parameters.DatastoreKey;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.yang.library.parameters.ModuleSet;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.yang.library.parameters.SchemaKey;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.YangIdentifier;
42 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContextFactory;
43 import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
44 import org.opendaylight.yangtools.rfc8528.data.api.YangLibraryConstants.ContainerName;
45 import org.opendaylight.yangtools.rfc8528.data.util.AbstractMountPointContextFactory;
46 import org.opendaylight.yangtools.yang.common.QName;
47 import org.opendaylight.yangtools.yang.common.Revision;
48 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
49 import org.opendaylight.yangtools.yang.common.XMLNamespace;
50 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
51 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
52 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
53 import org.opendaylight.yangtools.yang.parser.api.YangParserException;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 final class MountPointContextFactoryImpl extends AbstractMountPointContextFactory {
58     private static final Logger LOG = LoggerFactory.getLogger(MountPointContextFactoryImpl.class);
59
60     @SuppressWarnings("deprecation")
61     private final BindingDataObjectCodecTreeNode<ModulesState> legacyCodec;
62     private final BindingDataObjectCodecTreeNode<YangLibrary> codec;
63     private final BindingIdentityCodec identityCodec;
64     private final EffectiveModelContext yangLibContext;
65     private final SchemaContextResolver resolver;
66
67     MountPointContextFactoryImpl(final MountPointIdentifier mountId, final SchemaContextResolver resolver,
68             final EffectiveModelContext yangLibContext,
69             final BindingIdentityCodec identityCodec,
70             final BindingDataObjectCodecTreeNode<YangLibrary> codec,
71             @SuppressWarnings("deprecation")
72             final BindingDataObjectCodecTreeNode<ModulesState> legacyCodec) {
73         super(mountId);
74         this.resolver = requireNonNull(resolver);
75         this.identityCodec = requireNonNull(identityCodec);
76         this.yangLibContext = requireNonNull(yangLibContext);
77         this.codec = requireNonNull(codec);
78         this.legacyCodec = requireNonNull(legacyCodec);
79     }
80
81     @Override
82     protected MountPointContextFactory createContextFactory(final MountPointDefinition mountPoint) {
83         return new MountPointContextFactoryImpl(mountPoint.getIdentifier(), resolver, yangLibContext, identityCodec,
84             codec, legacyCodec);
85     }
86
87     @Override
88     protected Optional<EffectiveModelContext> findSchemaForLibrary(final ContainerName containerName) {
89         switch (containerName) {
90             case RFC7895:
91             case RFC8525:
92                 return Optional.of(yangLibContext);
93             default:
94                 LOG.debug("Unhandled YANG library container {}", containerName);
95                 return Optional.empty();
96         }
97     }
98
99     @Override
100     protected EffectiveModelContext bindLibrary(final ContainerName containerName, final ContainerNode libData)
101             throws YangParserException {
102         switch (containerName) {
103             case RFC7895:
104                 return bindLibrary(verifyNotNull(legacyCodec.deserialize(libData)));
105             case RFC8525:
106                 return bindLibrary(verifyNotNull(codec.deserialize(libData)));
107             default:
108                 throw new IllegalStateException("Unhandled container type " + containerName);
109         }
110     }
111
112     private @NonNull EffectiveModelContext bindLibrary(final @NonNull YangLibrary yangLib) throws YangParserException {
113         final var datastores = yangLib.nonnullDatastore();
114         checkArgument(!datastores.isEmpty(), "No datastore defined");
115
116         final var requiredSources = new ArrayList<SourceReference>();
117         final var librarySources = new ArrayList<SourceReference>();
118         final var supportedFeatures = new HashSet<QName>();
119         final var moduleSet = findModuleSet(yangLib, findSchemaName(datastores, Operational.QNAME));
120         for (var modSet : yangLib.nonnullModuleSet().values()) {
121             if (moduleSet.remove(modSet.getName())) {
122                 fillModules(librarySources, requiredSources, supportedFeatures, modSet);
123             }
124         }
125         checkArgument(moduleSet.isEmpty(), "Failed to resolve module sets %s", moduleSet);
126
127         return resolver.resolveSchemaContext(librarySources, requiredSources, supportedFeatures);
128     }
129
130     @SuppressWarnings("deprecation")
131     private @NonNull EffectiveModelContext bindLibrary(final @NonNull ModulesState modState)
132             throws YangParserException {
133         final var requiredSources = new ArrayList<SourceReference>();
134         final var librarySources = new ArrayList<SourceReference>();
135         final var supportedFeatures = new HashSet<QName>();
136
137         for (var module : modState.nonnullModule().values()) {
138             final var modRef = sourceRefFor(module, module.getSchema());
139
140             final var namespace = XMLNamespace.of(module.requireNamespace().getValue());
141             for (var feature : module.requireFeature()) {
142                 supportedFeatures.add(QName.create(namespace, feature.getValue()).intern());
143             }
144
145             // TODO: take deviations into account
146
147             if (ConformanceType.Import == module.getConformanceType()) {
148                 librarySources.add(modRef);
149             } else {
150                 requiredSources.add(modRef);
151             }
152
153             for (var submodule : module.nonnullSubmodule().values()) {
154                 // Submodules go to library, as they are pulled in as needed
155                 librarySources.add(sourceRefFor(submodule, submodule.getSchema()));
156             }
157         }
158
159         return resolver.resolveSchemaContext(librarySources, requiredSources, supportedFeatures);
160     }
161
162     private String findSchemaName(final Map<DatastoreKey, Datastore> datastores, final QName qname) {
163         final var it = datastores.values().iterator();
164         final var ds = it.next();
165
166         // FIXME: This is ugly, but it is the most compatible thing we can do without knowing the exact requested
167         //        datastore
168         if (it.hasNext() && !qname.equals(identityCodec.fromBinding(ds.getName()))) {
169             do {
170                 final Datastore next = it.next();
171                 if (qname.equals(identityCodec.fromBinding(ds.getName()))) {
172                     return next.getSchema();
173                 }
174             } while (it.hasNext());
175         }
176
177         return ds.getSchema();
178     }
179
180     @SuppressWarnings("deprecation")
181     private static SourceReference sourceRefFor(final CommonLeafs obj, final Uri uri) {
182         final var sourceId = new SourceIdentifier(Unqualified.of(obj.getName().getValue()),
183             LegacyRevisionUtils.toYangCommon(obj.getRevision()).orElse(null));
184         if (uri != null) {
185             try {
186                 return SourceReference.of(sourceId, new URL(uri.getValue()));
187             } catch (MalformedURLException e) {
188                 LOG.debug("Ignoring invalid schema location {}", uri, e);
189             }
190         }
191
192         return SourceReference.of(sourceId);
193     }
194
195     private static HashSet<String> findModuleSet(final YangLibrary yangLib, final String schemaName) {
196         final var schema = yangLib.nonnullSchema().get(new SchemaKey(schemaName));
197         if (schema == null) {
198             throw new IllegalArgumentException("Failed to find moduleSet for " + schemaName);
199         }
200         return new HashSet<>(schema.getModuleSet());
201     }
202
203     private static void fillModules(final List<SourceReference> librarySources,
204             final List<SourceReference> requiredSources, final Set<QName> supportedFeatures, final ModuleSet modSet) {
205         // TODO: take deviations/features into account
206
207         for (var mod : modSet.nonnullImportOnlyModule().values()) {
208             fillSource(librarySources, mod.getName(), RevisionUtils.toYangCommon(mod.getRevision()),
209                 mod.getLocation());
210             mod.nonnullSubmodule().values().forEach(sub -> {
211                 fillSource(librarySources, sub.getName(), toYangCommon(sub.getRevision()), sub.getLocation());
212             });
213         }
214         for (var mod : modSet.nonnullModule().values()) {
215             fillSource(requiredSources, mod.getName(), toYangCommon(mod.getRevision()), mod.getLocation());
216             final var namespace = XMLNamespace.of(mod.requireNamespace().getValue());
217             mod.requireFeature().forEach(feature -> supportedFeatures.add(QName.create(namespace, feature.getValue())));
218             mod.nonnullSubmodule().values().forEach(sub -> {
219                 fillSource(librarySources, sub.getName(), toYangCommon(sub.getRevision()), sub.getLocation());
220             });
221         }
222     }
223
224     private static void fillSource(final List<SourceReference> sources, final YangIdentifier sourceName,
225             final Optional<Revision> revision, final Set<Uri> uris) {
226         final var sourceId = new SourceIdentifier(Unqualified.of(sourceName.getValue()), revision.orElse(null));
227         final SourceReference sourceRef;
228         if (uris != null && uris.isEmpty()) {
229             final var locations = new ArrayList<URL>();
230             for (Uri uri : uris) {
231                 try {
232                     locations.add(new URL(uri.getValue()));
233                 } catch (MalformedURLException e) {
234                     LOG.debug("Ignoring invalid schema location {}", uri, e);
235                 }
236             }
237             sourceRef = SourceReference.of(sourceId, locations);
238         } else {
239             sourceRef = SourceReference.of(sourceId);
240         }
241
242         sources.add(sourceRef);
243     }
244
245     private static Optional<Revision> toYangCommon(final @Nullable RevisionIdentifier revisionIdentifier) {
246         return revisionIdentifier == null ? Optional.empty() : Optional.of(Revision.of(revisionIdentifier.getValue()));
247     }
248 }