1ab8e07a87fc5228695d762da96fdab0d534d81f
[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.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;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Optional;
29 import java.util.Set;
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;
56
57 /**
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.
61  */
62 @Beta
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;
68
69         private int refcount = 1;
70
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);
75         }
76
77         void incRef() {
78             ++refcount;
79         }
80
81         boolean decRef() {
82             return --refcount == 0;
83         }
84
85         @Override
86         public String toString() {
87             return MoreObjects.toStringHelper(this).add("info", info).add("registration", reg)
88                     .add("classLoader", loader).add("refCount", refcount).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 @Nullable ModuleInfoSnapshot currentSnapshot;
101
102     public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
103         ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
104     }
105
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)));
111         }
112         return ret;
113     }
114
115     @Holding("this")
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));
120         }
121         final ImmutableList<RegisteredModuleInfo> regInfos = regBuilder.build();
122
123         return new AbstractObjectRegistration<>(moduleInfo) {
124             @Override
125             protected void removeRegistration() {
126                 unregister(regInfos);
127             }
128         };
129     }
130
131     /*
132      * Perform registration of a YangModuleInfo.
133      */
134     @Holding("this")
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)) {
140                 reg.incRef();
141                 LOG.debug("Reusing registration {}", reg);
142                 return reg;
143             }
144         }
145
146         // Create an explicit registration
147         final Registration reg;
148         try {
149             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
150         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
151             throw new IllegalStateException("Failed to register info " + info, e);
152         }
153
154         final RegisteredModuleInfo regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
155         LOG.debug("Created new registration {}", regInfo);
156
157         sourceToInfoReg.put(sourceId, regInfo);
158         return regInfo;
159     }
160
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)) {
165             return local;
166         }
167
168         return updateSnapshot(effectiveModel);
169     }
170
171     @Holding("this")
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();
178
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);
183         }
184
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);
190
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());
197         }
198
199         final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
200         currentSnapshot = next;
201         return next;
202     }
203
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);
210                 continue;
211             }
212
213             final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
214             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
215                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
216             }
217
218             regInfo.reg.close();
219         }
220     }
221
222     static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
223         return YangTextSchemaSource.delegateForByteSource(sourceIdentifierFrom(moduleInfo),
224             moduleInfo.getYangTextByteSource());
225     }
226
227     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
228             final YangModuleInfo moduleInfo) {
229         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
230     }
231
232     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
233         final QName name = moduleInfo.getName();
234         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
235     }
236
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);
241
242         // ... now reverse the order in an effort to register dependencies first (triggering module last)
243         return ImmutableList.copyOf(requiredInfos).reverse();
244     }
245
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);
250             }
251         }
252     }
253 }