From: Robert Varga Date: Mon, 24 Feb 2020 11:40:56 +0000 (+0100) Subject: Rework binding component instantiation X-Git-Tag: v6.0.0~122 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=aa5816d267a0ee82fef7e8d9bcdf467fcacd10c3;p=mdsal.git Rework binding component instantiation This reshuffles how DOM and Binding interoperate where schema is concerned. Since Binding requires Classloader information available at scanning time, we refactor mdsal-dom-schema-service-osgi to actually perform Binding-aware scanning, so that this information is available in ModuleInfoSnapshot. These are published as OSGiModuleInfoSnapshot along with generation number for easier reference by a new component, OSGiModelRuntime. DOMSchemaService is realized on top of ServiceRegistry, where it listens for OSGiModelRuntime and for SchemaContextListeners, driving an OSGi whiteboard pattern. This renders mdsal-binding-dom-codec-osgi completely superfluous, as there is not enough magic going on to justify it. mdsal-binding-runtime-osgi is also hooked onto OSGiModuleInfoSnapshot, providing BindingRuntimeContexts as needed and retaining generation inforation. JIRA: MDSAL-392 Change-Id: I083caf3abbc3d7e2212d1df14e45d2745096b5f9 Signed-off-by: Robert Varga --- diff --git a/artifacts/pom.xml b/artifacts/pom.xml index 65f8d03883..699cf8ff7c 100644 --- a/artifacts/pom.xml +++ b/artifacts/pom.xml @@ -68,7 +68,7 @@ org.opendaylight.mdsal - mdsal-dom-schema-service-osgi + mdsal-dom-schema-osgi 6.0.0-SNAPSHOT @@ -154,17 +154,17 @@ org.opendaylight.mdsal - mdsal-binding-dom-codec-osgi + mdsal-binding-runtime-api 6.0.0-SNAPSHOT org.opendaylight.mdsal - mdsal-binding-runtime-api + mdsal-binding-runtime-spi 6.0.0-SNAPSHOT org.opendaylight.mdsal - mdsal-binding-runtime-spi + mdsal-binding-runtime-osgi 6.0.0-SNAPSHOT @@ -268,6 +268,13 @@ features xml + + ${project.groupId} + odl-mdsal-binding-runtime-api + 6.0.0-SNAPSHOT + features + xml + ${project.groupId} odl-mdsal-binding-runtime diff --git a/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/BindingClassLoadingStrategy.java b/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/BindingClassLoadingStrategy.java deleted file mode 100644 index d0ee2fa30f..0000000000 --- a/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/BindingClassLoadingStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.mdsal.binding.dom.codec.osgi.impl; - -import org.opendaylight.binding.runtime.api.ClassLoadingStrategy; -import org.opendaylight.binding.runtime.spi.GeneratedClassLoadingStrategy; -import org.opendaylight.binding.runtime.spi.ModuleInfoBackedContext; -import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; -import org.osgi.framework.BundleContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Component(immediate = true) -public final class BindingClassLoadingStrategy implements ClassLoadingStrategy { - private static final Logger LOG = LoggerFactory.getLogger(BindingClassLoadingStrategy.class); - - @Reference - YangParserFactory factory = null; - - private ModuleInfoBundleTracker bundleTracker = null; - private ModuleInfoBackedContext moduleInfoBackedContext = null; - - @Activate - void activate(final BundleContext ctx) { - LOG.info("Binding-DOM codec starting"); - - moduleInfoBackedContext = ModuleInfoBackedContext.create("binding-dom-codec", factory, - // FIXME: This is the fallback strategy, it should not be needed - GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy()); - - final OsgiModuleInfoRegistry registry = new OsgiModuleInfoRegistry(moduleInfoBackedContext, - moduleInfoBackedContext); - - LOG.debug("Starting Binding-DOM codec bundle tracker"); - bundleTracker = new ModuleInfoBundleTracker(ctx, registry); - bundleTracker.open(); - - LOG.info("Binding-DOM codec started"); - } - - @Deactivate - void deactivate() { - LOG.info("Binding-DOM codec stopping"); - LOG.debug("Stopping Binding-DOM codec bundle tracker"); - bundleTracker.close(); - moduleInfoBackedContext = null; - bundleTracker = null; - LOG.info("Binding-DOM codec stopped"); - } - - @Override - public Class loadClass(final String fullyQualifiedName) throws ClassNotFoundException { - return moduleInfoBackedContext.loadClass(fullyQualifiedName); - } -} diff --git a/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/ModuleInfoBundleTracker.java b/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/ModuleInfoBundleTracker.java deleted file mode 100644 index 4b39f1072b..0000000000 --- a/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/ModuleInfoBundleTracker.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.mdsal.binding.dom.codec.osgi.impl; - -import static java.util.Objects.requireNonNull; - -import com.google.common.collect.ImmutableList; -import com.google.common.io.Resources; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.regex.Pattern; -import org.opendaylight.yangtools.concepts.ObjectRegistration; -import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider; -import org.opendaylight.yangtools.yang.binding.YangModuleInfo; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.util.tracker.BundleTracker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Tracks bundles and attempts to retrieve YangModuleInfo, which is then fed into ModuleInfoRegistry. - */ -final class ModuleInfoBundleTracker extends BundleTracker>> { - private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBundleTracker.class); - // FIXME: this should be in a place shared with maven-sal-api-gen-plugin - private static final String MODULE_INFO_PROVIDER_PATH_PREFIX = "META-INF/services/"; - - private static final String YANG_MODLE_BINDING_PROVIDER_SERVICE = MODULE_INFO_PROVIDER_PATH_PREFIX - + YangModelBindingProvider.class.getName(); - - private static final Pattern BRACE_PATTERN = Pattern.compile("{}", Pattern.LITERAL); - - private final OsgiModuleInfoRegistry moduleInfoRegistry; - - private volatile boolean deferUpdates = true; - - ModuleInfoBundleTracker(final BundleContext context, final OsgiModuleInfoRegistry moduleInfoRegistry) { - super(context, Bundle.RESOLVED | Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE, null); - this.moduleInfoRegistry = requireNonNull(moduleInfoRegistry); - } - - @Override - public void open() { - super.open(); - deferUpdates = false; - moduleInfoRegistry.updateService(); - } - - @Override - public void close() { - deferUpdates = true; - super.close(); - } - - @Override - @SuppressWarnings("checkstyle:illegalCatch") - public Collection> addingBundle(final Bundle bundle, final BundleEvent event) { - final URL resource = bundle.getEntry(YANG_MODLE_BINDING_PROVIDER_SERVICE); - if (resource == null) { - LOG.debug("Bundle {} does not have an entry for {}", bundle, YANG_MODLE_BINDING_PROVIDER_SERVICE); - return ImmutableList.of(); - } - - LOG.debug("Got addingBundle({}) with YangModelBindingProvider resource {}", bundle, resource); - final List lines; - try { - lines = Resources.readLines(resource, StandardCharsets.UTF_8); - } catch (IOException e) { - LOG.error("Error while reading {} from bundle {}", resource, bundle, e); - return ImmutableList.of(); - } - - if (lines.isEmpty()) { - LOG.debug("Bundle {} has empty services for {}", bundle, YANG_MODLE_BINDING_PROVIDER_SERVICE); - return ImmutableList.of(); - } - - final List> registrations = new ArrayList<>(lines.size()); - for (String moduleInfoName : lines) { - LOG.trace("Retrieve ModuleInfo({}, {})", moduleInfoName, bundle); - final YangModuleInfo moduleInfo; - try { - moduleInfo = retrieveModuleInfo(moduleInfoName, bundle); - } catch (RuntimeException e) { - LOG.warn("Failed to acquire {} from bundle {}, ignoring it", moduleInfoName, bundle, e); - continue; - } - - registrations.add(moduleInfoRegistry.registerInfo(moduleInfo)); - } - - if (!deferUpdates) { - moduleInfoRegistry.updateService(); - } - - LOG.trace("Bundle {} resulted in registrations {}", bundle, registrations); - return registrations; - } - - @Override - public void modifiedBundle(final Bundle bundle, final BundleEvent event, - final Collection> object) { - // No-op - } - - @Override - @SuppressWarnings("checkstyle:illegalCatch") - public void removedBundle(final Bundle bundle, final BundleEvent event, - final Collection> regs) { - if (regs == null) { - return; - } - - try { - regs.forEach(reg -> { - try { - reg.close(); - } catch (Exception e) { - LOG.warn("Unable to unregister YangModuleInfo {}", reg.getInstance(), e); - } - }); - } finally { - if (!deferUpdates) { - moduleInfoRegistry.updateService(); - } - } - } - - private static YangModuleInfo retrieveModuleInfo(final String moduleInfoClass, final Bundle bundle) { - final Class clazz = loadClass(moduleInfoClass, bundle); - if (!YangModelBindingProvider.class.isAssignableFrom(clazz)) { - String errorMessage = logMessage("Class {} does not implement {} in bundle {}", clazz, - YangModelBindingProvider.class, bundle); - throw new IllegalStateException(errorMessage); - } - - final YangModelBindingProvider instance; - try { - Object instanceObj = clazz.newInstance(); - instance = YangModelBindingProvider.class.cast(instanceObj); - } catch (InstantiationException e) { - String errorMessage = logMessage("Could not instantiate {} in bundle {}, reason {}", moduleInfoClass, - bundle, e); - throw new IllegalStateException(errorMessage, e); - } catch (IllegalAccessException e) { - String errorMessage = logMessage("Illegal access during instantiation of class {} in bundle {}, reason {}", - moduleInfoClass, bundle, e); - throw new IllegalStateException(errorMessage, e); - } - - try { - return instance.getModuleInfo(); - } catch (NoClassDefFoundError | ExceptionInInitializerError e) { - throw new IllegalStateException("Error while executing getModuleInfo on " + instance, e); - } - } - - private static Class loadClass(final String moduleInfoClass, final Bundle bundle) { - try { - return bundle.loadClass(moduleInfoClass); - } catch (ClassNotFoundException e) { - String errorMessage = logMessage("Could not find class {} in bundle {}, reason {}", moduleInfoClass, bundle, - e); - throw new IllegalStateException(errorMessage); - } - } - - @SuppressFBWarnings("SLF4J_UNKNOWN_ARRAY") - private static String logMessage(final String slfMessage, final Object... params) { - LOG.info(slfMessage, params); - return String.format(BRACE_PATTERN.matcher(slfMessage).replaceAll("%s"), params); - } -} diff --git a/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/OsgiModuleInfoRegistry.java b/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/OsgiModuleInfoRegistry.java deleted file mode 100644 index b001583f70..0000000000 --- a/binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/OsgiModuleInfoRegistry.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.mdsal.binding.dom.codec.osgi.impl; - -import static java.util.Objects.requireNonNull; - -import org.checkerframework.checker.lock.qual.GuardedBy; -import org.opendaylight.binding.runtime.spi.ModuleInfoBackedContext; -import org.opendaylight.binding.runtime.spi.ModuleInfoRegistry; -import org.opendaylight.yangtools.concepts.ObjectRegistration; -import org.opendaylight.yangtools.yang.binding.YangModuleInfo; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; -import org.osgi.framework.ServiceRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Update SchemaContext service in Service Registry each time new YangModuleInfo is added or removed. - */ -final class OsgiModuleInfoRegistry implements ModuleInfoRegistry { - private static final Logger LOG = LoggerFactory.getLogger(OsgiModuleInfoRegistry.class); - - private final SchemaContextProvider schemaContextProvider; - private final ModuleInfoBackedContext moduleInfoRegistry; - - @GuardedBy("this") - private ServiceRegistration registration; - @GuardedBy("this") - private long generation; - - OsgiModuleInfoRegistry(final ModuleInfoBackedContext moduleInfoRegistry, - final SchemaContextProvider schemaContextProvider) { - - this.moduleInfoRegistry = requireNonNull(moduleInfoRegistry); - this.schemaContextProvider = requireNonNull(schemaContextProvider); - } - - @Override - public ObjectRegistration registerModuleInfo(final YangModuleInfo yangModuleInfo) { - return new ObjectRegistrationWrapper(registerInfo(yangModuleInfo)); - } - - @SuppressWarnings("checkstyle:illegalCatch") - synchronized void updateService() { - final SchemaContext context; - try { - context = schemaContextProvider.getSchemaContext(); - } catch (final RuntimeException e) { - // The ModuleInfoBackedContext throws a RuntimeException if it can't create the schema context. - LOG.error("Error updating the schema context", e); - return; - } - LOG.trace("Assembled context {}", context); - - // // FIXME: MDSAL-392: UGH, this should be a snapshot - // final BindingRuntimeContext next = DefaultBindingRuntimeContext.create( - // new DefaultBindingRuntimeGenerator().generateTypeMapping(context), moduleInfoRegistry); - - // FIXME: publish new the new context, remove the old one - } - - ObjectRegistration registerInfo(final YangModuleInfo yangModuleInfo) { - return moduleInfoRegistry.registerModuleInfo(yangModuleInfo); - } - - private class ObjectRegistrationWrapper implements ObjectRegistration { - private final ObjectRegistration inner; - - ObjectRegistrationWrapper(final ObjectRegistration inner) { - this.inner = requireNonNull(inner); - } - - @Override - public YangModuleInfo getInstance() { - return inner.getInstance(); - } - - @Override - @SuppressWarnings("checkstyle:illegalCatch") - public void close() { - try { - inner.close(); - } finally { - // send modify event when a bundle disappears - updateService(); - } - } - - @Override - public String toString() { - return inner.toString(); - } - } -} diff --git a/binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/binding/runtime/api/ModuleInfoSnapshot.java b/binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/binding/runtime/api/ModuleInfoSnapshot.java new file mode 100644 index 0000000000..5a5d1b276f --- /dev/null +++ b/binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/binding/runtime/api/ModuleInfoSnapshot.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.binding.runtime.api; + +import com.google.common.annotations.Beta; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; + +@Beta +public interface ModuleInfoSnapshot extends Immutable, ClassLoadingStrategy, EffectiveModelContextProvider, + SchemaSourceProvider { + +} diff --git a/binding/mdsal-binding-dom-codec-osgi/pom.xml b/binding/mdsal-binding-runtime-osgi/pom.xml similarity index 75% rename from binding/mdsal-binding-dom-codec-osgi/pom.xml rename to binding/mdsal-binding-runtime-osgi/pom.xml index 7311fa5761..3d941fc4f6 100644 --- a/binding/mdsal-binding-dom-codec-osgi/pom.xml +++ b/binding/mdsal-binding-runtime-osgi/pom.xml @@ -1,7 +1,7 @@ - - org.opendaylight.mdsal - mdsal-binding-generator-impl + mdsal-binding-runtime-spi - org.opendaylight.mdsal - mdsal-binding-runtime-spi + mdsal-dom-schema-osgi org.osgi @@ -51,8 +42,13 @@ mockito-configuration - org.opendaylight.yangtools - yang-test-util + org.opendaylight.mdsal + mdsal-binding-test-model + + + org.opendaylight.mdsal + mdsal-binding-generator-impl + test @@ -61,10 +57,11 @@ org.apache.felix maven-bundle-plugin - true + org.opendaylight.mdsal.binding.runtime.osgi - ${project.groupId}.${project.artifactId} + + <_dsannotations-options>norequirements diff --git a/binding/mdsal-binding-runtime-osgi/src/main/java/org/opendaylight/mdsal/binding/runtime/osgi/impl/BindingRuntimeContextImpl.java b/binding/mdsal-binding-runtime-osgi/src/main/java/org/opendaylight/mdsal/binding/runtime/osgi/impl/BindingRuntimeContextImpl.java new file mode 100644 index 0000000000..ddde76201c --- /dev/null +++ b/binding/mdsal-binding-runtime-osgi/src/main/java/org/opendaylight/mdsal/binding/runtime/osgi/impl/BindingRuntimeContextImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.runtime.osgi.impl; + +import org.opendaylight.binding.runtime.api.AbstractBindingRuntimeContext; +import org.opendaylight.binding.runtime.api.BindingRuntimeContext; +import org.opendaylight.binding.runtime.api.BindingRuntimeGenerator; +import org.opendaylight.binding.runtime.api.BindingRuntimeTypes; +import org.opendaylight.binding.runtime.api.ClassLoadingStrategy; +import org.opendaylight.binding.runtime.api.DefaultBindingRuntimeContext; +import org.opendaylight.mdsal.dom.schema.osgi.OSGiModuleInfoSnapshot; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Factory Component which implements {@link BindingRuntimeContext}. + */ +@Component(service = BindingRuntimeContext.class, immediate = true) +public final class BindingRuntimeContextImpl extends AbstractBindingRuntimeContext { + private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeContextImpl.class); + + @Reference + OSGiModuleInfoSnapshot effectiveModel = null; + @Reference + BindingRuntimeGenerator generator = null; + + private BindingRuntimeContext delegate = null; + private long generation; + + @Override + public ClassLoadingStrategy getStrategy() { + return delegate.getStrategy(); + } + + @Override + public BindingRuntimeTypes getTypes() { + return delegate.getTypes(); + } + + @Activate + void activate() { + generation = effectiveModel.getGeneration(); + delegate = DefaultBindingRuntimeContext.create( + generator.generateTypeMapping(effectiveModel.getEffectiveModelContext()), effectiveModel); + + LOG.debug("BindingRuntimeContext generation {} activated", generation); + } + + @Deactivate + void deactivate() { + delegate = null; + LOG.debug("BindingRuntimeContext generation {} deactivated", generation); + } +} diff --git a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/AbstractModuleInfoTracker.java b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/AbstractModuleInfoTracker.java new file mode 100644 index 0000000000..8c999bfc79 --- /dev/null +++ b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/AbstractModuleInfoTracker.java @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.binding.runtime.spi; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.util.concurrent.ListenableFuture; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import org.checkerframework.checker.lock.qual.GuardedBy; +import org.checkerframework.checker.lock.qual.Holding; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot; +import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.concepts.Mutable; +import org.opendaylight.yangtools.concepts.ObjectRegistration; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement; +import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; +import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver; +import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaSourceRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract base class for things that create an EffectiveModuleContext or similar things from a (dynamic) set of + * YangModuleInfo objects. + * + *

+ * Note this class has some locking quirks and may end up being further refactored. + */ +abstract class AbstractModuleInfoTracker implements Mutable { + abstract static class AbstractRegisteredModuleInfo { + final YangTextSchemaSourceRegistration reg; + final YangModuleInfo info; + final ClassLoader loader; + + AbstractRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg, + final ClassLoader loader) { + this.info = requireNonNull(info); + this.reg = requireNonNull(reg); + this.loader = requireNonNull(loader); + } + + @Override + public final String toString() { + return addToStringAttributes(MoreObjects.toStringHelper(this)).toString(); + } + + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return helper.add("info", info).add("registration", reg).add("classLoader", loader); + } + } + + private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo { + private int refcount = 1; + + ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg, + final ClassLoader loader) { + super(info, reg, loader); + } + + void incRef() { + ++refcount; + } + + boolean decRef() { + return --refcount == 0; + } + + @Override + ToStringHelper addToStringAttributes(final ToStringHelper helper) { + return super.addToStringAttributes(helper).add("refCount", refcount); + } + } + + private static final class ImplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo { + ImplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg, + final ClassLoader loader) { + super(info, reg, loader); + } + } + + private static final Logger LOG = LoggerFactory.getLogger(AbstractModuleInfoTracker.class); + + private final YangTextSchemaContextResolver ctxResolver; + + @GuardedBy("this") + private final ListMultimap packageToInfoReg = + MultimapBuilder.hashKeys().arrayListValues().build(); + @GuardedBy("this") + private final ListMultimap sourceToInfoReg = + MultimapBuilder.hashKeys().arrayListValues().build(); + @GuardedBy("this") + private @Nullable ModuleInfoSnapshot currentSnapshot; + + AbstractModuleInfoTracker(final YangTextSchemaContextResolver resolver) { + this.ctxResolver = requireNonNull(resolver); + } + + public final synchronized List> registerModuleInfos( + final Iterable moduleInfos) { + final List> ret = new ArrayList<>(); + for (YangModuleInfo yangModuleInfo : moduleInfos) { + ret.add(register(requireNonNull(yangModuleInfo))); + } + return ret; + } + + @Holding("this") + private ObjectRegistration register(final @NonNull YangModuleInfo moduleInfo) { + final Builder regBuilder = ImmutableList.builder(); + for (YangModuleInfo info : flatDependencies(moduleInfo)) { + regBuilder.add(registerExplicitModuleInfo(info)); + } + final ImmutableList regInfos = regBuilder.build(); + + return new AbstractObjectRegistration<>(moduleInfo) { + @Override + protected void removeRegistration() { + unregister(regInfos); + } + }; + } + + @Holding("this") + final void registerImplicitBindingClass(final Class bindingClass) { + registerImplicitModuleInfo(BindingRuntimeHelpers.extractYangModuleInfo(bindingClass)); + } + + @Holding("this") + final @Nullable ClassLoader findClassLoader(final String fullyQualifiedName) { + // This performs an explicit check for binding classes + final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName); + + // Try to find a loaded class loader + // FIXME: two-step process, try explicit registrations first + for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) { + return reg.loader; + } + return null; + } + + /* + * Perform implicit registration of a YangModuleInfo and any of its dependencies. If there is a registration for + * a particular source, we do not create a duplicate registration. + */ + @Holding("this") + private void registerImplicitModuleInfo(final @NonNull YangModuleInfo moduleInfo) { + for (YangModuleInfo info : flatDependencies(moduleInfo)) { + final Class infoClass = info.getClass(); + final SourceIdentifier sourceId = sourceIdentifierFrom(info); + if (sourceToInfoReg.containsKey(sourceId)) { + LOG.debug("Skipping implicit registration of {} as source {} is already registered", info, sourceId); + continue; + } + + final YangTextSchemaSourceRegistration reg; + try { + reg = ctxResolver.registerSource(toYangTextSource(sourceId, info)); + } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) { + LOG.warn("Failed to register info {} source {}, ignoring it", info, sourceId, e); + continue; + } + + final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg, + infoClass.getClassLoader()); + sourceToInfoReg.put(sourceId, regInfo); + packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo); + } + } + + /* + * Perform explicit registration of a YangModuleInfo. This always results in a new explicit registration. In case + * there is a pre-existing implicit registration, it is removed just after the explicit registration is made. + */ + @Holding("this") + private ExplicitRegisteredModuleInfo registerExplicitModuleInfo(final @NonNull YangModuleInfo info) { + // First search for an existing explicit registration + final SourceIdentifier sourceId = sourceIdentifierFrom(info); + for (AbstractRegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) { + if (reg instanceof ExplicitRegisteredModuleInfo && info.equals(reg.info)) { + final ExplicitRegisteredModuleInfo explicit = (ExplicitRegisteredModuleInfo) reg; + explicit.incRef(); + LOG.debug("Reusing explicit registration {}", explicit); + return explicit; + } + } + + // Create an explicit registration + final YangTextSchemaSourceRegistration reg; + try { + reg = ctxResolver.registerSource(toYangTextSource(sourceId, info)); + } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) { + throw new IllegalStateException("Failed to register info " + info, e); + } + + final Class infoClass = info.getClass(); + final String packageName = BindingReflections.getModelRootPackageName(infoClass.getPackage()); + final ExplicitRegisteredModuleInfo regInfo = new ExplicitRegisteredModuleInfo(info, reg, + infoClass.getClassLoader()); + LOG.debug("Created new explicit registration {}", regInfo); + + sourceToInfoReg.put(sourceId, regInfo); + removeImplicit(sourceToInfoReg.get(sourceId)); + packageToInfoReg.put(packageName, regInfo); + removeImplicit(packageToInfoReg.get(packageName)); + + return regInfo; + } + + // Reconsider utility of this + final Optional getResolverEffectiveModel() { + return ctxResolver.getEffectiveModelContext(); + } + + @Deprecated + final ListenableFuture getResolverSource(final SourceIdentifier sourceIdentifier) { + return ctxResolver.getSource(sourceIdentifier); + } + + @Holding("this") + final @NonNull ModuleInfoSnapshot updateSnapshot() { + final EffectiveModelContext effectiveModel = ctxResolver.getEffectiveModelContext().orElseThrow(); + final ModuleInfoSnapshot local = currentSnapshot; + if (local != null && local.getEffectiveModelContext().equals(effectiveModel)) { + return local; + } + + return updateSnapshot(effectiveModel); + } + + @Holding("this") + private @NonNull ModuleInfoSnapshot updateSnapshot(final EffectiveModelContext effectiveModel) { + // Alright, now let's find out which sources got captured + final Set sources = new HashSet<>(); + for (Entry entry : effectiveModel.getModuleStatements().entrySet()) { + final Optional revision = entry.getKey().getRevision(); + final ModuleEffectiveStatement module = entry.getValue(); + + sources.add(RevisionSourceIdentifier.create(module.argument(), revision)); + module.streamEffectiveSubstatements(SubmoduleEffectiveStatement.class) + .map(submodule -> RevisionSourceIdentifier.create(submodule.argument(), revision)) + .forEach(sources::add); + } + + final Map moduleInfos = new HashMap<>(); + final Map classLoaders = new HashMap<>(); + for (SourceIdentifier source : sources) { + final List regs = sourceToInfoReg.get(source); + checkState(!regs.isEmpty(), "No registration for %s", source); + + AbstractRegisteredModuleInfo reg = regs.stream() + .filter(ExplicitRegisteredModuleInfo.class::isInstance).findFirst() + .orElse(null); + if (reg == null) { + reg = regs.get(0); + } + + final YangModuleInfo info = reg.info; + moduleInfos.put(source, info); + final Class infoClass = info.getClass(); + classLoaders.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), + infoClass.getClassLoader()); + } + + final ModuleInfoSnapshot next = new DefaultModuleInfoSnapshot(effectiveModel, moduleInfos, classLoaders); + currentSnapshot = next; + return next; + } + + @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", + justification = "https://github.com/spotbugs/spotbugs/issues/811") + private synchronized void unregister(final ImmutableList regInfos) { + for (ExplicitRegisteredModuleInfo regInfo : regInfos) { + if (!regInfo.decRef()) { + LOG.debug("Registration {} has references, not removing it", regInfo); + continue; + } + + final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info); + if (!sourceToInfoReg.remove(sourceId, regInfo)) { + LOG.warn("Failed to find {} registered under {}", regInfo, sourceId); + } + + final String packageName = BindingReflections.getModelRootPackageName(regInfo.info.getClass().getPackage()); + if (!packageToInfoReg.remove(packageName, regInfo)) { + LOG.warn("Failed to find {} registered under {}", regInfo, packageName); + } + + regInfo.reg.close(); + } + } + + @Holding("this") + private static void removeImplicit(final List regs) { + /* + * Search for implicit registration for a sourceId/packageName. + * + * Since we are called while an explicit registration is being created (and has already been inserted, we know + * there is at least one entry in the maps. We also know registrations retain the order in which they were + * created and that implicit registrations are not created if there already is a registration. + * + * This means that if an implicit registration exists, it will be the first entry in the list. + */ + final AbstractRegisteredModuleInfo reg = regs.get(0); + if (reg instanceof ImplicitRegisteredModuleInfo) { + LOG.debug("Removing implicit registration {}", reg); + regs.remove(0); + reg.reg.close(); + } + } + + private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier, + final YangModuleInfo moduleInfo) { + return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource()); + } + + private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) { + final QName name = moduleInfo.getName(); + return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision()); + } + + private static @NonNull List<@NonNull YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) { + // Flatten the modules being registered, with the triggering module being first... + final Set requiredInfos = new LinkedHashSet<>(); + flatDependencies(requiredInfos, moduleInfo); + + // ... now reverse the order in an effort to register dependencies first (triggering module last) + return ImmutableList.copyOf(requiredInfos).reverse(); + } + + private static void flatDependencies(final Set set, final YangModuleInfo moduleInfo) { + if (set.add(moduleInfo)) { + for (YangModuleInfo dep : moduleInfo.getImportedModules()) { + flatDependencies(set, dep); + } + } + } +} diff --git a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/BindingRuntimeHelpers.java b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/BindingRuntimeHelpers.java index b252b2e5cf..c5ccd74835 100644 --- a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/BindingRuntimeHelpers.java +++ b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/BindingRuntimeHelpers.java @@ -56,7 +56,7 @@ public final class BindingRuntimeHelpers { } @SuppressWarnings("checkstyle:IllegalCatch") - static YangModuleInfo extractYangModuleInfo(final Class clazz) { + static @NonNull YangModuleInfo extractYangModuleInfo(final Class clazz) { try { return BindingReflections.getModuleInfo(clazz); } catch (Exception e) { @@ -66,7 +66,7 @@ public final class BindingRuntimeHelpers { private static ModuleInfoBackedContext prepareContext(final Iterable moduleInfos) { final ModuleInfoBackedContext ctx = ModuleInfoBackedContext.create(); - ctx.addModuleInfos(moduleInfos); + ctx.registerModuleInfos(moduleInfos); return ctx; } } diff --git a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/DefaultModuleInfoSnapshot.java b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/DefaultModuleInfoSnapshot.java new file mode 100644 index 0000000000..87c2e4308a --- /dev/null +++ b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/DefaultModuleInfoSnapshot.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.binding.runtime.spi; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Map; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot; +import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; + +final class DefaultModuleInfoSnapshot extends GeneratedClassLoadingStrategy implements ModuleInfoSnapshot { + private final ImmutableMap moduleInfos; + private final ImmutableMap classLoaders; + private final @NonNull EffectiveModelContext effectiveModel; + + DefaultModuleInfoSnapshot(final EffectiveModelContext effectiveModel, + final Map moduleInfos, final Map classLoaders) { + this.effectiveModel = requireNonNull(effectiveModel); + this.moduleInfos = ImmutableMap.copyOf(moduleInfos); + this.classLoaders = ImmutableMap.copyOf(classLoaders); + } + + @Override + public EffectiveModelContext getEffectiveModelContext() { + return effectiveModel; + } + + @Override + public ListenableFuture getSource(final SourceIdentifier sourceIdentifier) { + final YangModuleInfo info = moduleInfos.get(sourceIdentifier); + if (info == null) { + return Futures.immediateFailedFuture( + new MissingSchemaSourceException("No source registered", sourceIdentifier)); + } + return Futures.immediateFuture(YangTextSchemaSource.delegateForByteSource(sourceIdentifier, + info.getYangTextByteSource())); + } + + @Override + public Class loadClass(final String fullyQualifiedName) throws ClassNotFoundException { + final String packageName = BindingReflections.getModelRootPackageName(fullyQualifiedName); + final ClassLoader loader = classLoaders.get(packageName); + if (loader == null) { + throw new ClassNotFoundException("Package " + packageName + " not found"); + } + return loader.loadClass(fullyQualifiedName); + } +} diff --git a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoBackedContext.java b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoBackedContext.java index afb2afb3c2..31de1aac18 100644 --- a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoBackedContext.java +++ b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoBackedContext.java @@ -11,54 +11,32 @@ import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; -import com.google.common.base.MoreObjects; -import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.MultimapBuilder; import com.google.common.util.concurrent.ListenableFuture; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Optional; import java.util.Set; -import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.Holding; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.binding.runtime.api.BindingRuntimeContext; import org.opendaylight.binding.runtime.api.BindingRuntimeGenerator; import org.opendaylight.binding.runtime.api.ClassLoadingStrategy; import org.opendaylight.binding.runtime.api.DefaultBindingRuntimeContext; -import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; -import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; -import org.opendaylight.yangtools.concepts.ObjectRegistration; import org.opendaylight.yangtools.util.ClassLoaderUtils; import org.opendaylight.yangtools.yang.binding.YangModuleInfo; -import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider; import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; -import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException; -import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier; -import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver; -import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaSourceRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Beta -public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy - implements ModuleInfoRegistry, EffectiveModelContextProvider, SchemaSourceProvider { +public class ModuleInfoBackedContext extends AbstractModuleInfoTracker implements ClassLoadingStrategy, + EffectiveModelContextProvider, SchemaSourceProvider { private static final class WithFallback extends ModuleInfoBackedContext { private final @NonNull ClassLoadingStrategy fallback; @@ -71,64 +49,11 @@ public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy Class loadUnknownClass(final String fullyQualifiedName) throws ClassNotFoundException { // We have not found a matching registration, consult the backing strategy final Class cls = fallback.loadClass(fullyQualifiedName); - registerImplicitModuleInfo(BindingRuntimeHelpers.extractYangModuleInfo(cls)); + registerImplicitBindingClass(cls); return cls; } } - private abstract static class AbstractRegisteredModuleInfo { - final YangTextSchemaSourceRegistration reg; - final YangModuleInfo info; - final ClassLoader loader; - - AbstractRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg, - final ClassLoader loader) { - this.info = requireNonNull(info); - this.reg = requireNonNull(reg); - this.loader = requireNonNull(loader); - } - - @Override - public final String toString() { - return addToStringAttributes(MoreObjects.toStringHelper(this)).toString(); - } - - ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return helper.add("info", info).add("registration", reg).add("classLoader", loader); - } - } - - private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo { - private int refcount = 1; - - ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg, - final ClassLoader loader) { - super(info, reg, loader); - } - - void incRef() { - ++refcount; - } - - boolean decRef() { - return --refcount == 0; - } - - @Override - ToStringHelper addToStringAttributes(final ToStringHelper helper) { - return super.addToStringAttributes(helper).add("refCount", refcount); - } - } - - private static final class ImplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo { - ImplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg, - final ClassLoader loader) { - super(info, reg, loader); - } - } - - private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBackedContext.class); - private static final LoadingCache, ModuleInfoBackedContext>> CONTEXT_CACHES = CacheBuilder.newBuilder() .weakKeys().build(new CacheLoader key) { final ModuleInfoBackedContext context = ModuleInfoBackedContext.create(strategy); - context.addModuleInfos(key); + context.registerModuleInfos(key); return context; } }); } }); - private final YangTextSchemaContextResolver ctxResolver; - - @GuardedBy("this") - private final ListMultimap packageToInfoReg = - MultimapBuilder.hashKeys().arrayListValues().build(); - @GuardedBy("this") - private final ListMultimap sourceToInfoReg = - MultimapBuilder.hashKeys().arrayListValues().build(); - ModuleInfoBackedContext(final YangTextSchemaContextResolver resolver) { - this.ctxResolver = requireNonNull(resolver); + super(resolver); } @Beta @@ -200,42 +116,15 @@ public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy } @Override - @SuppressWarnings("checkstyle:illegalCatch") - public final Class loadClass(final String fullyQualifiedName) throws ClassNotFoundException { - // This performs an explicit check for binding classes - final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName); - - synchronized (this) { - // Try to find a loaded class loader - // FIXME: two-step process, try explicit registrations first - for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) { - return ClassLoaderUtils.loadClass(reg.loader, fullyQualifiedName); - } - - return loadUnknownClass(fullyQualifiedName); - } - } - - @Holding("this") - Class loadUnknownClass(final String fullyQualifiedName) throws ClassNotFoundException { - throw new ClassNotFoundException(fullyQualifiedName); - } - - @Override - public final synchronized ObjectRegistration registerModuleInfo( - final YangModuleInfo yangModuleInfo) { - return register(requireNonNull(yangModuleInfo)); + public final synchronized Class loadClass(final String fullyQualifiedName) throws ClassNotFoundException { + final ClassLoader loader = findClassLoader(fullyQualifiedName); + return loader != null ? ClassLoaderUtils.loadClass(loader, fullyQualifiedName) + : loadUnknownClass(fullyQualifiedName); } @Override public final ListenableFuture getSource(final SourceIdentifier sourceIdentifier) { - return ctxResolver.getSource(sourceIdentifier); - } - - final synchronized void addModuleInfos(final Iterable moduleInfos) { - for (YangModuleInfo yangModuleInfo : moduleInfos) { - register(requireNonNull(yangModuleInfo)); - } + return getResolverSource(sourceIdentifier); } @Beta @@ -248,160 +137,11 @@ public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy // Unite with current SchemaService public final Optional tryToCreateModelContext() { - return ctxResolver.getEffectiveModelContext(); + return getResolverEffectiveModel(); } @Holding("this") - private ObjectRegistration register(final @NonNull YangModuleInfo moduleInfo) { - final Builder regBuilder = ImmutableList.builder(); - for (YangModuleInfo info : flatDependencies(moduleInfo)) { - regBuilder.add(registerExplicitModuleInfo(info)); - } - final ImmutableList regInfos = regBuilder.build(); - - return new AbstractObjectRegistration<>(moduleInfo) { - @Override - protected void removeRegistration() { - unregister(regInfos); - } - }; - } - - /* - * Perform implicit registration of a YangModuleInfo and any of its dependencies. If there is a registration for - * a particular source, we do not create a duplicate registration. - */ - @Holding("this") - final void registerImplicitModuleInfo(final @NonNull YangModuleInfo moduleInfo) { - for (YangModuleInfo info : flatDependencies(moduleInfo)) { - final Class infoClass = info.getClass(); - final SourceIdentifier sourceId = sourceIdentifierFrom(info); - if (sourceToInfoReg.containsKey(sourceId)) { - LOG.debug("Skipping implicit registration of {} as source {} is already registered", info, sourceId); - continue; - } - - final YangTextSchemaSourceRegistration reg; - try { - reg = ctxResolver.registerSource(toYangTextSource(sourceId, info)); - } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) { - LOG.warn("Failed to register info {} source {}, ignoring it", info, sourceId, e); - continue; - } - - final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg, - infoClass.getClassLoader()); - sourceToInfoReg.put(sourceId, regInfo); - packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo); - } - } - - /* - * Perform explicit registration of a YangModuleInfo. This always results in a new explicit registration. In case - * there is a pre-existing implicit registration, it is removed just after the explicit registration is made. - */ - @Holding("this") - private ExplicitRegisteredModuleInfo registerExplicitModuleInfo(final @NonNull YangModuleInfo info) { - // First search for an existing explicit registration - final SourceIdentifier sourceId = sourceIdentifierFrom(info); - for (AbstractRegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) { - if (reg instanceof ExplicitRegisteredModuleInfo && info.equals(reg.info)) { - final ExplicitRegisteredModuleInfo explicit = (ExplicitRegisteredModuleInfo) reg; - explicit.incRef(); - LOG.debug("Reusing explicit registration {}", explicit); - return explicit; - } - } - - // Create an explicit registration - final YangTextSchemaSourceRegistration reg; - try { - reg = ctxResolver.registerSource(toYangTextSource(sourceId, info)); - } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) { - throw new IllegalStateException("Failed to register info " + info, e); - } - - final Class infoClass = info.getClass(); - final String packageName = BindingReflections.getModelRootPackageName(infoClass.getPackage()); - final ExplicitRegisteredModuleInfo regInfo = new ExplicitRegisteredModuleInfo(info, reg, - infoClass.getClassLoader()); - LOG.debug("Created new explicit registration {}", regInfo); - - sourceToInfoReg.put(sourceId, regInfo); - removeImplicit(sourceToInfoReg.get(sourceId)); - packageToInfoReg.put(packageName, regInfo); - removeImplicit(packageToInfoReg.get(packageName)); - - return regInfo; - } - - final synchronized void unregister(final ImmutableList regInfos) { - for (ExplicitRegisteredModuleInfo regInfo : regInfos) { - if (!regInfo.decRef()) { - LOG.debug("Registration {} has references, not removing it", regInfo); - continue; - } - - final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info); - if (!sourceToInfoReg.remove(sourceId, regInfo)) { - LOG.warn("Failed to find {} registered under {}", regInfo, sourceId); - } - - final String packageName = BindingReflections.getModelRootPackageName(regInfo.info.getClass().getPackage()); - if (!packageToInfoReg.remove(packageName, regInfo)) { - LOG.warn("Failed to find {} registered under {}", regInfo, packageName); - } - - regInfo.reg.close(); - } - } - - @Holding("this") - private static void removeImplicit(final List regs) { - /* - * Search for implicit registration for a sourceId/packageName. - * - * Since we are called while an explicit registration is being created (and has already been inserted, we know - * there is at least one entry in the maps. We also know registrations retain the order in which they were - * created and that implicit registrations are not created if there already is a registration. - * - * This means that if an implicit registration exists, it will be the first entry in the list. - */ - final AbstractRegisteredModuleInfo reg = regs.get(0); - if (reg instanceof ImplicitRegisteredModuleInfo) { - LOG.debug("Removing implicit registration {}", reg); - regs.remove(0); - reg.reg.close(); - } - } - - private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier, - final YangModuleInfo moduleInfo) { - return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource()); - } - - private static SourceIdentifier sourceIdentifierFrom(final YangModuleInfo moduleInfo) { - final QName name = moduleInfo.getName(); - return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision()); - } - - private static List flatDependencies(final YangModuleInfo moduleInfo) { - // Flatten the modules being registered, with the triggering module being first... - final Set requiredInfos = new LinkedHashSet<>(); - flatDependencies(requiredInfos, moduleInfo); - - // ... now reverse the order in an effort to register dependencies first (triggering module last) - final List intendedOrder = new ArrayList<>(requiredInfos); - Collections.reverse(intendedOrder); - - return intendedOrder; - } - - private static void flatDependencies(final Set set, final YangModuleInfo moduleInfo) { - if (set.add(moduleInfo)) { - for (YangModuleInfo dep : moduleInfo.getImportedModules()) { - flatDependencies(set, dep); - } - } + Class loadUnknownClass(final String fullyQualifiedName) throws ClassNotFoundException { + throw new ClassNotFoundException(fullyQualifiedName); } } diff --git a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoRegistry.java b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoRegistry.java deleted file mode 100644 index 8def478575..0000000000 --- a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoRegistry.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.binding.runtime.spi; - -import com.google.common.annotations.Beta; -import org.opendaylight.yangtools.concepts.ObjectRegistration; -import org.opendaylight.yangtools.yang.binding.YangModuleInfo; - -@Beta -public interface ModuleInfoRegistry { - - ObjectRegistration registerModuleInfo(YangModuleInfo yangModuleInfo); -} diff --git a/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoSnapshotBuilder.java b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoSnapshotBuilder.java new file mode 100644 index 0000000000..96b38a2094 --- /dev/null +++ b/binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoSnapshotBuilder.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.binding.runtime.spi; + +import com.google.common.annotations.Beta; +import java.util.NoSuchElementException; +import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot; +import org.opendaylight.yangtools.concepts.CheckedBuilder; +import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; +import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver; + +@Beta +public final class ModuleInfoSnapshotBuilder extends AbstractModuleInfoTracker + implements CheckedBuilder { + + public ModuleInfoSnapshotBuilder(final String name, final YangParserFactory parserFactory) { + super(YangTextSchemaContextResolver.create(name, parserFactory)); + } + + @Override + public synchronized ModuleInfoSnapshot build() { + return updateSnapshot(); + } +} diff --git a/binding/pom.xml b/binding/pom.xml index bdcb0f4892..6c7e488cdc 100644 --- a/binding/pom.xml +++ b/binding/pom.xml @@ -39,12 +39,12 @@ mdsal-binding-runtime-api mdsal-binding-runtime-spi + mdsal-binding-runtime-osgi mdsal-binding-test-model mdsal-binding-dom-codec mdsal-binding-dom-codec-api mdsal-binding-dom-codec-spi - mdsal-binding-dom-codec-osgi mdsal-binding-api mdsal-binding-spi diff --git a/docs/pom.xml b/docs/pom.xml index 7f7b49e9af..fea9eda01a 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -44,7 +44,7 @@ org.opendaylight.mdsal - mdsal-dom-schema-service-osgi + mdsal-dom-schema-osgi org.opendaylight.mdsal @@ -112,15 +112,15 @@ org.opendaylight.mdsal - mdsal-binding-dom-codec-osgi + mdsal-binding-runtime-api org.opendaylight.mdsal - mdsal-binding-runtime-api + mdsal-binding-runtime-spi org.opendaylight.mdsal - mdsal-binding-runtime-spi + mdsal-binding-runtime-osgi org.opendaylight.mdsal diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/schema/ScanningSchemaServiceProvider.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/schema/ScanningSchemaServiceProvider.java index f715cc8bec..029d354a63 100644 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/schema/ScanningSchemaServiceProvider.java +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/schema/ScanningSchemaServiceProvider.java @@ -30,6 +30,7 @@ import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated public class ScanningSchemaServiceProvider extends AbstractDOMSchemaService.WithYangTextSources implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(ScanningSchemaServiceProvider.class); diff --git a/dom/mdsal-dom-schema-service-osgi/pom.xml b/dom/mdsal-dom-schema-osgi/pom.xml similarity index 82% rename from dom/mdsal-dom-schema-service-osgi/pom.xml rename to dom/mdsal-dom-schema-osgi/pom.xml index ef7a022ce0..6bf2e1aa7c 100644 --- a/dom/mdsal-dom-schema-service-osgi/pom.xml +++ b/dom/mdsal-dom-schema-osgi/pom.xml @@ -16,28 +16,22 @@ ../dom-parent - mdsal-dom-schema-service-osgi + mdsal-dom-schema-osgi bundle - org.osgi - org.osgi.core - - - com.google.guava - guava + org.opendaylight.mdsal + mdsal-dom-api - org.opendaylight.mdsal - mdsal-dom-api + mdsal-binding-runtime-spi org.opendaylight.mdsal mdsal-dom-broker - org.opendaylight.yangtools util @@ -51,14 +45,22 @@ yang-parser-impl - org.opendaylight.yangtools - yang-test-util + org.osgi + org.osgi.core + + + org.osgi + osgi.cmpn org.opendaylight.yangtools mockito-configuration + + org.opendaylight.yangtools + yang-test-util + @@ -68,12 +70,10 @@ maven-bundle-plugin true + org.opendaylight.mdsal.dom.schema.osgi - osgiBundleScanningSchema - - *, - org.opendaylight.mdsal.dom.api - + + <_dsannotations-options>norequirements diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/OSGiModuleInfoSnapshot.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/OSGiModuleInfoSnapshot.java new file mode 100644 index 0000000000..8f16b23b27 --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/OSGiModuleInfoSnapshot.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi; + +import com.google.common.annotations.Beta; +import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot; + +/** + * Combination of a {@link ModuleInfoSnapshot} with a linear generation. + */ +@Beta +public interface OSGiModuleInfoSnapshot extends ModuleInfoSnapshot { + + long getGeneration(); +} diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiDOMSchemaService.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiDOMSchemaService.java new file mode 100644 index 0000000000..768ad5f27c --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiDOMSchemaService.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017, 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.schema.osgi.OSGiModuleInfoSnapshot; +import org.opendaylight.mdsal.dom.spi.AbstractDOMSchemaService; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; +import org.osgi.service.component.ComponentFactory; +import org.osgi.service.component.ComponentInstance; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.FieldOption; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * OSGi Service Registry-backed implementation of {@link DOMSchemaService}. + */ +@Component(service = DOMSchemaService.class, immediate = true) +public final class OSGiDOMSchemaService extends AbstractDOMSchemaService { + private static final Logger LOG = LoggerFactory.getLogger(OSGiDOMSchemaService.class); + + @Reference(target = "(component.factory=" + SchemaSchemaContextListenerImpl.FACTORY_NAME + ")") + ComponentFactory listenerFactory = null; + + private final List listeners = new CopyOnWriteArrayList<>(); + + private volatile OSGiModuleInfoSnapshot currentContext; + + @Override + public EffectiveModelContext getGlobalContext() { + return currentContext.getEffectiveModelContext(); + } + + @Override + public ListenerRegistration registerSchemaContextListener( + final SchemaContextListener listener) { + return registerListener(requireNonNull(listener)); + } + + @Reference(fieldOption = FieldOption.REPLACE) + void bindContext(final OSGiModuleInfoSnapshot newContext) { + final EffectiveModelContext ctx = newContext.getEffectiveModelContext(); + LOG.trace("Updating context to {}", ctx); + currentContext = newContext; + listeners.forEach(listener -> notifyListener(ctx, listener)); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, + policyOption = ReferencePolicyOption.GREEDY) + void addListener(final SchemaContextListener listener) { + LOG.trace("Adding listener {}", listener); + listeners.add(listener); + listener.onGlobalContextUpdated(getGlobalContext()); + } + + void removeListener(final SchemaContextListener listener) { + LOG.trace("Removing listener {}", listener); + listeners.remove(listener); + } + + @Activate + @SuppressWarnings("static-method") + void activate() { + LOG.info("DOM Schema services activated"); + } + + @Deactivate + @SuppressWarnings("static-method") + void deactivate() { + LOG.info("DOM Schema services deactivated"); + } + + private @NonNull ListenerRegistration registerListener( + final @NonNull SchemaContextListener listener) { + final ComponentInstance reg = listenerFactory.newInstance(SchemaSchemaContextListenerImpl.props(listener)); + return new ListenerRegistration<>() { + @Override + public SchemaContextListener getInstance() { + return listener; + } + + @Override + public void close() { + reg.dispose(); + } + }; + } + + @SuppressWarnings("checkstyle:illegalCatch") + private static void notifyListener(final SchemaContext context, final SchemaContextListener listener) { + try { + listener.onGlobalContextUpdated(context); + } catch (RuntimeException e) { + LOG.warn("Failed to notify listener {}", listener, e); + } + } +} diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiEffectiveModelImpl.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiEffectiveModelImpl.java new file mode 100644 index 0000000000..96b58a52d0 --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiEffectiveModelImpl.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Map; +import org.gaul.modernizer_maven_annotations.SuppressModernizer; +import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot; +import org.opendaylight.mdsal.dom.schema.osgi.OSGiModuleInfoSnapshot; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +@Component(factory = OSGiEffectiveModelImpl.FACTORY_NAME, + service = { OSGiModuleInfoSnapshot.class, ModuleInfoSnapshot.class }) +public final class OSGiEffectiveModelImpl implements OSGiModuleInfoSnapshot { + // OSGi DS Component Factory name + static final String FACTORY_NAME = "org.opendaylight.mdsal.dom.schema.osgi.impl.OSGiEffectiveModelImpl"; + + // Keys to for activation properties + @VisibleForTesting + static final String GENERATION = "org.opendaylight.mdsal.dom.schema.osgi.impl.Generation"; + @VisibleForTesting + static final String DELEGATE = "org.opendaylight.mdsal.dom.schema.osgi.impl.ModuleInfoSnapshot"; + + private static final Logger LOG = LoggerFactory.getLogger(OSGiEffectiveModelImpl.class); + + private ModuleInfoSnapshot delegate; + private long generation; + + @Override + public long getGeneration() { + return generation; + } + + @Override + public EffectiveModelContext getEffectiveModelContext() { + return delegate.getEffectiveModelContext(); + } + + @Override + public ListenableFuture getSource(final SourceIdentifier sourceIdentifier) { + return delegate.getSource(sourceIdentifier); + } + + @Override + public Class loadClass(final String fullyQualifiedName) throws ClassNotFoundException { + return delegate.loadClass(fullyQualifiedName); + } + + @Activate + void activate(final Map properties) { + generation = (Long) verifyNotNull(properties.get(GENERATION)); + delegate = (ModuleInfoSnapshot) verifyNotNull(properties.get(DELEGATE)); + LOG.debug("ClassLoadingEffectiveModelContext generation {} activated", generation); + } + + @Deactivate + void deactivate() { + delegate = null; + LOG.debug("ClassLoadingEffectiveModelContext generation {} deactivated", generation); + } + + @SuppressModernizer + static Dictionary props(final long generation, final ModuleInfoSnapshot delegate) { + final Dictionary ret = new Hashtable<>(4); + ret.put(Constants.SERVICE_RANKING, ranking(generation)); + ret.put(GENERATION, generation); + ret.put(DELEGATE, requireNonNull(delegate)); + return ret; + } + + private static Integer ranking(final long generation) { + return generation >= 0 && generation <= Integer.MAX_VALUE ? (int) generation : Integer.MAX_VALUE; + } +} diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntime.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntime.java new file mode 100644 index 0000000000..3bea8c0257 --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntime.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(immediate = true) +public final class OSGiModelRuntime { + private static final Logger LOG = LoggerFactory.getLogger(OSGiModelRuntime.class); + + @Reference + YangParserFactory parserFactory = null; + @Reference(target = "(component.factory=" + OSGiEffectiveModelImpl.FACTORY_NAME + ")") + ComponentFactory contextFactory = null; + + private YangModuleInfoScanner bundleTracker = null; + private YangModuleInfoRegistry moduleRegistry = null; + + @Activate + void activate(final BundleContext ctx) { + LOG.info("Model Runtime starting"); + moduleRegistry = new YangModuleInfoRegistry(contextFactory, parserFactory); + bundleTracker = new YangModuleInfoScanner(ctx, moduleRegistry); + bundleTracker.open(); + moduleRegistry.enableScannerAndUpdate(); + LOG.info("Model Runtime started"); + } + + @Deactivate + void deactivate() { + LOG.info("Model Runtime stopping"); + moduleRegistry.close(); + bundleTracker.close(); + LOG.info("Model Runtime stopped"); + } +} diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/SchemaSchemaContextListenerImpl.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/SchemaSchemaContextListenerImpl.java new file mode 100644 index 0000000000..6f4eeaa385 --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/SchemaSchemaContextListenerImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Map; +import org.gaul.modernizer_maven_annotations.SuppressModernizer; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; + +/** + * A Factory Component which implements {@link SchemaContextListener}. Instances of this component are created through + * by {@link OSGiDOMSchemaService} each time a listener is registered. + */ +@Component(factory = SchemaSchemaContextListenerImpl.FACTORY_NAME, service = SchemaContextListener.class) +public final class SchemaSchemaContextListenerImpl implements SchemaContextListener { + static final String FACTORY_NAME = "org.opendaylight.mdsal.dom.schema.osgi.impl.SchemaSchemaContextListener"; + + @VisibleForTesting + static final String DELEGATE = "org.opendaylight.mdsal.dom.schema.osgi.SchemaSchemaContextListener"; + + private SchemaContextListener delegate = null; + + @Override + public void onGlobalContextUpdated(final SchemaContext context) { + delegate.onGlobalContextUpdated(context); + } + + @Activate + void activate(final Map properties) { + delegate = (SchemaContextListener) verifyNotNull(properties.get(DELEGATE)); + } + + @Deactivate + void deactivate() { + delegate = null; + } + + @SuppressModernizer + static Dictionary props(final SchemaContextListener delegate) { + final Dictionary ret = new Hashtable<>(2); + ret.put(DELEGATE, requireNonNull(delegate)); + return ret; + } +} diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoRegistry.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoRegistry.java new file mode 100644 index 0000000000..de1b4ad914 --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoRegistry.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017, 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.NoSuchElementException; +import org.checkerframework.checker.lock.qual.GuardedBy; +import org.checkerframework.checker.lock.qual.Holding; +import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot; +import org.opendaylight.binding.runtime.spi.ModuleInfoSnapshotBuilder; +import org.opendaylight.yangtools.concepts.ObjectRegistration; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; +import org.osgi.service.component.ComponentFactory; +import org.osgi.service.component.ComponentInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Update SchemaContext service in Service Registry each time new YangModuleInfo is added or removed. + */ +final class YangModuleInfoRegistry { + private static final Logger LOG = LoggerFactory.getLogger(YangModuleInfoRegistry.class); + + private final ComponentFactory contextFactory; + private final ModuleInfoSnapshotBuilder moduleInfoRegistry; + + @GuardedBy("this") + private ComponentInstance currentInstance; + @GuardedBy("this") + private ModuleInfoSnapshot currentSnapshot; + @GuardedBy("this") + private int generation; + + private volatile boolean ignoreScanner = true; + + YangModuleInfoRegistry(final ComponentFactory contextFactory, final YangParserFactory factory) { + this.contextFactory = requireNonNull(contextFactory); + moduleInfoRegistry = new ModuleInfoSnapshotBuilder("binding-dom-codec", factory); + } + + // Invocation from scanner, we may want to ignore this in order to not process partial updates + void scannerUpdate() { + if (!ignoreScanner) { + synchronized (this) { + updateService(); + } + } + } + + synchronized void scannerShutdown() { + ignoreScanner = true; + } + + synchronized void enableScannerAndUpdate() { + ignoreScanner = false; + updateService(); + } + + synchronized void close() { + ignoreScanner = true; + if (currentInstance != null) { + currentInstance.dispose(); + currentInstance = null; + } + } + + List> registerInfos(final List infos) { + return moduleInfoRegistry.registerModuleInfos(infos); + } + + @Holding("this") + private void updateService() { + final ModuleInfoSnapshot newSnapshot; + try { + newSnapshot = moduleInfoRegistry.build(); + } catch (NoSuchElementException e) { + LOG.debug("No snapshot available", e); + return; + } + if (newSnapshot.equals(currentSnapshot)) { + LOG.debug("No update to snapshot"); + return; + } + + + final ComponentInstance newInstance = contextFactory.newInstance( + OSGiEffectiveModelImpl.props(nextGeneration(), newSnapshot)); + if (currentInstance != null) { + currentInstance.dispose(); + } + currentInstance = newInstance; + currentSnapshot = newSnapshot; + } + + @Holding("this") + private long nextGeneration() { + return generation == -1 ? -1 : ++generation; + } +} diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoScanner.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoScanner.java new file mode 100644 index 0000000000..1033e4d909 --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoScanner.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2017, 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.Resources; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.concepts.ObjectRegistration; +import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.Constants; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tracks bundles and attempts to retrieve YangModuleInfo, which is then fed into ModuleInfoRegistry. + */ +final class YangModuleInfoScanner extends BundleTracker>> { + private static final Logger LOG = LoggerFactory.getLogger(YangModuleInfoScanner.class); + // FIXME: this should be in a place shared with maven-sal-api-gen-plugin + private static final String MODULE_INFO_PROVIDER_PATH_PREFIX = "META-INF/services/"; + + private static final String YANG_MODLE_BINDING_PROVIDER_SERVICE = MODULE_INFO_PROVIDER_PATH_PREFIX + + YangModelBindingProvider.class.getName(); + + private final YangModuleInfoRegistry moduleInfoRegistry; + + YangModuleInfoScanner(final BundleContext context, final YangModuleInfoRegistry moduleInfoRegistry) { + super(context, Bundle.RESOLVED | Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE, null); + this.moduleInfoRegistry = requireNonNull(moduleInfoRegistry); + } + + @Override + @SuppressWarnings("checkstyle:illegalCatch") + public List> addingBundle(final Bundle bundle, final BundleEvent event) { + if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) { + LOG.debug("Ignoring system bundle {}", bundle); + return ImmutableList.of(); + } + + final URL resource = bundle.getEntry(YANG_MODLE_BINDING_PROVIDER_SERVICE); + if (resource == null) { + LOG.debug("Bundle {} does not have an entry for {}", bundle, YANG_MODLE_BINDING_PROVIDER_SERVICE); + return ImmutableList.of(); + } + + LOG.debug("Got addingBundle({}) with YangModelBindingProvider resource {}", bundle, resource); + final List lines; + try { + lines = Resources.readLines(resource, StandardCharsets.UTF_8); + } catch (IOException e) { + LOG.error("Error while reading {} from bundle {}", resource, bundle, e); + return ImmutableList.of(); + } + + if (lines.isEmpty()) { + LOG.debug("Bundle {} has empty services for {}", bundle, YANG_MODLE_BINDING_PROVIDER_SERVICE); + return ImmutableList.of(); + } + + final List infos = new ArrayList<>(lines.size()); + for (String moduleInfoName : lines) { + LOG.trace("Retrieve ModuleInfo({}, {})", moduleInfoName, bundle); + final YangModuleInfo moduleInfo; + try { + moduleInfo = retrieveModuleInfo(moduleInfoName, bundle); + } catch (ScanningException e) { + LOG.warn("Failed to acquire {} from bundle {}, ignoring it", moduleInfoName, bundle, e); + continue; + } + + infos.add(moduleInfo); + } + + final List> registrations = moduleInfoRegistry.registerInfos(infos); + LOG.trace("Bundle {} resulted in registrations {}", bundle, registrations); + moduleInfoRegistry.scannerUpdate(); + return registrations; + } + + @Override + public void modifiedBundle(final Bundle bundle, final BundleEvent event, + final List> regs) { + if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) { + LOG.debug("Framework bundle {} got event {}", bundle, event.getType()); + if ((event.getType() & BundleEvent.STOPPING) != 0) { + LOG.info("OSGi framework is being stopped, halting bundle scanning"); + moduleInfoRegistry.scannerShutdown(); + } + } + } + + + @Override + public void removedBundle(final Bundle bundle, final BundleEvent event, + final List> regs) { + regs.forEach(ObjectRegistration::close); + moduleInfoRegistry.scannerUpdate(); + } + + private static YangModuleInfo retrieveModuleInfo(final String className, final Bundle bundle) + throws ScanningException { + final Class loadedClass; + try { + loadedClass = bundle.loadClass(className); + } catch (ClassNotFoundException e) { + throw new ScanningException(e, "Failed to load class %s", className); + } + + final Class providerClass; + try { + providerClass = loadedClass.asSubclass(YangModelBindingProvider.class); + } catch (ClassCastException e) { + throw new ScanningException(e, "Failed to validate %s", loadedClass); + } + + final Constructor ctor; + try { + ctor = providerClass.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new ScanningException(e, "%s does not have a no-argument constructor", providerClass); + } catch (SecurityException e) { + throw new ScanningException(e, "Failed to reflect on %s", providerClass); + } + + YangModelBindingProvider instance; + try { + instance = ctor.newInstance(); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + throw new ScanningException(e, "Failed to instantiate %s", providerClass); + } + + return instance.getModuleInfo(); + } + + @NonNullByDefault + private static final class ScanningException extends Exception { + private static final long serialVersionUID = 1L; + + ScanningException(final Exception cause, final String format, final Object... args) { + super(String.format(format, args), cause); + } + } +} diff --git a/dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntimeTest.java b/dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntimeTest.java new file mode 100644 index 0000000000..da2f5435aa --- /dev/null +++ b/dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntimeTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.schema.osgi.impl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentFactory; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class OSGiModelRuntimeTest { + @Mock + private YangParserFactory parserFactory; + @Mock + private ComponentFactory contextFactory; + @Mock + private BundleContext bundleContext; + + private OSGiModelRuntime target; + + @Before + public void before() { + target = new OSGiModelRuntime(); + target.parserFactory = parserFactory; + target.contextFactory = contextFactory; + } + + @After + public void after() { + verifyNoMoreInteractions(parserFactory); + verifyNoMoreInteractions(contextFactory); + verifyNoMoreInteractions(bundleContext); + } + + @Test + public void testActivate() { + doReturn(new Bundle[0]).when(bundleContext).getBundles(); + doNothing().when(bundleContext).addBundleListener(any()); + target.activate(bundleContext); + } + + @Test + public void testDeactivate() { + testActivate(); + doNothing().when(bundleContext).removeBundleListener(any()); + target.deactivate(); + } +} diff --git a/dom/mdsal-dom-schema-service-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/service/osgi/util/TestModel.java b/dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/TestModel.java similarity index 98% rename from dom/mdsal-dom-schema-service-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/service/osgi/util/TestModel.java rename to dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/TestModel.java index 189e1357e8..f136e4976c 100644 --- a/dom/mdsal-dom-schema-service-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/service/osgi/util/TestModel.java +++ b/dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/TestModel.java @@ -5,7 +5,7 @@ * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.mdsal.dom.schema.service.osgi.util; +package org.opendaylight.mdsal.dom.schema.osgi.impl; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; diff --git a/dom/mdsal-dom-schema-service-osgi/src/test/resources/odl-datastore-test.yang b/dom/mdsal-dom-schema-osgi/src/test/resources/odl-datastore-test.yang similarity index 100% rename from dom/mdsal-dom-schema-service-osgi/src/test/resources/odl-datastore-test.yang rename to dom/mdsal-dom-schema-osgi/src/test/resources/odl-datastore-test.yang diff --git a/dom/mdsal-dom-schema-service-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/service/osgi/OsgiBundleScanningSchemaService.java b/dom/mdsal-dom-schema-service-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/service/osgi/OsgiBundleScanningSchemaService.java deleted file mode 100644 index 727d790ab2..0000000000 --- a/dom/mdsal-dom-schema-service-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/service/osgi/OsgiBundleScanningSchemaService.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.mdsal.dom.schema.service.osgi; - -import static com.google.common.base.Preconditions.checkState; -import static java.util.Objects.requireNonNull; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.checkerframework.checker.lock.qual.GuardedBy; -import org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.mdsal.dom.broker.schema.ScanningSchemaServiceProvider; -import org.opendaylight.yangtools.concepts.Registration; -import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.framework.Constants; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.BundleTracker; -import org.osgi.util.tracker.BundleTrackerCustomizer; -import org.osgi.util.tracker.ServiceTracker; -import org.osgi.util.tracker.ServiceTrackerCustomizer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class OsgiBundleScanningSchemaService extends ScanningSchemaServiceProvider - implements ServiceTrackerCustomizer { - - private static final Logger LOG = LoggerFactory.getLogger(OsgiBundleScanningSchemaService.class); - private static final AtomicReference GLOBAL_INSTANCE = new AtomicReference<>(); - - private final BundleScanner scanner = new BundleScanner(); - private final BundleContext context; - - @GuardedBy("lock") - private BundleTracker> bundleTracker; - private final Object lock = new Object(); - - private ServiceTracker listenerTracker; - private boolean starting = true; - - private volatile boolean stopping; - - private OsgiBundleScanningSchemaService(final BundleContext context) { - this.context = requireNonNull(context); - } - - public static @NonNull OsgiBundleScanningSchemaService createInstance(final BundleContext ctx) { - final OsgiBundleScanningSchemaService instance = new OsgiBundleScanningSchemaService(ctx); - checkState(GLOBAL_INSTANCE.compareAndSet(null, instance)); - instance.start(); - return instance; - } - - private void start() { - checkState(context != null); - LOG.debug("start() starting"); - - listenerTracker = new ServiceTracker<>(context, SchemaContextListener.class, this); - bundleTracker = new BundleTracker<>(context, - Bundle.RESOLVED | Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE, scanner); - - synchronized (lock) { - bundleTracker.open(); - - LOG.debug("BundleTracker.open() complete"); - - if (!hasListeners()) { - tryToUpdateSchemaContext(); - } - } - - listenerTracker.open(); - starting = false; - - LOG.debug("start() complete"); - } - - public static OsgiBundleScanningSchemaService getInstance() { - final OsgiBundleScanningSchemaService instance = GLOBAL_INSTANCE.get(); - checkState(instance != null, "Global Instance was not instantiated"); - return instance; - } - - @VisibleForTesting - public static void destroyInstance() { - final OsgiBundleScanningSchemaService instance = GLOBAL_INSTANCE.getAndSet(null); - if (instance != null) { - instance.closeInstance(); - } - } - - private void closeInstance() { - stopping = true; - if (bundleTracker != null) { - bundleTracker.close(); - bundleTracker = null; - } - if (listenerTracker != null) { - listenerTracker.close(); - listenerTracker = null; - } - close(); - } - - public BundleContext getContext() { - return context; - } - - @SuppressWarnings("checkstyle:IllegalCatch") - private class BundleScanner implements BundleTrackerCustomizer> { - @Override - public Iterable addingBundle(final Bundle bundle, final BundleEvent event) { - - if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) { - return Collections.emptyList(); - } - - final Enumeration enumeration = bundle.findEntries("META-INF/yang", "*.yang", false); - if (enumeration == null) { - return Collections.emptyList(); - } - - final List urls = new ArrayList<>(); - while (enumeration.hasMoreElements()) { - final URL u = enumeration.nextElement(); - try { - urls.add(u); - LOG.debug("Registered {}", u); - } catch (final Exception e) { - LOG.warn("Failed to register {}, ignoring it", u, e); - } - } - - final List registrations = registerAvailableYangs(urls); - if (!registrations.isEmpty()) { - LOG.debug("Loaded {} new URLs from bundle {}, attempting to rebuild schema context", - registrations.size(), bundle.getSymbolicName()); - if (!starting && !stopping) { - tryToUpdateSchemaContext(); - } - } - return ImmutableList.copyOf(registrations); - } - - @Override - public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Iterable object) { - if (bundle.getBundleId() == Constants.SYSTEM_BUNDLE_ID) { - LOG.debug("Framework bundle {} got event {}", bundle, event.getType()); - if ((event.getType() & BundleEvent.STOPPING) != 0) { - LOG.info("OSGi framework is being stopped, halting bundle scanning"); - stopping = true; - } - } - } - - /** - * If removing YANG files makes yang store inconsistent, method {@link #getYangStoreSnapshot()} will - * throw exception. There is no rollback. - */ - @SuppressWarnings("checkstyle:IllegalCatch") - @Override - public void removedBundle(final Bundle bundle, final BundleEvent event, final Iterable urls) { - for (final Registration url : urls) { - try { - url.close(); - } catch (final Exception e) { - LOG.warn("Failed do unregister URL {}, proceeding", url, e); - } - } - - final int numUrls = Iterables.size(urls); - if (numUrls > 0) { - if (LOG.isDebugEnabled()) { - LOG.debug("removedBundle: {}, state: {}, # urls: {}", bundle.getSymbolicName(), bundle.getState(), - numUrls); - } - if (!starting && !stopping) { - tryToUpdateSchemaContext(); - } - } - } - } - - @Override - public SchemaContextListener addingService(final ServiceReference reference) { - final SchemaContextListener listener = context.getService(reference); - registerSchemaContextListener(listener); - return listener; - } - - @Override - public void modifiedService(final ServiceReference reference, - final SchemaContextListener service) { - // NOOP - } - - @Override - public void removedService(final ServiceReference reference, - final SchemaContextListener service) { - context.ungetService(reference); - removeListener(service); - } -} diff --git a/dom/mdsal-dom-schema-service-osgi/src/main/resources/org/opendaylight/blueprint/dom-osgi-schema-service.xml b/dom/mdsal-dom-schema-service-osgi/src/main/resources/org/opendaylight/blueprint/dom-osgi-schema-service.xml deleted file mode 100644 index ae3f347088..0000000000 --- a/dom/mdsal-dom-schema-service-osgi/src/main/resources/org/opendaylight/blueprint/dom-osgi-schema-service.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/dom/mdsal-dom-schema-service-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/service/osgi/OsgiBundleScanningSchemaServiceTest.java b/dom/mdsal-dom-schema-service-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/service/osgi/OsgiBundleScanningSchemaServiceTest.java deleted file mode 100644 index 373a02a345..0000000000 --- a/dom/mdsal-dom-schema-service-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/service/osgi/OsgiBundleScanningSchemaServiceTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.mdsal.dom.schema.service.osgi; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.opendaylight.mdsal.dom.api.DOMSchemaService; -import org.opendaylight.mdsal.dom.schema.service.osgi.util.TestModel; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Filter; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -public class OsgiBundleScanningSchemaServiceTest { - - private OsgiBundleScanningSchemaService osgiService; - private final BundleContext bundleContext = mock(BundleContext.class, "bundleContext"); - - @Before - public void setUp() throws InvalidSyntaxException { - destroyInstance(); - doReturn(mock(Filter.class)).when(bundleContext).createFilter(any()); - doNothing().when(bundleContext).addBundleListener(any()); - doReturn(new Bundle[] {}).when(bundleContext).getBundles(); - doNothing().when(bundleContext).addServiceListener(any(), any()); - doReturn(new ServiceReference[] {}).when(bundleContext).getServiceReferences(anyString(), any()); - doNothing().when(bundleContext).removeBundleListener(any()); - doNothing().when(bundleContext).removeServiceListener(any()); - osgiService = OsgiBundleScanningSchemaService.createInstance(bundleContext); - assertEquals(osgiService, OsgiBundleScanningSchemaService.getInstance()); - assertEquals(bundleContext, osgiService.getContext()); - } - - @SuppressWarnings("checkstyle:IllegalCatch") - @After - public void destroyInstance() { - try { - OsgiBundleScanningSchemaService.getInstance(); - OsgiBundleScanningSchemaService.destroyInstance(); - } catch (final Exception e) { - assertTrue(e instanceof IllegalStateException); - } - } - - @Test - public void basicTest() { - assertTrue(osgiService instanceof DOMSchemaService); - - final SchemaContext schemaContext = TestModel.createTestContext(); - - final SchemaContextListener schemaContextListener = mock(SchemaContextListener.class); - doNothing().when(schemaContextListener).onGlobalContextUpdated(schemaContext); - osgiService.registerSchemaContextListener(schemaContextListener); - - osgiService.notifyListeners(schemaContext); - - doReturn(schemaContextListener).when(bundleContext).getService(null); - assertEquals(schemaContextListener, osgiService.addingService(null)); - - osgiService.registerSchemaContextListener(schemaContextListener); - assertNull(osgiService.getSchemaContext()); - - doReturn(false).when(bundleContext).ungetService(null); - osgiService.removedService(null, null); - verify(bundleContext).ungetService(any()); - - osgiService.close(); - } - - @Test(expected = UnsupportedOperationException.class) - public void sessionContextTest() { - osgiService.getSessionContext(); - } -} diff --git a/dom/pom.xml b/dom/pom.xml index 7cc9f472f5..9231151804 100644 --- a/dom/pom.xml +++ b/dom/pom.xml @@ -29,7 +29,7 @@ mdsal-dom-spi mdsal-dom-broker mdsal-dom-inmemory-datastore - mdsal-dom-schema-service-osgi + mdsal-dom-schema-osgi diff --git a/features/odl-mdsal-binding-runtime-api/pom.xml b/features/odl-mdsal-binding-runtime-api/pom.xml new file mode 100644 index 0000000000..f84fe8b9a6 --- /dev/null +++ b/features/odl-mdsal-binding-runtime-api/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + org.opendaylight.mdsal + feature-parent + 6.0.0-SNAPSHOT + ../feature-parent + + + odl-mdsal-binding-runtime-api + feature + OpenDaylight :: MD-SAL :: Binding Runtime APIs + MD-SAL Java Binding runtime APIs + + + + org.opendaylight.yangtools + odl-yangtools-data + xml + features + + + org.opendaylight.yangtools + odl-yangtools-parser + xml + features + + + org.opendaylight.mdsal + odl-mdsal-binding-base + xml + features + + + org.opendaylight.mdsal + mdsal-binding-generator-api + + + org.opendaylight.mdsal + mdsal-binding-generator-util + + + org.opendaylight.mdsal + mdsal-binding-runtime-api + + + org.opendaylight.mdsal + mdsal-binding-runtime-spi + + + org.opendaylight.mdsal + mdsal-binding-dom-codec-api + + + org.opendaylight.mdsal + mdsal-binding-dom-codec-spi + + + diff --git a/features/odl-mdsal-binding-runtime/pom.xml b/features/odl-mdsal-binding-runtime/pom.xml index 6618dd3cdc..3820001189 100644 --- a/features/odl-mdsal-binding-runtime/pom.xml +++ b/features/odl-mdsal-binding-runtime/pom.xml @@ -41,24 +41,14 @@ org.opendaylight.mdsal - mdsal-binding-generator-api + odl-mdsal-binding-runtime-api + xml + features org.opendaylight.mdsal mdsal-binding-generator-impl - - org.opendaylight.mdsal - mdsal-binding-generator-util - - - org.opendaylight.mdsal - mdsal-binding-dom-codec-api - - - org.opendaylight.mdsal - mdsal-binding-dom-codec-spi - org.opendaylight.mdsal mdsal-binding-dom-codec @@ -73,7 +63,7 @@ org.opendaylight.mdsal - mdsal-binding-dom-codec-osgi + mdsal-binding-runtime-osgi diff --git a/features/odl-mdsal-dom-broker/pom.xml b/features/odl-mdsal-dom-broker/pom.xml index e74b3e4733..93b820ee3e 100644 --- a/features/odl-mdsal-dom-broker/pom.xml +++ b/features/odl-mdsal-dom-broker/pom.xml @@ -45,13 +45,19 @@ xml features + + org.opendaylight.mdsal + odl-mdsal-binding-runtime-api + xml + features + org.opendaylight.mdsal mdsal-dom-broker org.opendaylight.mdsal - mdsal-dom-schema-service-osgi + mdsal-dom-schema-osgi diff --git a/features/pom.xml b/features/pom.xml index 6e54e76c33..005a04c934 100644 --- a/features/pom.xml +++ b/features/pom.xml @@ -33,6 +33,7 @@ odl-mdsal-binding-base odl-mdsal-binding-dom-adapter odl-mdsal-binding-runtime + odl-mdsal-binding-runtime-api odl-mdsal-common