458a7bc141e70afa095acfb118180c701627267f
[mdsal.git] / binding / mdsal-binding-runtime-spi / src / main / java / org / opendaylight / mdsal / binding / runtime / spi / ModuleInfoSnapshotResolver.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.mdsal.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.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;
25 import java.util.Set;
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;
52
53 /**
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.
57  */
58 @Beta
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;
64
65         private int refcount = 1;
66
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);
71         }
72
73         void incRef() {
74             ++refcount;
75         }
76
77         boolean decRef() {
78             return --refcount == 0;
79         }
80
81         @Override
82         public String toString() {
83             return MoreObjects.toStringHelper(this)
84                 .add("info", info)
85                 .add("registration", reg)
86                 .add("classLoader", loader)
87                 .add("refCount", refcount)
88                 .toString();
89         }
90     }
91
92     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoSnapshotResolver.class);
93
94     private final YangTextSchemaContextResolver ctxResolver;
95
96     @GuardedBy("this")
97     private final ListMultimap<SourceIdentifier, RegisteredModuleInfo> sourceToInfoReg =
98             MultimapBuilder.hashKeys().arrayListValues().build();
99     @GuardedBy("this")
100     private final ListMultimap<Class<? extends DataRoot>, ImmutableSet<YangFeature<?, ?>>> moduleToFeatures =
101             MultimapBuilder.hashKeys().arrayListValues().build();
102     @GuardedBy("this")
103     private @Nullable ModuleInfoSnapshot currentSnapshot;
104
105     public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
106         ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
107     }
108
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);
114     }
115
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)));
121         }
122         return ret;
123     }
124
125     @Holding("this")
126     private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
127         final var regInfos = flatDependencies(moduleInfo).stream()
128             .map(this::registerModuleInfo)
129             .collect(ImmutableList.toImmutableList());
130
131         return new AbstractObjectRegistration<>(moduleInfo) {
132             @Override
133             protected void removeRegistration() {
134                 unregister(regInfos);
135             }
136         };
137     }
138
139     /*
140      * Perform registration of a YangModuleInfo.
141      */
142     @Holding("this")
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)) {
148                 reg.incRef();
149                 LOG.debug("Reusing registration {}", reg);
150                 return reg;
151             }
152         }
153
154         // Create an explicit registration
155         final Registration reg;
156         try {
157             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
158         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
159             throw new IllegalStateException("Failed to register info " + info, e);
160         }
161
162         final var regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
163         LOG.debug("Created new registration {}", regInfo);
164
165         sourceToInfoReg.put(sourceId, regInfo);
166         return regInfo;
167     }
168
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)) {
173             return local;
174         }
175
176         return updateSnapshot(effectiveModel);
177     }
178
179     @Holding("this")
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();
186
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);
191         }
192
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);
198
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());
205         }
206
207         final var next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
208         currentSnapshot = next;
209         return next;
210     }
211
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);
216                 continue;
217             }
218
219             final var sourceId = sourceIdentifierFrom(regInfo.info);
220             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
221                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
222             }
223
224             regInfo.reg.close();
225         }
226     }
227
228     static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
229         return YangTextSchemaSource.delegateForCharSource(sourceIdentifierFrom(moduleInfo),
230             moduleInfo.getYangTextCharSource());
231     }
232
233     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
234             final YangModuleInfo moduleInfo) {
235         return YangTextSchemaSource.delegateForCharSource(identifier, moduleInfo.getYangTextCharSource());
236     }
237
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));
241     }
242
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);
247
248         // ... now reverse the order in an effort to register dependencies first (triggering module last)
249         return ImmutableList.copyOf(requiredInfos).reverse();
250     }
251
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);
256             }
257         }
258     }
259 }