2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.binding.runtime.spi;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
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;
28 import java.util.Map.Entry;
29 import java.util.Optional;
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;
58 * Abstract base class for things that create an EffectiveModuleContext or similar things from a (dynamic) set of
59 * YangModuleInfo objects.
62 * Note this class has some locking quirks and may end up being further refactored.
64 abstract class AbstractModuleInfoTracker implements Mutable {
65 abstract static class AbstractRegisteredModuleInfo {
66 final YangTextSchemaSourceRegistration reg;
67 final YangModuleInfo info;
68 final ClassLoader loader;
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);
78 public final String toString() {
79 return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
82 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
83 return helper.add("info", info).add("registration", reg).add("classLoader", loader);
87 private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
88 private int refcount = 1;
90 ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
91 final ClassLoader loader) {
92 super(info, reg, loader);
100 return --refcount == 0;
104 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
105 return super.addToStringAttributes(helper).add("refCount", refcount);
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);
116 private static final Logger LOG = LoggerFactory.getLogger(AbstractModuleInfoTracker.class);
118 private final YangTextSchemaContextResolver ctxResolver;
121 private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
122 MultimapBuilder.hashKeys().arrayListValues().build();
124 private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
125 MultimapBuilder.hashKeys().arrayListValues().build();
127 private @Nullable ModuleInfoSnapshot currentSnapshot;
129 AbstractModuleInfoTracker(final YangTextSchemaContextResolver resolver) {
130 this.ctxResolver = requireNonNull(resolver);
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)));
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));
148 final ImmutableList<ExplicitRegisteredModuleInfo> regInfos = regBuilder.build();
150 return new AbstractObjectRegistration<>(moduleInfo) {
152 protected void removeRegistration() {
153 unregister(regInfos);
159 final void registerImplicitBindingClass(final Class<?> bindingClass) {
160 registerImplicitModuleInfo(BindingRuntimeHelpers.extractYangModuleInfo(bindingClass));
164 final @Nullable ClassLoader findClassLoader(final String fullyQualifiedName) {
165 // This performs an explicit check for binding classes
166 final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName);
168 // Try to find a loaded class loader
169 // FIXME: two-step process, try explicit registrations first
170 for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) {
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.
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);
190 final YangTextSchemaSourceRegistration reg;
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);
198 final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg,
199 infoClass.getClassLoader());
200 sourceToInfoReg.put(sourceId, regInfo);
201 packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo);
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.
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;
217 LOG.debug("Reusing explicit registration {}", explicit);
222 // Create an explicit registration
223 final YangTextSchemaSourceRegistration reg;
225 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
226 } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
227 throw new IllegalStateException("Failed to register info " + info, e);
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);
236 sourceToInfoReg.put(sourceId, regInfo);
237 removeImplicit(sourceToInfoReg.get(sourceId));
238 packageToInfoReg.put(packageName, regInfo);
239 removeImplicit(packageToInfoReg.get(packageName));
244 // Reconsider utility of this
245 final Optional<? extends EffectiveModelContext> getResolverEffectiveModel() {
246 return ctxResolver.getEffectiveModelContext();
250 final ListenableFuture<? extends YangTextSchemaSource> getResolverSource(final SourceIdentifier sourceIdentifier) {
251 return ctxResolver.getSource(sourceIdentifier);
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)) {
262 return updateSnapshot(effectiveModel);
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();
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);
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);
285 AbstractRegisteredModuleInfo reg = regs.stream()
286 .filter(ExplicitRegisteredModuleInfo.class::isInstance).findFirst()
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());
299 final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
300 currentSnapshot = next;
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);
313 final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
314 if (!sourceToInfoReg.remove(sourceId, regInfo)) {
315 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
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);
328 private static void removeImplicit(final List<AbstractRegisteredModuleInfo> regs) {
330 * Search for implicit registration for a sourceId/packageName.
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.
336 * This means that if an implicit registration exists, it will be the first entry in the list.
338 final AbstractRegisteredModuleInfo reg = regs.get(0);
339 if (reg instanceof ImplicitRegisteredModuleInfo) {
340 LOG.debug("Removing implicit registration {}", reg);
346 private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
347 final YangModuleInfo moduleInfo) {
348 return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
351 private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
352 final QName name = moduleInfo.getName();
353 return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
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);
361 // ... now reverse the order in an effort to register dependencies first (triggering module last)
362 return ImmutableList.copyOf(requiredInfos).reverse();
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);