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