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