Adopt odlparent-10.0.0/yangtools-8.0.0-SNAPSHOT
[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 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.Map;
26 import java.util.Map.Entry;
27 import java.util.Optional;
28 import java.util.Set;
29 import org.checkerframework.checker.lock.qual.GuardedBy;
30 import org.checkerframework.checker.lock.qual.Holding;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.opendaylight.mdsal.binding.runtime.api.ModuleInfoSnapshot;
34 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
35 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
36 import org.opendaylight.yangtools.concepts.Mutable;
37 import org.opendaylight.yangtools.concepts.ObjectRegistration;
38 import org.opendaylight.yangtools.concepts.Registration;
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.repo.api.RevisionSourceIdentifier;
47 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
48 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
49 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
50 import org.opendaylight.yangtools.yang.parser.api.YangParserFactory;
51 import org.opendaylight.yangtools.yang.parser.api.YangSyntaxErrorException;
52 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * A dynamic registry of {@link YangModuleInfo} objects, capable of combining them into an
58  * {@link ModuleInfoSnapshot}. If you need a one-shot way of creating an ModuleInfoSnapshot, prefer
59  * {@link ModuleInfoSnapshotBuilder} instead.
60  */
61 @Beta
62 public final class ModuleInfoSnapshotResolver implements Mutable {
63     private static final class RegisteredModuleInfo {
64         final Registration reg;
65         final YangModuleInfo info;
66         final ClassLoader loader;
67
68         private int refcount = 1;
69
70         RegisteredModuleInfo(final YangModuleInfo info, final Registration reg, final ClassLoader loader) {
71             this.info = requireNonNull(info);
72             this.reg = requireNonNull(reg);
73             this.loader = requireNonNull(loader);
74         }
75
76         void incRef() {
77             ++refcount;
78         }
79
80         boolean decRef() {
81             return --refcount == 0;
82         }
83
84         @Override
85         public String toString() {
86             return MoreObjects.toStringHelper(this).add("info", info).add("registration", reg)
87                     .add("classLoader", loader).add("refCount", refcount).toString();
88         }
89     }
90
91     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoSnapshotResolver.class);
92
93     private final YangTextSchemaContextResolver ctxResolver;
94
95     @GuardedBy("this")
96     private final ListMultimap<SourceIdentifier, RegisteredModuleInfo> sourceToInfoReg =
97             MultimapBuilder.hashKeys().arrayListValues().build();
98     @GuardedBy("this")
99     private @Nullable ModuleInfoSnapshot currentSnapshot;
100
101     public ModuleInfoSnapshotResolver(final String name, final YangParserFactory parserFactory) {
102         ctxResolver = YangTextSchemaContextResolver.create(name, parserFactory);
103     }
104
105     public synchronized List<ObjectRegistration<YangModuleInfo>> registerModuleInfos(
106             final Iterable<? extends YangModuleInfo> moduleInfos) {
107         final List<ObjectRegistration<YangModuleInfo>> ret = new ArrayList<>();
108         for (YangModuleInfo yangModuleInfo : moduleInfos) {
109             ret.add(register(requireNonNull(yangModuleInfo)));
110         }
111         return ret;
112     }
113
114     @Holding("this")
115     private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
116         final Builder<RegisteredModuleInfo> regBuilder = ImmutableList.builder();
117         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
118             regBuilder.add(registerModuleInfo(info));
119         }
120         final ImmutableList<RegisteredModuleInfo> regInfos = regBuilder.build();
121
122         return new AbstractObjectRegistration<>(moduleInfo) {
123             @Override
124             protected void removeRegistration() {
125                 unregister(regInfos);
126             }
127         };
128     }
129
130     /*
131      * Perform registration of a YangModuleInfo.
132      */
133     @Holding("this")
134     private RegisteredModuleInfo registerModuleInfo(final @NonNull YangModuleInfo info) {
135         // First search for an existing explicit registration
136         final SourceIdentifier sourceId = sourceIdentifierFrom(info);
137         for (RegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
138             if (info.equals(reg.info)) {
139                 reg.incRef();
140                 LOG.debug("Reusing registration {}", reg);
141                 return reg;
142             }
143         }
144
145         // Create an explicit registration
146         final Registration reg;
147         try {
148             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
149         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
150             throw new IllegalStateException("Failed to register info " + info, e);
151         }
152
153         final RegisteredModuleInfo regInfo = new RegisteredModuleInfo(info, reg, info.getClass().getClassLoader());
154         LOG.debug("Created new registration {}", regInfo);
155
156         sourceToInfoReg.put(sourceId, regInfo);
157         return regInfo;
158     }
159
160     public synchronized @NonNull ModuleInfoSnapshot takeSnapshot() {
161         final EffectiveModelContext effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow();
162         final ModuleInfoSnapshot local = currentSnapshot;
163         if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) {
164             return local;
165         }
166
167         return updateSnapshot(effectiveModel);
168     }
169
170     @Holding("this")
171     private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) {
172         // Alright, now let's find out which sources got captured
173         final Set<SourceIdentifier> sources = new HashSet<>();
174         for (Entry<QNameModule, ModuleEffectiveStatement> entry : effectiveModel.getModuleStatements().entrySet()) {
175             final Optional<Revision> revision = entry.getKey().getRevision();
176             final ModuleEffectiveStatement module = entry.getValue();
177
178             sources.add(RevisionSourceIdentifier.create(module.argument().getLocalName(), revision));
179             module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class)
180                 .map(submodule -> RevisionSourceIdentifier.create(submodule.argument().getLocalName(), revision))
181                 .forEach(sources::add);
182         }
183
184         final Map<SourceIdentifier, YangModuleInfo> moduleInfos = new HashMap<>();
185         final Map<String, ClassLoader> classLoaders = new HashMap<>();
186         for (SourceIdentifier source : sources) {
187             final List<RegisteredModuleInfo> regs = sourceToInfoReg.get(source);
188             checkState(!regs.isEmpty(), "No registration for %s", source);
189
190             final RegisteredModuleInfo reg = regs.get(0);
191             final YangModuleInfo info = reg.info;
192             moduleInfos.put(source, info);
193             final Class<?> infoClass = info.getClass();
194             classLoaders.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()),
195                 infoClass.getClassLoader());
196         }
197
198         final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders);
199         currentSnapshot = next;
200         return next;
201     }
202
203     private synchronized void unregister(final ImmutableList<RegisteredModuleInfo> regInfos) {
204         for (RegisteredModuleInfo regInfo : regInfos) {
205             if (!regInfo.decRef()) {
206                 LOG.debug("Registration {} has references, not removing it", regInfo);
207                 continue;
208             }
209
210             final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
211             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
212                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
213             }
214
215             regInfo.reg.close();
216         }
217     }
218
219     static @NonNull YangTextSchemaSource toYangTextSource(final YangModuleInfo moduleInfo) {
220         return YangTextSchemaSource.delegateForByteSource(sourceIdentifierFrom(moduleInfo),
221             moduleInfo.getYangTextByteSource());
222     }
223
224     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
225             final YangModuleInfo moduleInfo) {
226         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
227     }
228
229     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
230         final QName name = moduleInfo.getName();
231         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
232     }
233
234     private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
235         // Flatten the modules being registered, with the triggering module being first...
236         final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
237         flatDependencies(requiredInfos, moduleInfo);
238
239         // ... now reverse the order in an effort to register dependencies first (triggering module last)
240         return ImmutableList.copyOf(requiredInfos).reverse();
241     }
242
243     static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
244         if (set.add(moduleInfo)) {
245             for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
246                 flatDependencies(set, dep);
247             }
248         }
249     }
250 }