ab655c878286b1d785a09d6989594e6db370dcbe
[mdsal.git] / binding / mdsal-binding-runtime-spi / src / main / java / org / opendaylight / mdsal / binding / runtime / spi / ModuleInfoSnapshotResolver.java
1 /*
2  * Copyright (c) 2020 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.binding.runtime.spi;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.MoreObjects;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.ListMultimap;
18 import com.google.common.collect.MultimapBuilder;
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.LinkedHashSet;
24 import java.util.List;
25 import java.util.Set;
26 import org.checkerframework.checker.lock.qual.GuardedBy;
27 import org.checkerframework.checker.lock.qual.Holding;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.opendaylight.mdsal.binding.runtime.api.ModuleInfoSnapshot;
31 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
32 import org.opendaylight.yangtools.concepts.AbstractRegistration;
33 import org.opendaylight.yangtools.concepts.Mutable;
34 import org.opendaylight.yangtools.concepts.Registration;
35 import org.opendaylight.yangtools.yang.binding.DataRoot;
36 import org.opendaylight.yangtools.yang.binding.YangFeature;
37 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
38 import org.opendaylight.yangtools.yang.binding.contract.Naming;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.common.Revision;
41 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
42 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
43 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
44 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
45 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
46 import org.opendaylight.yangtools.yang.parser.api.YangParserFactory;
47 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
48 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * A dynamic registry of {@link YangModuleInfo} objects, capable of combining them into an
54  * {@link ModuleInfoSnapshot}. If you need a one-shot way of creating an ModuleInfoSnapshot, prefer
55  * {@link ModuleInfoSnapshotBuilder} instead.
56  */
57 @Beta
58 public final class ModuleInfoSnapshotResolver implements Mutable {
59     private static final class RegisteredModuleInfo {
60         final Registration reg;
61         final YangModuleInfo info;
62         final ClassLoader loader;
63
64         private int refcount = 1;
65
66         RegisteredModuleInfo(final YangModuleInfo info, final Registration reg, final ClassLoader loader) {
67             this.info = requireNonNull(info);
68             this.reg = requireNonNull(reg);
69             this.loader = requireNonNull(loader);
70         }
71
72         void incRef() {
73             ++refcount;
74         }
75
76         boolean decRef() {
77             return --refcount == 0;
78         }
79
80         @Override
81         public String toString() {
82             return MoreObjects.toStringHelper(this)
83                 .add("info", info)
84                 .add("registration", reg)
85                 .add("classLoader", loader)
86                 .add("refCount", refcount)
87                 .toString();
88         }
89     }
90
91     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoSnapshotResolver.class);
92
93     private final YangTextSchemaContextResolver ctxResolver;
94
95     @GuardedBy("this")
96     private final ListMultimap<SourceIdentifier, RegisteredModuleInfo> sourceToInfoReg =
97             MultimapBuilder.hashKeys().arrayListValues().build();
98     @GuardedBy("this")
99     private final ListMultimap<Class<? extends DataRoot>, ImmutableSet<YangFeature<?, ?>>> moduleToFeatures =
100             MultimapBuilder.hashKeys().arrayListValues().build();
101     @GuardedBy("this")
102     private @Nullable ModuleInfoSnapshot currentSnapshot;
103
104     public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
105         ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
106     }
107
108     public synchronized <R extends @NonNull DataRoot> Registration registerModuleFeatures(final Class<R> module,
109             final Set<? extends YangFeature<?, R>> supportedFeatures) {
110         final var features = supportedFeatures.stream().map(YangFeature::qname).map(QName::getLocalName).sorted()
111             .collect(ImmutableSet.toImmutableSet());
112         return ctxResolver.registerSupportedFeatures(BindingReflections.getQNameModule(module), features);
113     }
114
115     public synchronized List<Registration> registerModuleInfos(final Iterable<? extends YangModuleInfo> moduleInfos) {
116         final var ret = new ArrayList<Registration>();
117         for (var moduleInfo : moduleInfos) {
118             ret.add(register(requireNonNull(moduleInfo)));
119         }
120         return ret;
121     }
122
123     @Holding("this")
124     private Registration register(final @NonNull YangModuleInfo moduleInfo) {
125         final var regInfos = flatDependencies(moduleInfo).stream()
126             .map(this::registerModuleInfo)
127             .collect(ImmutableList.toImmutableList());
128
129         return new AbstractRegistration() {
130             @Override
131             protected void removeRegistration() {
132                 unregister(regInfos);
133             }
134         };
135     }
136
137     /*
138      * Perform registration of a YangModuleInfo.
139      */
140     @Holding("this")
141     private RegisteredModuleInfo registerModuleInfo(final @NonNull YangModuleInfo info) {
142         // First search for an existing explicit registration
143         final var sourceId = sourceIdentifierFrom(info);
144         for (var reg : sourceToInfoReg.get(sourceId)) {
145             if (info.equals(reg.info)) {
146                 reg.incRef();
147                 LOG.debug("Reusing registration {}", reg);
148                 return reg;
149             }
150         }
151
152         // Create an explicit registration
153         final Registration reg;
154         try {
155             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
156         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
157             throw new IllegalStateException("Failed to register info " + info, e);
158         }
159
160         final var regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
161         LOG.debug("Created new registration {}", regInfo);
162
163         sourceToInfoReg.put(sourceId, regInfo);
164         return regInfo;
165     }
166
167     public synchronized @NonNull ModuleInfoSnapshot takeSnapshot() {
168         final var effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow();
169         final var local = currentSnapshot;
170         if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) {
171             return local;
172         }
173
174         return updateSnapshot(effectiveModel);
175     }
176
177     @Holding("this")
178     private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) {
179         // Alright, now let's find out which sources got captured
180         final var sources = new HashSet<SourceIdentifier>();
181         for (var entry : effectiveModel.getModuleStatements().entrySet()) {
182             final var revision = entry.getKey().getRevision().orElse(null);
183             final var module = entry.getValue();
184
185             sources.add(new SourceIdentifier(module.argument(), revision));
186             module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class)
187                 .map(submodule -> new SourceIdentifier(submodule.argument(), revision))
188                 .forEach(sources::add);
189         }
190
191         final var moduleInfos = new HashMap<SourceIdentifier, YangModuleInfo>();
192         final var classLoaders = new HashMap<String, ClassLoader>();
193         for (var source : sources) {
194             final var regs = sourceToInfoReg.get(source);
195             checkState(!regs.isEmpty(), "No registration for %s", source);
196
197             final var reg = regs.get(0);
198             final var info = reg.info;
199             moduleInfos.put(source, info);
200             final var infoClass = info.getClass();
201             classLoaders.put(Naming.getModelRootPackageName(infoClass.getPackage().getName()),
202                 infoClass.getClassLoader());
203         }
204
205         final var next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
206         currentSnapshot = next;
207         return next;
208     }
209
210     private synchronized void unregister(final List<RegisteredModuleInfo> regInfos) {
211         for (var regInfo : regInfos) {
212             if (!regInfo.decRef()) {
213                 LOG.debug("Registration {} has references, not removing it", regInfo);
214                 continue;
215             }
216
217             final var sourceId = sourceIdentifierFrom(regInfo.info);
218             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
219                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
220             }
221
222             regInfo.reg.close();
223         }
224     }
225
226     static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
227         return YangTextSchemaSource.delegateForCharSource(sourceIdentifierFrom(moduleInfo),
228             moduleInfo.getYangTextCharSource());
229     }
230
231     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
232             final YangModuleInfo moduleInfo) {
233         return YangTextSchemaSource.delegateForCharSource(identifier, moduleInfo.getYangTextCharSource());
234     }
235
236     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
237         final var name = moduleInfo.getName();
238         return new SourceIdentifier(name.getLocalName(), name.getRevision().map(Revision::toString).orElse(null));
239     }
240
241     private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
242         // Flatten the modules being registered, with the triggering module being first...
243         final var requiredInfos = new LinkedHashSet<YangModuleInfo>();
244         flatDependencies(requiredInfos, moduleInfo);
245
246         // ... now reverse the order in an effort to register dependencies first (triggering module last)
247         return ImmutableList.copyOf(requiredInfos).reverse();
248     }
249
250     static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
251         if (set.add(moduleInfo)) {
252             for (var dep : moduleInfo.getImportedModules()) {
253                 flatDependencies(set, dep);
254             }
255         }
256     }
257 }