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.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;
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.AbstractObjectRegistration;
33 import org.opendaylight.yangtools.concepts.Mutable;
34 import org.opendaylight.yangtools.concepts.ObjectRegistration;
35 import org.opendaylight.yangtools.concepts.Registration;
36 import org.opendaylight.yangtools.yang.binding.DataRoot;
37 import org.opendaylight.yangtools.yang.binding.YangFeature;
38 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
39 import org.opendaylight.yangtools.yang.binding.contract.Naming;
40 import org.opendaylight.yangtools.yang.common.QName;
41 import org.opendaylight.yangtools.yang.common.Revision;
42 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
43 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
44 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
45 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
46 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
47 import org.opendaylight.yangtools.yang.parser.api.YangParserFactory;
48 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
49 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * A dynamic registry of {@link YangModuleInfo} objects, capable of combining them into an
55 * {@link ModuleInfoSnapshot}. If you need a one-shot way of creating an ModuleInfoSnapshot, prefer
56 * {@link ModuleInfoSnapshotBuilder} instead.
59 public final class ModuleInfoSnapshotResolver implements Mutable {
60 private static final class RegisteredModuleInfo {
61 final Registration reg;
62 final YangModuleInfo info;
63 final ClassLoader loader;
65 private int refcount = 1;
67 RegisteredModuleInfo(final YangModuleInfo info, final Registration reg, final ClassLoader loader) {
68 this.info = requireNonNull(info);
69 this.reg = requireNonNull(reg);
70 this.loader = requireNonNull(loader);
78 return --refcount == 0;
82 public String toString() {
83 return MoreObjects.toStringHelper(this)
85 .add("registration", reg)
86 .add("classLoader", loader)
87 .add("refCount", refcount)
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 final ListMultimap<Class<? extends DataRoot>, ImmutableSet<YangFeature<?, ?>>> moduleToFeatures =
101 MultimapBuilder.hashKeys().arrayListValues().build();
103 private @Nullable ModuleInfoSnapshot currentSnapshot;
105 public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
106 ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
109 public synchronized <R extends @NonNull DataRoot> Registration registerModuleFeatures(final Class<R> module,
110 final Set<? extends YangFeature<?, R>> supportedFeatures) {
111 final var features = supportedFeatures.stream().map(YangFeature::qname).map(QName::getLocalName).sorted()
112 .collect(ImmutableSet.toImmutableSet());
113 return ctxResolver.registerSupportedFeatures(BindingReflections.getQNameModule(module), features);
116 public synchronized List<ObjectRegistration<YangModuleInfo>> registerModuleInfos(
117 final Iterable<? extends YangModuleInfo> moduleInfos) {
118 final var ret = new ArrayList<ObjectRegistration<YangModuleInfo>>();
119 for (var moduleInfo : moduleInfos) {
120 ret.add(register(requireNonNull(moduleInfo)));
126 private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
127 final var regInfos = flatDependencies(moduleInfo).stream()
128 .map(this::registerModuleInfo)
129 .collect(ImmutableList.toImmutableList());
131 return new AbstractObjectRegistration<>(moduleInfo) {
133 protected void removeRegistration() {
134 unregister(regInfos);
140 * Perform registration of a YangModuleInfo.
143 private RegisteredModuleInfo registerModuleInfo(final @NonNull YangModuleInfo info) {
144 // First search for an existing explicit registration
145 final var sourceId = sourceIdentifierFrom(info);
146 for (var reg : sourceToInfoReg.get(sourceId)) {
147 if (info.equals(reg.info)) {
149 LOG.debug("Reusing registration {}", reg);
154 // Create an explicit registration
155 final Registration reg;
157 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
158 } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
159 throw new IllegalStateException("Failed to register info " + info, e);
162 final var regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
163 LOG.debug("Created new registration {}", regInfo);
165 sourceToInfoReg.put(sourceId, regInfo);
169 public synchronized @NonNull ModuleInfoSnapshot takeSnapshot() {
170 final var effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow();
171 final var local = currentSnapshot;
172 if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) {
176 return updateSnapshot(effectiveModel);
180 private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) {
181 // Alright, now let's find out which sources got captured
182 final var sources = new HashSet<SourceIdentifier>();
183 for (var entry : effectiveModel.getModuleStatements().entrySet()) {
184 final var revision = entry.getKey().getRevision().orElse(null);
185 final var module = entry.getValue();
187 sources.add(new SourceIdentifier(module.argument(), revision));
188 module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class)
189 .map(submodule -> new SourceIdentifier(submodule.argument(), revision))
190 .forEach(sources::add);
193 final var moduleInfos = new HashMap<SourceIdentifier, YangModuleInfo>();
194 final var classLoaders = new HashMap<String, ClassLoader>();
195 for (var source : sources) {
196 final var regs = sourceToInfoReg.get(source);
197 checkState(!regs.isEmpty(), "No registration for %s", source);
199 final var reg = regs.get(0);
200 final var info = reg.info;
201 moduleInfos.put(source, info);
202 final var infoClass = info.getClass();
203 classLoaders.put(Naming.getModelRootPackageName(infoClass.getPackage().getName()),
204 infoClass.getClassLoader());
207 final var next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
208 currentSnapshot = next;
212 private synchronized void unregister(final List<RegisteredModuleInfo> regInfos) {
213 for (var regInfo : regInfos) {
214 if (!regInfo.decRef()) {
215 LOG.debug("Registration {} has references, not removing it", regInfo);
219 final var sourceId = sourceIdentifierFrom(regInfo.info);
220 if (!sourceToInfoReg.remove(sourceId, regInfo)) {
221 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
228 static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
229 return YangTextSchemaSource.delegateForCharSource(sourceIdentifierFrom(moduleInfo),
230 moduleInfo.getYangTextCharSource());
233 private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
234 final YangModuleInfo moduleInfo) {
235 return YangTextSchemaSource.delegateForCharSource(identifier, moduleInfo.getYangTextCharSource());
238 private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
239 final var name = moduleInfo.getName();
240 return new SourceIdentifier(name.getLocalName(), name.getRevision().map(Revision::toString).orElse(null));
243 private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
244 // Flatten the modules being registered, with the triggering module being first...
245 final var requiredInfos = new LinkedHashSet<YangModuleInfo>();
246 flatDependencies(requiredInfos, moduleInfo);
248 // ... now reverse the order in an effort to register dependencies first (triggering module last)
249 return ImmutableList.copyOf(requiredInfos).reverse();
252 static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
253 if (set.add(moduleInfo)) {
254 for (var dep : moduleInfo.getImportedModules()) {
255 flatDependencies(set, dep);