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