afecd8caec637182d97538d76a57c4dd4cf9f628
[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.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;
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 YangTextSchemaSourceRegistration reg;
66         final YangModuleInfo info;
67         final ClassLoader loader;
68
69         private int refcount = 1;
70
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);
76         }
77
78         void incRef() {
79             ++refcount;
80         }
81
82         boolean decRef() {
83             return --refcount == 0;
84         }
85
86         @Override
87         public String toString() {
88             return MoreObjects.toStringHelper(this).add("info", info).add("registration", reg)
89                     .add("classLoader", loader).add("refCount", refcount).toString();
90         }
91     }
92
93     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoSnapshotResolver.class);
94
95     private final YangTextSchemaContextResolver ctxResolver;
96
97     @GuardedBy("this")
98     private final ListMultimap<SourceIdentifier, RegisteredModuleInfo> sourceToInfoReg =
99             MultimapBuilder.hashKeys().arrayListValues().build();
100     @GuardedBy("this")
101     private @Nullable ModuleInfoSnapshot currentSnapshot;
102
103     public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
104         ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
105     }
106
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)));
112         }
113         return ret;
114     }
115
116     @Holding("this")
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));
121         }
122         final ImmutableList<RegisteredModuleInfo> regInfos = regBuilder.build();
123
124         return new AbstractObjectRegistration<>(moduleInfo) {
125             @Override
126             protected void removeRegistration() {
127                 unregister(regInfos);
128             }
129         };
130     }
131
132     /*
133      * Perform registration of a YangModuleInfo.
134      */
135     @Holding("this")
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)) {
141                 reg.incRef();
142                 LOG.debug("Reusing registration {}", reg);
143                 return reg;
144             }
145         }
146
147         // Create an explicit registration
148         final YangTextSchemaSourceRegistration reg;
149         try {
150             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
151         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
152             throw new IllegalStateException("Failed to register info " + info, e);
153         }
154
155         final RegisteredModuleInfo regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
156         LOG.debug("Created new registration {}", regInfo);
157
158         sourceToInfoReg.put(sourceId, regInfo);
159         return regInfo;
160     }
161
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)) {
166             return local;
167         }
168
169         return updateSnapshot(effectiveModel);
170     }
171
172     @Holding("this")
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();
179
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);
184         }
185
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);
191
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());
198         }
199
200         final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
201         currentSnapshot = next;
202         return next;
203     }
204
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);
211                 continue;
212             }
213
214             final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
215             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
216                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
217             }
218
219             regInfo.reg.close();
220         }
221     }
222
223     static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
224         return YangTextSchemaSource.delegateForByteSource(sourceIdentifierFrom(moduleInfo),
225             moduleInfo.getYangTextByteSource());
226     }
227
228     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
229             final YangModuleInfo moduleInfo) {
230         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
231     }
232
233     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
234         final QName name = moduleInfo.getName();
235         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
236     }
237
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);
242
243         // ... now reverse the order in an effort to register dependencies first (triggering module last)
244         return ImmutableList.copyOf(requiredInfos).reverse();
245     }
246
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);
251             }
252         }
253     }
254 }