Rework binding component instantiation
[mdsal.git] / binding / mdsal-binding-runtime-spi / src / main / java / org / opendaylight / binding / runtime / spi / AbstractModuleInfoTracker.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.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.base.MoreObjects;
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.ImmutableList.Builder;
17 import com.google.common.collect.ListMultimap;
18 import com.google.common.collect.MultimapBuilder;
19 import com.google.common.util.concurrent.ListenableFuture;
20 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Optional;
30 import java.util.Set;
31 import org.checkerframework.checker.lock.qual.GuardedBy;
32 import org.checkerframework.checker.lock.qual.Holding;
33 import org.eclipse.jdt.annotation.NonNull;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot;
36 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
37 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
38 import org.opendaylight.yangtools.concepts.Mutable;
39 import org.opendaylight.yangtools.concepts.ObjectRegistration;
40 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
41 import org.opendaylight.yangtools.yang.common.QName;
42 import org.opendaylight.yangtools.yang.common.QNameModule;
43 import org.opendaylight.yangtools.yang.common.Revision;
44 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
45 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
46 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
47 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
48 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
49 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
50 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
51 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
52 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
53 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaSourceRegistration;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * Abstract base class for things that create an EffectiveModuleContext or similar things from a (dynamic) set of
59  * YangModuleInfo objects.
60  *
61  * <p>
62  * Note this class has some locking quirks and may end up being further refactored.
63  */
64 abstract class AbstractModuleInfoTracker implements Mutable {
65     abstract static class AbstractRegisteredModuleInfo {
66         final YangTextSchemaSourceRegistration reg;
67         final YangModuleInfo info;
68         final ClassLoader loader;
69
70         AbstractRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
71             final ClassLoader loader) {
72             this.info = requireNonNull(info);
73             this.reg = requireNonNull(reg);
74             this.loader = requireNonNull(loader);
75         }
76
77         @Override
78         public final String toString() {
79             return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
80         }
81
82         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
83             return helper.add("info", info).add("registration", reg).add("classLoader", loader);
84         }
85     }
86
87     private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
88         private int refcount = 1;
89
90         ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
91                 final ClassLoader loader) {
92             super(info, reg, loader);
93         }
94
95         void incRef() {
96             ++refcount;
97         }
98
99         boolean decRef() {
100             return --refcount == 0;
101         }
102
103         @Override
104         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
105             return super.addToStringAttributes(helper).add("refCount", refcount);
106         }
107     }
108
109     private static final class ImplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
110         ImplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
111                 final ClassLoader loader) {
112             super(info, reg, loader);
113         }
114     }
115
116     private static final Logger LOG = LoggerFactory.getLogger(AbstractModuleInfoTracker.class);
117
118     private final YangTextSchemaContextResolver ctxResolver;
119
120     @GuardedBy("this")
121     private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
122             MultimapBuilder.hashKeys().arrayListValues().build();
123     @GuardedBy("this")
124     private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
125             MultimapBuilder.hashKeys().arrayListValues().build();
126     @GuardedBy("this")
127     private @Nullable ModuleInfoSnapshot currentSnapshot;
128
129     AbstractModuleInfoTracker(final YangTextSchemaContextResolver resolver) {
130         this.ctxResolver = requireNonNull(resolver);
131     }
132
133     public final synchronized List<ObjectRegistration<YangModuleInfo>> registerModuleInfos(
134             final Iterable<? extends YangModuleInfo> moduleInfos) {
135         final List<ObjectRegistration<YangModuleInfo>> ret = new ArrayList<>();
136         for (YangModuleInfo yangModuleInfo : moduleInfos) {
137             ret.add(register(requireNonNull(yangModuleInfo)));
138         }
139         return ret;
140     }
141
142     @Holding("this")
143     private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
144         final Builder<ExplicitRegisteredModuleInfo> regBuilder = ImmutableList.builder();
145         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
146             regBuilder.add(registerExplicitModuleInfo(info));
147         }
148         final ImmutableList<ExplicitRegisteredModuleInfo> regInfos = regBuilder.build();
149
150         return new AbstractObjectRegistration<>(moduleInfo) {
151             @Override
152             protected void removeRegistration() {
153                 unregister(regInfos);
154             }
155         };
156     }
157
158     @Holding("this")
159     final void registerImplicitBindingClass(final Class<?> bindingClass) {
160         registerImplicitModuleInfo(BindingRuntimeHelpers.extractYangModuleInfo(bindingClass));
161     }
162
163     @Holding("this")
164     final @Nullable ClassLoader findClassLoader(final String fullyQualifiedName) {
165         // This performs an explicit check for binding classes
166         final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName);
167
168         // Try to find a loaded class loader
169         // FIXME: two-step process, try explicit registrations first
170         for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) {
171             return reg.loader;
172         }
173         return null;
174     }
175
176     /*
177      * Perform implicit registration of a YangModuleInfo and any of its dependencies. If there is a registration for
178      * a particular source, we do not create a duplicate registration.
179      */
180     @Holding("this")
181     private void registerImplicitModuleInfo(final @NonNull YangModuleInfo moduleInfo) {
182         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
183             final Class<?> infoClass = info.getClass();
184             final SourceIdentifier sourceId = sourceIdentifierFrom(info);
185             if (sourceToInfoReg.containsKey(sourceId)) {
186                 LOG.debug("Skipping implicit registration of {} as source {} is already registered", info, sourceId);
187                 continue;
188             }
189
190             final YangTextSchemaSourceRegistration reg;
191             try {
192                 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
193             } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
194                 LOG.warn("Failed to register info {} source {}, ignoring it", info, sourceId, e);
195                 continue;
196             }
197
198             final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg,
199                 infoClass.getClassLoader());
200             sourceToInfoReg.put(sourceId, regInfo);
201             packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo);
202         }
203     }
204
205     /*
206      * Perform explicit registration of a YangModuleInfo. This always results in a new explicit registration. In case
207      * there is a pre-existing implicit registration, it is removed just after the explicit registration is made.
208      */
209     @Holding("this")
210     private ExplicitRegisteredModuleInfo registerExplicitModuleInfo(final @NonNull YangModuleInfo info) {
211         // First search for an existing explicit registration
212         final SourceIdentifier sourceId = sourceIdentifierFrom(info);
213         for (AbstractRegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
214             if (reg instanceof ExplicitRegisteredModuleInfo && info.equals(reg.info)) {
215                 final ExplicitRegisteredModuleInfo explicit = (ExplicitRegisteredModuleInfo) reg;
216                 explicit.incRef();
217                 LOG.debug("Reusing explicit registration {}", explicit);
218                 return explicit;
219             }
220         }
221
222         // Create an explicit registration
223         final YangTextSchemaSourceRegistration reg;
224         try {
225             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
226         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
227             throw new IllegalStateException("Failed to register info " + info, e);
228         }
229
230         final Class<?> infoClass = info.getClass();
231         final String packageName = BindingReflections.getModelRootPackageName(infoClass.getPackage());
232         final ExplicitRegisteredModuleInfo regInfo = new ExplicitRegisteredModuleInfo(info, reg,
233             infoClass.getClassLoader());
234         LOG.debug("Created new explicit registration {}", regInfo);
235
236         sourceToInfoReg.put(sourceId, regInfo);
237         removeImplicit(sourceToInfoReg.get(sourceId));
238         packageToInfoReg.put(packageName, regInfo);
239         removeImplicit(packageToInfoReg.get(packageName));
240
241         return regInfo;
242     }
243
244     // Reconsider utility of this
245     final Optional<? extends EffectiveModelContext> getResolverEffectiveModel() {
246         return ctxResolver.getEffectiveModelContext();
247     }
248
249     @Deprecated
250     final ListenableFuture<? extends YangTextSchemaSource> getResolverSource(final SourceIdentifier sourceIdentifier) {
251         return ctxResolver.getSource(sourceIdentifier);
252     }
253
254     @Holding("this")
255     final @NonNull ModuleInfoSnapshot updateSnapshot() {
256         final EffectiveModelContext effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow();
257         final ModuleInfoSnapshot local = currentSnapshot;
258         if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) {
259             return local;
260         }
261
262         return updateSnapshot(effectiveModel);
263     }
264
265     @Holding("this")
266     private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) {
267         // Alright, now let's find out which sources got captured
268         final Set<SourceIdentifier> sources = new HashSet<>();
269         for (Entry<QNameModule, ModuleEffectiveStatement> entry : effectiveModel.getModuleStatements().entrySet()) {
270             final Optional<Revision> revision = entry.getKey().getRevision();
271             final ModuleEffectiveStatement module = entry.getValue();
272
273             sources.add(RevisionSourceIdentifier.create(module.argument(), revision));
274             module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class)
275                 .map(submodule -> RevisionSourceIdentifier.create(submodule.argument(), revision))
276                 .forEach(sources::add);
277         }
278
279         final Map<SourceIdentifier, YangModuleInfo> moduleInfos = new HashMap<>();
280         final Map<String, ClassLoader> classLoaders = new HashMap<>();
281         for (SourceIdentifier source : sources) {
282             final List<AbstractRegisteredModuleInfo> regs = sourceToInfoReg.get(source);
283             checkState(!regs.isEmpty(), "No registration for %s", source);
284
285             AbstractRegisteredModuleInfo reg = regs.stream()
286                     .filter(ExplicitRegisteredModuleInfo.class::isInstance).findFirst()
287                     .orElse(null);
288             if (reg == null) {
289                 reg = regs.get(0);
290             }
291
292             final YangModuleInfo info = reg.info;
293             moduleInfos.put(source, info);
294             final Class<?> infoClass = info.getClass();
295             classLoaders.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()),
296                 infoClass.getClassLoader());
297         }
298
299         final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
300         currentSnapshot = next;
301         return next;
302     }
303
304     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
305                 justification = "https://github.com/spotbugs/spotbugs/issues/811")
306     private synchronized void unregister(final ImmutableList<ExplicitRegisteredModuleInfo> regInfos) {
307         for (ExplicitRegisteredModuleInfo regInfo : regInfos) {
308             if (!regInfo.decRef()) {
309                 LOG.debug("Registration {} has references, not removing it", regInfo);
310                 continue;
311             }
312
313             final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
314             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
315                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
316             }
317
318             final String packageName = BindingReflections.getModelRootPackageName(regInfo.info.getClass().getPackage());
319             if (!packageToInfoReg.remove(packageName, regInfo)) {
320                 LOG.warn("Failed to find {} registered under {}", regInfo, packageName);
321             }
322
323             regInfo.reg.close();
324         }
325     }
326
327     @Holding("this")
328     private static void removeImplicit(final List<AbstractRegisteredModuleInfo> regs) {
329         /*
330          * Search for implicit registration for a sourceId/packageName.
331          *
332          * Since we are called while an explicit registration is being created (and has already been inserted, we know
333          * there is at least one entry in the maps. We also know registrations retain the order in which they were
334          * created and that implicit registrations are not created if there already is a registration.
335          *
336          * This means that if an implicit registration exists, it will be the first entry in the list.
337          */
338         final AbstractRegisteredModuleInfo reg = regs.get(0);
339         if (reg instanceof ImplicitRegisteredModuleInfo) {
340             LOG.debug("Removing implicit registration {}", reg);
341             regs.remove(0);
342             reg.reg.close();
343         }
344     }
345
346     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
347             final YangModuleInfo moduleInfo) {
348         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
349     }
350
351     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
352         final QName name = moduleInfo.getName();
353         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
354     }
355
356     private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
357         // Flatten the modules being registered, with the triggering module being first...
358         final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
359         flatDependencies(requiredInfos, moduleInfo);
360
361         // ... now reverse the order in an effort to register dependencies first (triggering module last)
362         return ImmutableList.copyOf(requiredInfos).reverse();
363     }
364
365     private static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
366         if (set.add(moduleInfo)) {
367             for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
368                 flatDependencies(set, dep);
369             }
370         }
371     }
372 }