Bump yangtools to 5.0.0-SNAPSHOT
[mdsal.git] / binding / mdsal-binding-runtime-spi / src / main / java / org / opendaylight / binding / runtime / spi / ModuleInfoBackedContext.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.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.base.MoreObjects.ToStringHelper;
16 import com.google.common.cache.CacheBuilder;
17 import com.google.common.cache.CacheLoader;
18 import com.google.common.cache.LoadingCache;
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableList.Builder;
21 import com.google.common.collect.ImmutableSet;
22 import com.google.common.collect.ListMultimap;
23 import com.google.common.collect.MultimapBuilder;
24 import com.google.common.util.concurrent.ListenableFuture;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.Set;
32 import org.checkerframework.checker.lock.qual.GuardedBy;
33 import org.checkerframework.checker.lock.qual.Holding;
34 import org.eclipse.jdt.annotation.NonNull;
35 import org.opendaylight.binding.runtime.api.BindingRuntimeContext;
36 import org.opendaylight.binding.runtime.api.BindingRuntimeGenerator;
37 import org.opendaylight.binding.runtime.api.ClassLoadingStrategy;
38 import org.opendaylight.binding.runtime.api.DefaultBindingRuntimeContext;
39 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
40 import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
41 import org.opendaylight.yangtools.concepts.ObjectRegistration;
42 import org.opendaylight.yangtools.util.ClassLoaderUtils;
43 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
44 import org.opendaylight.yangtools.yang.common.QName;
45 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
46 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
47 import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory;
48 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
49 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
50 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
51 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
52 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
53 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
54 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
55 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaSourceRegistration;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 @Beta
60 public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
61         implements ModuleInfoRegistry, EffectiveModelContextProvider, SchemaSourceProvider<YangTextSchemaSource> {
62     private static final class WithFallback extends ModuleInfoBackedContext {
63         private final @NonNull ClassLoadingStrategy fallback;
64
65         WithFallback(final YangTextSchemaContextResolver resolver, final ClassLoadingStrategy fallback) {
66             super(resolver);
67             this.fallback = requireNonNull(fallback);
68         }
69
70         @Override
71         Class<?> loadUnknownClass(final String fullyQualifiedName) throws ClassNotFoundException {
72             // We have not found a matching registration, consult the backing strategy
73             final Class<?> cls = fallback.loadClass(fullyQualifiedName);
74             registerImplicitModuleInfo(BindingRuntimeHelpers.extractYangModuleInfo(cls));
75             return cls;
76         }
77     }
78
79     private abstract static class AbstractRegisteredModuleInfo {
80         final YangTextSchemaSourceRegistration reg;
81         final YangModuleInfo info;
82         final ClassLoader loader;
83
84         AbstractRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
85             final ClassLoader loader) {
86             this.info = requireNonNull(info);
87             this.reg = requireNonNull(reg);
88             this.loader = requireNonNull(loader);
89         }
90
91         @Override
92         public final String toString() {
93             return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
94         }
95
96         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
97             return helper.add("info", info).add("registration", reg).add("classLoader", loader);
98         }
99     }
100
101     private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
102         private int refcount = 1;
103
104         ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
105                 final ClassLoader loader) {
106             super(info, reg, loader);
107         }
108
109         void incRef() {
110             ++refcount;
111         }
112
113         boolean decRef() {
114             return --refcount == 0;
115         }
116
117         @Override
118         ToStringHelper addToStringAttributes(final ToStringHelper helper) {
119             return super.addToStringAttributes(helper).add("refCount", refcount);
120         }
121     }
122
123     private static final class ImplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
124         ImplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
125                 final ClassLoader loader) {
126             super(info, reg, loader);
127         }
128     }
129
130     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBackedContext.class);
131
132     private static final LoadingCache<ClassLoadingStrategy,
133         LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext>> CONTEXT_CACHES = CacheBuilder.newBuilder()
134             .weakKeys().build(new CacheLoader<ClassLoadingStrategy,
135                 LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext>>() {
136                     @Override
137                     public LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext> load(
138                             final ClassLoadingStrategy strategy) {
139                         return CacheBuilder.newBuilder().weakValues().build(
140                             new CacheLoader<Set<YangModuleInfo>, ModuleInfoBackedContext>() {
141                                 @Override
142                                 public ModuleInfoBackedContext load(final Set<YangModuleInfo> key) {
143                                     final ModuleInfoBackedContext context = ModuleInfoBackedContext.create(strategy);
144                                     context.addModuleInfos(key);
145                                     return context;
146                                 }
147                             });
148                     }
149             });
150
151     private final YangTextSchemaContextResolver ctxResolver;
152
153     @GuardedBy("this")
154     private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
155             MultimapBuilder.hashKeys().arrayListValues().build();
156     @GuardedBy("this")
157     private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
158             MultimapBuilder.hashKeys().arrayListValues().build();
159
160     ModuleInfoBackedContext(final YangTextSchemaContextResolver resolver) {
161         this.ctxResolver = requireNonNull(resolver);
162     }
163
164     @Beta
165     public static ModuleInfoBackedContext cacheContext(final ClassLoadingStrategy loadingStrategy,
166             final ImmutableSet<YangModuleInfo> infos) {
167         return CONTEXT_CACHES.getUnchecked(loadingStrategy).getUnchecked(infos);
168     }
169
170     public static ModuleInfoBackedContext create() {
171         return create("unnamed");
172     }
173
174     public static ModuleInfoBackedContext create(final String id) {
175         return new ModuleInfoBackedContext(YangTextSchemaContextResolver.create(id));
176     }
177
178     public static ModuleInfoBackedContext create(final ClassLoadingStrategy loadingStrategy) {
179         return create("unnamed", loadingStrategy);
180     }
181
182     public static ModuleInfoBackedContext create(final String id, final ClassLoadingStrategy loadingStrategy) {
183         return new WithFallback(YangTextSchemaContextResolver.create(id), loadingStrategy);
184     }
185
186     public static ModuleInfoBackedContext create(final String id, final YangParserFactory factory) {
187         return new ModuleInfoBackedContext(YangTextSchemaContextResolver.create(id, factory));
188     }
189
190     public static ModuleInfoBackedContext create(final String id, final YangParserFactory factory,
191             final ClassLoadingStrategy loadingStrategy) {
192         return new WithFallback(YangTextSchemaContextResolver.create(id, factory), loadingStrategy);
193     }
194
195     @Override
196     public final EffectiveModelContext getEffectiveModelContext() {
197         final Optional<? extends EffectiveModelContext> contextOptional = tryToCreateModelContext();
198         checkState(contextOptional.isPresent(), "Unable to recreate SchemaContext, error while parsing");
199         return contextOptional.get();
200     }
201
202     @Override
203     @SuppressWarnings("checkstyle:illegalCatch")
204     public final Class<?> loadClass(final String fullyQualifiedName) throws ClassNotFoundException {
205         // This performs an explicit check for binding classes
206         final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName);
207
208         synchronized (this) {
209             // Try to find a loaded class loader
210             // FIXME: two-step process, try explicit registrations first
211             for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) {
212                 return ClassLoaderUtils.loadClass(reg.loader, fullyQualifiedName);
213             }
214
215             return loadUnknownClass(fullyQualifiedName);
216         }
217     }
218
219     @Holding("this")
220     Class<?> loadUnknownClass(final String fullyQualifiedName) throws ClassNotFoundException {
221         throw new ClassNotFoundException(fullyQualifiedName);
222     }
223
224     @Override
225     public final synchronized ObjectRegistration<YangModuleInfo> registerModuleInfo(
226             final YangModuleInfo yangModuleInfo) {
227         return register(requireNonNull(yangModuleInfo));
228     }
229
230     @Override
231     public final ListenableFuture<? extends YangTextSchemaSource> getSource(final SourceIdentifier sourceIdentifier) {
232         return ctxResolver.getSource(sourceIdentifier);
233     }
234
235     final synchronized void addModuleInfos(final Iterable<? extends YangModuleInfo> moduleInfos) {
236         for (YangModuleInfo yangModuleInfo : moduleInfos) {
237             register(requireNonNull(yangModuleInfo));
238         }
239     }
240
241     @Beta
242     public final @NonNull BindingRuntimeContext createRuntimeContext(final BindingRuntimeGenerator generator) {
243         return DefaultBindingRuntimeContext.create(
244             generator.generateTypeMapping(tryToCreateModelContext().orElseThrow()), this);
245     }
246
247     // TODO finish schema parsing and expose as SchemaService
248     // Unite with current SchemaService
249
250     public final Optional<? extends EffectiveModelContext> tryToCreateModelContext() {
251         return ctxResolver.getEffectiveModelContext();
252     }
253
254     @Holding("this")
255     private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
256         final Builder<ExplicitRegisteredModuleInfo> regBuilder = ImmutableList.builder();
257         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
258             regBuilder.add(registerExplicitModuleInfo(info));
259         }
260         final ImmutableList<ExplicitRegisteredModuleInfo> regInfos = regBuilder.build();
261
262         return new AbstractObjectRegistration<>(moduleInfo) {
263             @Override
264             protected void removeRegistration() {
265                 unregister(regInfos);
266             }
267         };
268     }
269
270     /*
271      * Perform implicit registration of a YangModuleInfo and any of its dependencies. If there is a registration for
272      * a particular source, we do not create a duplicate registration.
273      */
274     @Holding("this")
275     final void registerImplicitModuleInfo(final @NonNull YangModuleInfo moduleInfo) {
276         for (YangModuleInfo info : flatDependencies(moduleInfo)) {
277             final Class<?> infoClass = info.getClass();
278             final SourceIdentifier sourceId = sourceIdentifierFrom(info);
279             if (sourceToInfoReg.containsKey(sourceId)) {
280                 LOG.debug("Skipping implicit registration of {} as source {} is already registered", info, sourceId);
281                 continue;
282             }
283
284             final YangTextSchemaSourceRegistration reg;
285             try {
286                 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
287             } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
288                 LOG.warn("Failed to register info {} source {}, ignoring it", info, sourceId, e);
289                 continue;
290             }
291
292             final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg,
293                 infoClass.getClassLoader());
294             sourceToInfoReg.put(sourceId, regInfo);
295             packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo);
296         }
297     }
298
299     /*
300      * Perform explicit registration of a YangModuleInfo. This always results in a new explicit registration. In case
301      * there is a pre-existing implicit registration, it is removed just after the explicit registration is made.
302      */
303     @Holding("this")
304     private ExplicitRegisteredModuleInfo registerExplicitModuleInfo(final @NonNull YangModuleInfo info) {
305         // First search for an existing explicit registration
306         final SourceIdentifier sourceId = sourceIdentifierFrom(info);
307         for (AbstractRegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
308             if (reg instanceof ExplicitRegisteredModuleInfo && info.equals(reg.info)) {
309                 final ExplicitRegisteredModuleInfo explicit = (ExplicitRegisteredModuleInfo) reg;
310                 explicit.incRef();
311                 LOG.debug("Reusing explicit registration {}", explicit);
312                 return explicit;
313             }
314         }
315
316         // Create an explicit registration
317         final YangTextSchemaSourceRegistration reg;
318         try {
319             reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
320         } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
321             throw new IllegalStateException("Failed to register info " + info, e);
322         }
323
324         final Class<?> infoClass = info.getClass();
325         final String packageName = BindingReflections.getModelRootPackageName(infoClass.getPackage());
326         final ExplicitRegisteredModuleInfo regInfo = new ExplicitRegisteredModuleInfo(info, reg,
327             infoClass.getClassLoader());
328         LOG.debug("Created new explicit registration {}", regInfo);
329
330         sourceToInfoReg.put(sourceId, regInfo);
331         removeImplicit(sourceToInfoReg.get(sourceId));
332         packageToInfoReg.put(packageName, regInfo);
333         removeImplicit(packageToInfoReg.get(packageName));
334
335         return regInfo;
336     }
337
338     final synchronized void unregister(final ImmutableList<ExplicitRegisteredModuleInfo> regInfos) {
339         for (ExplicitRegisteredModuleInfo regInfo : regInfos) {
340             if (!regInfo.decRef()) {
341                 LOG.debug("Registration {} has references, not removing it", regInfo);
342                 continue;
343             }
344
345             final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
346             if (!sourceToInfoReg.remove(sourceId, regInfo)) {
347                 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
348             }
349
350             final String packageName = BindingReflections.getModelRootPackageName(regInfo.info.getClass().getPackage());
351             if (!packageToInfoReg.remove(packageName, regInfo)) {
352                 LOG.warn("Failed to find {} registered under {}", regInfo, packageName);
353             }
354
355             regInfo.reg.close();
356         }
357     }
358
359     @Holding("this")
360     private static void removeImplicit(final List<AbstractRegisteredModuleInfo> regs) {
361         /*
362          * Search for implicit registration for a sourceId/packageName.
363          *
364          * Since we are called while an explicit registration is being created (and has already been inserted, we know
365          * there is at least one entry in the maps. We also know registrations retain the order in which they were
366          * created and that implicit registrations are not created if there already is a registration.
367          *
368          * This means that if an implicit registration exists, it will be the first entry in the list.
369          */
370         final AbstractRegisteredModuleInfo reg = regs.get(0);
371         if (reg instanceof ImplicitRegisteredModuleInfo) {
372             LOG.debug("Removing implicit registration {}", reg);
373             regs.remove(0);
374             reg.reg.close();
375         }
376     }
377
378     private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
379             final YangModuleInfo moduleInfo) {
380         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
381     }
382
383     private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
384         final QName name = moduleInfo.getName();
385         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
386     }
387
388     private static List<YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
389         // Flatten the modules being registered, with the triggering module being first...
390         final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
391         flatDependencies(requiredInfos, moduleInfo);
392
393         // ... now reverse the order in an effort to register dependencies first (triggering module last)
394         final List<YangModuleInfo> intendedOrder = new ArrayList<>(requiredInfos);
395         Collections.reverse(intendedOrder);
396
397         return intendedOrder;
398     }
399
400     private static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
401         if (set.add(moduleInfo)) {
402             for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
403                 flatDependencies(set, dep);
404             }
405         }
406     }
407 }