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.mdsal.binding.runtime.spi;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
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.ImmutableList.Builder;
17 import com.google.common.collect.ListMultimap;
18 import com.google.common.collect.MultimapBuilder;
19 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.LinkedHashSet;
25 import java.util.List;
27 import java.util.Map.Entry;
28 import java.util.Optional;
30 import org.checkerframework.checker.lock.qual.GuardedBy;
31 import org.checkerframework.checker.lock.qual.Holding;
32 import org.eclipse.jdt.annotation.NonNull;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.opendaylight.mdsal.binding.runtime.api.ModuleInfoSnapshot;
35 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
36 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
37 import org.opendaylight.yangtools.concepts.Mutable;
38 import org.opendaylight.yangtools.concepts.ObjectRegistration;
39 import org.opendaylight.yangtools.concepts.Registration;
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.repo.api.RevisionSourceIdentifier;
48 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
49 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
50 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
51 import org.opendaylight.yangtools.yang.parser.api.YangParserFactory;
52 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
53 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * A dynamic registry of {@link YangModuleInfo} objects, capable of combining them into an
59 * {@link ModuleInfoSnapshot}. If you need a one-shot way of creating an ModuleInfoSnapshot, prefer
60 * {@link ModuleInfoSnapshotBuilder} instead.
63 public final class ModuleInfoSnapshotResolver implements Mutable {
64 private static final class RegisteredModuleInfo {
65 final Registration reg;
66 final YangModuleInfo info;
67 final ClassLoader loader;
69 private int refcount = 1;
71 RegisteredModuleInfo(final YangModuleInfo info, final Registration reg, final ClassLoader loader) {
72 this.info = requireNonNull(info);
73 this.reg = requireNonNull(reg);
74 this.loader = requireNonNull(loader);
82 return --refcount == 0;
86 public String toString() {
87 return MoreObjects.toStringHelper(this).add("info", info).add("registration", reg)
88 .add("classLoader", loader).add("refCount", refcount).toString();
92 private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoSnapshotResolver.class);
94 private final YangTextSchemaContextResolver ctxResolver;
97 private final ListMultimap<SourceIdentifier, RegisteredModuleInfo> sourceToInfoReg =
98 MultimapBuilder.hashKeys().arrayListValues().build();
100 private @Nullable ModuleInfoSnapshot currentSnapshot;
102 public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
103 ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
106 public synchronized List<ObjectRegistration<YangModuleInfo>> registerModuleInfos(
107 final Iterable<? extends YangModuleInfo> moduleInfos) {
108 final List<ObjectRegistration<YangModuleInfo>> ret = new ArrayList<>();
109 for (YangModuleInfo yangModuleInfo : moduleInfos) {
110 ret.add(register(requireNonNull(yangModuleInfo)));
116 private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
117 final Builder<RegisteredModuleInfo> regBuilder = ImmutableList.builder();
118 for (YangModuleInfo info : flatDependencies(moduleInfo)) {
119 regBuilder.add(registerModuleInfo(info));
121 final ImmutableList<RegisteredModuleInfo> regInfos = regBuilder.build();
123 return new AbstractObjectRegistration<>(moduleInfo) {
125 protected void removeRegistration() {
126 unregister(regInfos);
132 * Perform registration of a YangModuleInfo.
135 private RegisteredModuleInfo registerModuleInfo(final @NonNull YangModuleInfo info) {
136 // First search for an existing explicit registration
137 final SourceIdentifier sourceId = sourceIdentifierFrom(info);
138 for (RegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
139 if (info.equals(reg.info)) {
141 LOG.debug("Reusing registration {}", reg);
146 // Create an explicit registration
147 final Registration reg;
149 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
150 } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
151 throw new IllegalStateException("Failed to register info " + info, e);
154 final RegisteredModuleInfo regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
155 LOG.debug("Created new registration {}", regInfo);
157 sourceToInfoReg.put(sourceId, regInfo);
161 public synchronized @NonNull ModuleInfoSnapshot takeSnapshot() {
162 final EffectiveModelContext effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow();
163 final ModuleInfoSnapshot local = currentSnapshot;
164 if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) {
168 return updateSnapshot(effectiveModel);
172 private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) {
173 // Alright, now let's find out which sources got captured
174 final Set<SourceIdentifier> sources = new HashSet<>();
175 for (Entry<QNameModule, ModuleEffectiveStatement> entry : effectiveModel.getModuleStatements().entrySet()) {
176 final Optional<Revision> revision = entry.getKey().getRevision();
177 final ModuleEffectiveStatement module = entry.getValue();
179 sources.add(RevisionSourceIdentifier.create(module.argument().getLocalName(), revision));
180 module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class)
181 .map(submodule -> RevisionSourceIdentifier.create(submodule.argument().getLocalName(), revision))
182 .forEach(sources::add);
185 final Map<SourceIdentifier, YangModuleInfo> moduleInfos = new HashMap<>();
186 final Map<String, ClassLoader> classLoaders = new HashMap<>();
187 for (SourceIdentifier source : sources) {
188 final List<RegisteredModuleInfo> regs = sourceToInfoReg.get(source);
189 checkState(!regs.isEmpty(), "No registration for %s", source);
191 final RegisteredModuleInfo reg = regs.get(0);
192 final YangModuleInfo info = reg.info;
193 moduleInfos.put(source, info);
194 final Class<?> infoClass = info.getClass();
195 classLoaders.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()),
196 infoClass.getClassLoader());
199 final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
200 currentSnapshot = next;
204 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
205 justification = "https://github.com/spotbugs/spotbugs/issues/811")
206 private synchronized void unregister(final ImmutableList<RegisteredModuleInfo> regInfos) {
207 for (RegisteredModuleInfo regInfo : regInfos) {
208 if (!regInfo.decRef()) {
209 LOG.debug("Registration {} has references, not removing it", regInfo);
213 final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
214 if (!sourceToInfoReg.remove(sourceId, regInfo)) {
215 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
222 static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
223 return YangTextSchemaSource.delegateForByteSource(sourceIdentifierFrom(moduleInfo),
224 moduleInfo.getYangTextByteSource());
227 private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
228 final YangModuleInfo moduleInfo) {
229 return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
232 private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
233 final QName name = moduleInfo.getName();
234 return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
237 private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
238 // Flatten the modules being registered, with the triggering module being first...
239 final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
240 flatDependencies(requiredInfos, moduleInfo);
242 // ... now reverse the order in an effort to register dependencies first (triggering module last)
243 return ImmutableList.copyOf(requiredInfos).reverse();
246 static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
247 if (set.add(moduleInfo)) {
248 for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
249 flatDependencies(set, dep);