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