Rework binding component instantiation
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 24 Feb 2020 11:40:56 +0000 (12:40 +0100)
committerAnil Belur <abelur@linuxfoundation.org>
Wed, 19 Jun 2024 00:41:31 +0000 (10:41 +1000)
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 <robert.varga@pantheon.tech>
12 files changed:
binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/BindingClassLoadingStrategy.java [deleted file]
binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/ModuleInfoBundleTracker.java [deleted file]
binding/mdsal-binding-dom-codec-osgi/src/main/java/org/opendaylight/mdsal/binding/dom/codec/osgi/impl/OsgiModuleInfoRegistry.java [deleted file]
binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/binding/runtime/api/ModuleInfoSnapshot.java [new file with mode: 0644]
binding/mdsal-binding-runtime-osgi/pom.xml [moved from binding/mdsal-binding-dom-codec-osgi/pom.xml with 75% similarity]
binding/mdsal-binding-runtime-osgi/src/main/java/org/opendaylight/mdsal/binding/runtime/osgi/impl/BindingRuntimeContextImpl.java [new file with mode: 0644]
binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/AbstractModuleInfoTracker.java [new file with mode: 0644]
binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/BindingRuntimeHelpers.java
binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/DefaultModuleInfoSnapshot.java [new file with mode: 0644]
binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoBackedContext.java
binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoRegistry.java [deleted file]
binding/mdsal-binding-runtime-spi/src/main/java/org/opendaylight/binding/runtime/spi/ModuleInfoSnapshotBuilder.java [new file with mode: 0644]

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 (file)
index d0ee2fa..0000000
+++ /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 (file)
index 4b39f10..0000000
+++ /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<Collection<ObjectRegistration<YangModuleInfo>>> {
-    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<ObjectRegistration<YangModuleInfo>> 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<String> 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<ObjectRegistration<YangModuleInfo>> 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<ObjectRegistration<YangModuleInfo>> object) {
-        // No-op
-    }
-
-    @Override
-    @SuppressWarnings("checkstyle:illegalCatch")
-    public void removedBundle(final Bundle bundle, final BundleEvent event,
-            final Collection<ObjectRegistration<YangModuleInfo>> 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 (file)
index b001583..0000000
+++ /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<YangModuleInfo> 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<YangModuleInfo> registerInfo(final YangModuleInfo yangModuleInfo) {
-        return moduleInfoRegistry.registerModuleInfo(yangModuleInfo);
-    }
-
-    private class ObjectRegistrationWrapper implements ObjectRegistration<YangModuleInfo> {
-        private final ObjectRegistration<YangModuleInfo> inner;
-
-        ObjectRegistrationWrapper(final ObjectRegistration<YangModuleInfo> 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 (file)
index 0000000..5a5d1b2
--- /dev/null
@@ -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<YangTextSchemaSource> {
+
+}
similarity index 75%
rename from binding/mdsal-binding-dom-codec-osgi/pom.xml
rename to binding/mdsal-binding-runtime-osgi/pom.xml
index 7311fa57614e68fa9921c95c274fcf10697cff26..3d941fc4f65208b52d652748afb891228c98912d 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- vi: set et smarttab sw=4 tabstop=4: -->
 <!--
- Copyright (c) 2017 Pantheon Technologies, s.r.o. and others.  All rights reserved.
+ 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,
         <relativePath>../../dom/dom-parent</relativePath>
     </parent>
 
-    <artifactId>mdsal-binding-dom-codec-osgi</artifactId>
+    <artifactId>mdsal-binding-runtime-osgi</artifactId>
     <packaging>bundle</packaging>
 
     <dependencies>
         <dependency>
             <groupId>org.opendaylight.mdsal</groupId>
-            <artifactId>mdsal-binding-dom-codec</artifactId>
-        </dependency>
-
-        <!-- FIXME: MDSAL-392: this is ugly, we should be looking this up,
-                               but then we may want to end up doing something
-                               completely different in thes artifacts -->
-        <dependency>
-            <groupId>org.opendaylight.mdsal</groupId>
-            <artifactId>mdsal-binding-generator-impl</artifactId>
+            <artifactId>mdsal-binding-runtime-spi</artifactId>
         </dependency>
-
         <dependency>
             <groupId>org.opendaylight.mdsal</groupId>
-            <artifactId>mdsal-binding-runtime-spi</artifactId>
+            <artifactId>mdsal-dom-schema-osgi</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>mockito-configuration</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.opendaylight.yangtools</groupId>
-            <artifactId>yang-test-util</artifactId>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-test-model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-generator-impl</artifactId>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
                 <configuration>
+                    <Automatic-Module-Name>org.opendaylight.mdsal.binding.runtime.osgi</Automatic-Module-Name>
                     <instructions>
-                        <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+                        <!-- Karaf cannot handle Factory Component requirements, see https://issues.apache.org/jira/browse/KARAF-6625 -->
+                        <_dsannotations-options>norequirements</_dsannotations-options>
                     </instructions>
                 </configuration>
             </plugin>
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 (file)
index 0000000..ddde762
--- /dev/null
@@ -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 (file)
index 0000000..8c999bf
--- /dev/null
@@ -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.
+ *
+ * <p>
+ * 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<String, AbstractRegisteredModuleInfo> packageToInfoReg =
+            MultimapBuilder.hashKeys().arrayListValues().build();
+    @GuardedBy("this")
+    private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
+            MultimapBuilder.hashKeys().arrayListValues().build();
+    @GuardedBy("this")
+    private @Nullable ModuleInfoSnapshot currentSnapshot;
+
+    AbstractModuleInfoTracker(final YangTextSchemaContextResolver resolver) {
+        this.ctxResolver = requireNonNull(resolver);
+    }
+
+    public final synchronized List<ObjectRegistration<YangModuleInfo>> registerModuleInfos(
+            final Iterable<? extends YangModuleInfo> moduleInfos) {
+        final List<ObjectRegistration<YangModuleInfo>> ret = new ArrayList<>();
+        for (YangModuleInfo yangModuleInfo : moduleInfos) {
+            ret.add(register(requireNonNull(yangModuleInfo)));
+        }
+        return ret;
+    }
+
+    @Holding("this")
+    private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
+        final Builder<ExplicitRegisteredModuleInfo> regBuilder = ImmutableList.builder();
+        for (YangModuleInfo info : flatDependencies(moduleInfo)) {
+            regBuilder.add(registerExplicitModuleInfo(info));
+        }
+        final ImmutableList<ExplicitRegisteredModuleInfo> 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<? extends EffectiveModelContext> getResolverEffectiveModel() {
+        return ctxResolver.getEffectiveModelContext();
+    }
+
+    @Deprecated
+    final ListenableFuture<? extends YangTextSchemaSource> 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<SourceIdentifier> sources = new HashSet<>();
+        for (Entry<QNameModule, ModuleEffectiveStatement> entry : effectiveModel.getModuleStatements().entrySet()) {
+            final Optional<Revision> 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<SourceIdentifier, YangModuleInfo> moduleInfos = new HashMap<>();
+        final Map<String, ClassLoader> classLoaders = new HashMap<>();
+        for (SourceIdentifier source : sources) {
+            final List<AbstractRegisteredModuleInfo> 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<ExplicitRegisteredModuleInfo> 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<AbstractRegisteredModuleInfo> 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<YangModuleInfo> 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<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
+        if (set.add(moduleInfo)) {
+            for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
+                flatDependencies(set, dep);
+            }
+        }
+    }
+}
index b252b2e5cfd4dc52566b0942d387badd608caefc..c5ccd7483540de4eac04844f40abee7ffb8427e6 100644 (file)
@@ -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<? extends YangModuleInfo> 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 (file)
index 0000000..87c2e43
--- /dev/null
@@ -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<SourceIdentifier, YangModuleInfo> moduleInfos;
+    private final ImmutableMap<String, ClassLoader> classLoaders;
+    private final @NonNull EffectiveModelContext effectiveModel;
+
+    DefaultModuleInfoSnapshot(final EffectiveModelContext effectiveModel,
+            final Map<SourceIdentifier, YangModuleInfo> moduleInfos, final Map<String, ClassLoader> classLoaders) {
+        this.effectiveModel = requireNonNull(effectiveModel);
+        this.moduleInfos = ImmutableMap.copyOf(moduleInfos);
+        this.classLoaders = ImmutableMap.copyOf(classLoaders);
+    }
+
+    @Override
+    public EffectiveModelContext getEffectiveModelContext() {
+        return effectiveModel;
+    }
+
+    @Override
+    public ListenableFuture<? extends YangTextSchemaSource> 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);
+    }
+}
index afb2afb3c2a19f71f82ea91c94aea020b7fb5996..31de1aac189b0acabf383db569e23d3c8815fb10 100644 (file)
@@ -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<YangTextSchemaSource> {
+public class ModuleInfoBackedContext extends AbstractModuleInfoTracker implements ClassLoadingStrategy,
+        EffectiveModelContextProvider, SchemaSourceProvider<YangTextSchemaSource> {
     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<ClassLoadingStrategy,
         LoadingCache<ImmutableSet<YangModuleInfo>, ModuleInfoBackedContext>> CONTEXT_CACHES = CacheBuilder.newBuilder()
             .weakKeys().build(new CacheLoader<ClassLoadingStrategy,
@@ -141,24 +66,15 @@ public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
                                 @Override
                                 public ModuleInfoBackedContext load(final Set<YangModuleInfo> key) {
                                     final ModuleInfoBackedContext context = ModuleInfoBackedContext.create(strategy);
-                                    context.addModuleInfos(key);
+                                    context.registerModuleInfos(key);
                                     return context;
                                 }
                             });
                     }
             });
 
