2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.binding.runtime.spi;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
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;
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;
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;
65 WithFallback(final YangTextSchemaContextResolver resolver, final ClassLoadingStrategy fallback) {
67 this.fallback = requireNonNull(fallback);
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));
79 private abstract static class AbstractRegisteredModuleInfo {
80 final YangTextSchemaSourceRegistration reg;
81 final YangModuleInfo info;
82 final ClassLoader loader;
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);
92 public final String toString() {
93 return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
96 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
97 return helper.add("info", info).add("registration", reg).add("classLoader", loader);
101 private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
102 private int refcount = 1;
104 ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
105 final ClassLoader loader) {
106 super(info, reg, loader);
114 return --refcount == 0;
118 ToStringHelper addToStringAttributes(final ToStringHelper helper) {
119 return super.addToStringAttributes(helper).add("refCount", refcount);
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);
130 private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBackedContext.class);
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>>() {
137 public LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext> load(
138 final ClassLoadingStrategy strategy) {
139 return CacheBuilder.newBuilder().weakValues().build(
140 new CacheLoader<Set<YangModuleInfo>, ModuleInfoBackedContext>() {
142 public ModuleInfoBackedContext load(final Set<YangModuleInfo> key) {
143 final ModuleInfoBackedContext context = ModuleInfoBackedContext.create(strategy);
144 context.addModuleInfos(key);
151 private final YangTextSchemaContextResolver ctxResolver;
154 private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
155 MultimapBuilder.hashKeys().arrayListValues().build();
157 private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
158 MultimapBuilder.hashKeys().arrayListValues().build();
160 ModuleInfoBackedContext(final YangTextSchemaContextResolver resolver) {
161 this.ctxResolver = requireNonNull(resolver);
165 public static ModuleInfoBackedContext cacheContext(final ClassLoadingStrategy loadingStrategy,
166 final ImmutableSet<YangModuleInfo> infos) {
167 return CONTEXT_CACHES.getUnchecked(loadingStrategy).getUnchecked(infos);
170 public static ModuleInfoBackedContext create() {
171 return create("unnamed");
174 public static ModuleInfoBackedContext create(final String id) {
175 return new ModuleInfoBackedContext(YangTextSchemaContextResolver.create(id));
178 public static ModuleInfoBackedContext create(final ClassLoadingStrategy loadingStrategy) {
179 return create("unnamed", loadingStrategy);
182 public static ModuleInfoBackedContext create(final String id, final ClassLoadingStrategy loadingStrategy) {
183 return new WithFallback(YangTextSchemaContextResolver.create(id), loadingStrategy);
186 public static ModuleInfoBackedContext create(final String id, final YangParserFactory factory) {
187 return new ModuleInfoBackedContext(YangTextSchemaContextResolver.create(id, factory));
190 public static ModuleInfoBackedContext create(final String id, final YangParserFactory factory,
191 final ClassLoadingStrategy loadingStrategy) {
192 return new WithFallback(YangTextSchemaContextResolver.create(id, factory), loadingStrategy);
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();
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);
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);
215 return loadUnknownClass(fullyQualifiedName);
220 Class<?> loadUnknownClass(final String fullyQualifiedName) throws ClassNotFoundException {
221 throw new ClassNotFoundException(fullyQualifiedName);
225 public final synchronized ObjectRegistration<YangModuleInfo> registerModuleInfo(
226 final YangModuleInfo yangModuleInfo) {
227 return register(requireNonNull(yangModuleInfo));
231 public final ListenableFuture<? extends YangTextSchemaSource> getSource(final SourceIdentifier sourceIdentifier) {
232 return ctxResolver.getSource(sourceIdentifier);
235 final synchronized void addModuleInfos(final Iterable<? extends YangModuleInfo> moduleInfos) {
236 for (YangModuleInfo yangModuleInfo : moduleInfos) {
237 register(requireNonNull(yangModuleInfo));
242 public final @NonNull BindingRuntimeContext createRuntimeContext(final BindingRuntimeGenerator generator) {
243 return DefaultBindingRuntimeContext.create(
244 generator.generateTypeMapping(tryToCreateModelContext().orElseThrow()), this);
247 // TODO finish schema parsing and expose as SchemaService
248 // Unite with current SchemaService
250 public final Optional<? extends EffectiveModelContext> tryToCreateModelContext() {
251 return ctxResolver.getEffectiveModelContext();
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));
260 final ImmutableList<ExplicitRegisteredModuleInfo> regInfos = regBuilder.build();
262 return new AbstractObjectRegistration<>(moduleInfo) {
264 protected void removeRegistration() {
265 unregister(regInfos);
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.
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);
284 final YangTextSchemaSourceRegistration reg;
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);
292 final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg,
293 infoClass.getClassLoader());
294 sourceToInfoReg.put(sourceId, regInfo);
295 packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo);
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.
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;
311 LOG.debug("Reusing explicit registration {}", explicit);
316 // Create an explicit registration
317 final YangTextSchemaSourceRegistration reg;
319 reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
320 } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
321 throw new IllegalStateException("Failed to register info " + info, e);
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);
330 sourceToInfoReg.put(sourceId, regInfo);
331 removeImplicit(sourceToInfoReg.get(sourceId));
332 packageToInfoReg.put(packageName, regInfo);
333 removeImplicit(packageToInfoReg.get(packageName));
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);
345 final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
346 if (!sourceToInfoReg.remove(sourceId, regInfo)) {
347 LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
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);
360 private static void removeImplicit(final List<AbstractRegisteredModuleInfo> regs) {
362 * Search for implicit registration for a sourceId/packageName.
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.
368 * This means that if an implicit registration exists, it will be the first entry in the list.
370 final AbstractRegisteredModuleInfo reg = regs.get(0);
371 if (reg instanceof ImplicitRegisteredModuleInfo) {
372 LOG.debug("Removing implicit registration {}", reg);
378 private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
379 final YangModuleInfo moduleInfo) {
380 return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
383 private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) {
384 final QName name = moduleInfo.getName();
385 return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
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);
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);
397 return intendedOrder;
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);