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.yang.binding.YangModuleInfo;
40 import org.opendaylight.yangtools.yang.common.QName;
41 import org.opendaylight.yangtools.yang.common.QNameModule;
42 import org.opendaylight.yangtools.yang.common.Revision;
43 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
44 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
45 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
46 import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory;
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 * 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 YangTextSchemaSourceRegistration reg;
66 final YangModuleInfo info;
67 final ClassLoader loader;
69 private int refcount = 1;
71 RegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
72 final ClassLoader loader) {
73 this.info = requireNonNull(info);
74 this.reg = requireNonNull(reg);
75 this.loader = requireNonNull(loader);
83 return --refcount == 0;
87 public String toString() {
88 return MoreObjects.toStringHelper(this).add("info", info).add("registration", reg)
89 .add("classLoader", loader).add("refCount", refcount).toString();
93 private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoSnapshotResolver.class);
95 private final YangTextSchemaContextResolver ctxResolver;
98 private final ListMultimap<SourceIdentifier, RegisteredModuleInfo> sourceToInfoReg =
99 MultimapBuilder.hashKeys().arrayListValues().build();
101 private @Nullable ModuleInfoSnapshot currentSnapshot;
103 public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
104 ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
107 public synchronized List<ObjectRegistration<YangModuleInfo>> registerModuleInfos(
108 final Iterable<? extends YangModuleInfo> moduleInfos) {
109 final List<ObjectRegistration<YangModuleInfo>> ret = new ArrayList<>();
110 for (YangModuleInfo yangModuleInfo : moduleInfos) {
111 ret.add(register(requireNonNull(yangModuleInfo)));
117 private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
118 final Builder<RegisteredModuleInfo> regBuilder = ImmutableList.builder();
119 for (YangModuleInfo info : flatDependencies(moduleInfo)) {
120 regBuilder.add(registerModuleInfo(info));
122 final ImmutableList<RegisteredModuleInfo> regInfos = regBuilder.build();
124 return new AbstractObjectRegistration<>(moduleInfo) {
126 protected void removeRegistration() {
127 unregister(regInfos);
133 * Perform registration of a YangModuleInfo.
136 private RegisteredModuleInfo registerModuleInfo(final @NonNull YangModuleInfo info) {
137 // First search for an existing explicit registration
138 final SourceIdentifier sourceId = sourceIdentifierFrom(info);
139 for (RegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
140 if (info.equals(reg.info)) {
142 LOG.debug("Reusing registration {}", reg);
147 // Create an explicit registration
148 final YangTextSchemaSourceRegistration reg;
150 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
151 } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
152 throw new IllegalStateException("Failed to register info " + info, e);
155 final RegisteredModuleInfo regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
156 LOG.debug("Created new registration {}", regInfo);
158 sourceToInfoReg.put(sourceId, regInfo);
162 public synchronized @NonNull ModuleInfoSnapshot takeSnapshot() {
163 final EffectiveModelContext effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow();
164 final ModuleInfoSnapshot local = currentSnapshot;
165 if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) {
169 return updateSnapshot(effectiveModel);
173 private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) {
174 // Alright, now let's find out which sources got captured
175 final Set<SourceIdentifier> sources = new HashSet<>();
176 for (Entry<QNameModule, ModuleEffectiveStatement> entry : effectiveModel.getModuleStatements().entrySet()) {
177 final Optional<Revision> revision = entry.getKey().getRevision();
178 final ModuleEffectiveStatement module = entry.getValue();
180 sources.add(RevisionSourceIdentifier.create(module.argument().getLocalName(), revision));
181 module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class)
182 .map(submodule -> RevisionSourceIdentifier.create(submodule.argument().getLocalName(), revision))
183 .forEach(sources::add);
186 final Map<SourceIdentifier, YangModuleInfo> moduleInfos = new HashMap<>();
187 final Map<String, ClassLoader> classLoaders = new HashMap<>();
188 for (SourceIdentifier source : sources) {
189 final List<RegisteredModuleInfo> regs = sourceToInfoReg.get(source);
190 checkState(!regs.isEmpty(), "No registration for %s", source);
192 final RegisteredModuleInfo reg = regs.get(0);
193 final YangModuleInfo info = reg.info;
194 moduleInfos.put(source, info);
195 final Class<?> infoClass = info.getClass();
196 classLoaders.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()),
197 infoClass.getClassLoader());
200 final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
201 currentSnapshot = next;
205 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
206 justification = "https://github.com/spotbugs/spotbugs/issues/811")
207 private synchronized void unregister(final ImmutableList<RegisteredModuleInfo> regInfos) {
208 for (RegisteredModuleInfo regInfo : regInfos) {
209 if (!regInfo.decRef()) {
210 LOG.debug("Registration {} has references, not removing it", regInfo);
214 final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
215 if (!sourceToInfoReg.remove(sourceId, regInfo)) {
216 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
223 static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
224 return YangTextSchemaSource.delegateForByteSource(sourceIdentifierFrom(moduleInfo),
225 moduleInfo.getYangTextByteSource());
228 private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
229 final YangModuleInfo moduleInfo) {
230 return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
233 private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
234 final QName name = moduleInfo.getName();
235 return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
238 private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
239 // Flatten the modules being registered, with the triggering module being first...
240 final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
241 flatDependencies(requiredInfos, moduleInfo);
243 // ... now reverse the order in an effort to register dependencies first (triggering module last)
244 return ImmutableList.copyOf(requiredInfos).reverse();
247 static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
248 if (set.add(moduleInfo)) {
249 for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
250 flatDependencies(set, dep);