-    private final YangTextSchemaContextResolver ctxResolver;
-
-    @GuardedBy("this")
-    private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
-            MultimapBuilder.hashKeys().arrayListValues().build();
-    @GuardedBy("this")
-    private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> 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<YangModuleInfo> 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<? extends YangTextSchemaSource> getSource(final SourceIdentifier sourceIdentifier) {
-        return ctxResolver.getSource(sourceIdentifier);
-    }
-
-    final synchronized void addModuleInfos(final Iterable<? extends YangModuleInfo> 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<? extends EffectiveModelContext> tryToCreateModelContext() {
-        return ctxResolver.getEffectiveModelContext();
+        return getResolverEffectiveModel();
     }
 
     @Holding("this")
-    private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
-        final Builder<ExplicitRegisteredModuleInfo> regBuilder = ImmutableList.builder();
-        for (YangModuleInfo info : flatDependencies(moduleInfo)) {
-            regBuilder.add(registerExplicitModuleInfo(info));
-        }
-        final ImmutableList<ExplicitRegisteredModuleInfo> 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<ExplicitRegisteredModuleInfo> 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<AbstractRegisteredModuleInfo> 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<YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
-        // Flatten the modules being registered, with the triggering module being first...
-        final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
-        flatDependencies(requiredInfos, moduleInfo);
-
-        // ... now reverse the order in an effort to register dependencies first (triggering module last)
-        final List<YangModuleInfo> intendedOrder = new ArrayList<>(requiredInfos);
-        Collections.reverse(intendedOrder);
-
-        return intendedOrder;
-    }
-
-    private static void flatDependencies(final Set<YangModuleInfo> 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 (file)
index 8def478..0000000
+++ /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<YangModuleInfo> 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 (file)
index 0000000..96b38a2
--- /dev/null
@@ -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<ModuleInfoSnapshot, NoSuchElementException> {
+
+    public ModuleInfoSnapshotBuilder(final String name, final YangParserFactory parserFactory) {
+        super(YangTextSchemaContextResolver.create(name, parserFactory));
+    }
+
+    @Override
+    public synchronized ModuleInfoSnapshot build() {
+        return updateSnapshot();
+    }
+}