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