BUG-8327: Introduce DOMYangTextSourceProvider and implement it
[mdsal.git] / dom / mdsal-dom-broker / src / main / java / org / opendaylight / mdsal / dom / broker / osgi / OsgiBundleScanningSchemaService.java
index 1ff49c0186dea19f588aae21511d9025d2a1d885..87e5c4d23e4e770fef87883f68188b1526b5df0d 100644 (file)
@@ -13,20 +13,31 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.CheckedFuture;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaServiceExtension;
+import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.util.ListenerRegistry;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
-import org.opendaylight.yangtools.yang.parser.repo.URLSchemaContextResolver;
+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.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
@@ -38,41 +49,50 @@ import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class OsgiBundleScanningSchemaService implements SchemaContextProvider, DOMSchemaService, ServiceTrackerCustomizer<SchemaContextListener, SchemaContextListener>, AutoCloseable {
+public class OsgiBundleScanningSchemaService implements SchemaContextProvider, DOMSchemaService,
+        ServiceTrackerCustomizer<SchemaContextListener, SchemaContextListener>, DOMYangTextSourceProvider,
+        AutoCloseable {
     private static final Logger LOG = LoggerFactory.getLogger(OsgiBundleScanningSchemaService.class);
 
+    private static final AtomicReference<OsgiBundleScanningSchemaService> GLOBAL_INSTANCE = new AtomicReference<>();
+
+    private static final long FRAMEWORK_BUNDLE_ID = 0;
+
+    @GuardedBy("lock")
     private final ListenerRegistry<SchemaContextListener> listeners = new ListenerRegistry<>();
-    private final URLSchemaContextResolver contextResolver = URLSchemaContextResolver.create("global-bundle");
+    private final YangTextSchemaContextResolver contextResolver = YangTextSchemaContextResolver.create("global-bundle");
     private final BundleScanner scanner = new BundleScanner();
+    private final Object lock = new Object();
     private final BundleContext context;
 
     private ServiceTracker<SchemaContextListener, SchemaContextListener> listenerTracker;
     private BundleTracker<Iterable<Registration>> bundleTracker;
     private boolean starting = true;
-    private static OsgiBundleScanningSchemaService instance;
+
+    private volatile boolean stopping;
 
     private OsgiBundleScanningSchemaService(final BundleContext context) {
         this.context = Preconditions.checkNotNull(context);
     }
 
-    public synchronized static OsgiBundleScanningSchemaService createInstance(final BundleContext ctx) {
-        Preconditions.checkState(instance == null);
-        instance = new OsgiBundleScanningSchemaService(ctx);
+    public static @Nonnull OsgiBundleScanningSchemaService createInstance(final BundleContext ctx) {
+        OsgiBundleScanningSchemaService instance = new OsgiBundleScanningSchemaService(ctx);
+        Preconditions.checkState(GLOBAL_INSTANCE.compareAndSet(null, instance));
         instance.start();
         return instance;
     }
 
-    public synchronized static OsgiBundleScanningSchemaService getInstance() {
+    public static OsgiBundleScanningSchemaService getInstance() {
+        OsgiBundleScanningSchemaService instance = GLOBAL_INSTANCE.get();
         Preconditions.checkState(instance != null, "Global Instance was not instantiated");
         return instance;
     }
 
     @VisibleForTesting
-    public static synchronized void destroyInstance() {
-        try {
+    public static void destroyInstance() {
+        OsgiBundleScanningSchemaService instance = GLOBAL_INSTANCE.getAndSet(null);
+        if (instance != null) {
             instance.close();
-        } finally {
-            instance = null;
         }
     }
 
@@ -80,20 +100,26 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
         return context;
     }
 
-    public void start() {
+    private void start() {
         checkState(context != null);
         LOG.debug("start() starting");
 
-        listenerTracker = new ServiceTracker<>(context, SchemaContextListener.class, OsgiBundleScanningSchemaService.this);
-        bundleTracker = new BundleTracker<>(context, Bundle.RESOLVED | Bundle.STARTING |
-                Bundle.STOPPING | Bundle.ACTIVE, scanner);
-        bundleTracker.open();
+        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");
+            LOG.debug("BundleTracker.open() complete");
+
+            if (Iterables.size(listeners.getListeners()) > 0) {
+                tryToUpdateSchemaContext();
+            }
+        }
 
         listenerTracker.open();
         starting = false;
-        tryToUpdateSchemaContext();
 
         LOG.debug("start() complete");
     }
@@ -114,29 +140,40 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
     }
 
     @Override
-    public synchronized ListenerRegistration<SchemaContextListener> registerSchemaContextListener(final SchemaContextListener listener) {
-        final Optional<SchemaContext> potentialCtx = contextResolver.getSchemaContext();
-        if(potentialCtx.isPresent()) {
-            listener.onGlobalContextUpdated(potentialCtx.get());
+    public ListenerRegistration<SchemaContextListener> registerSchemaContextListener(
+            final SchemaContextListener listener) {
+        synchronized (lock) {
+            final Optional<SchemaContext> potentialCtx = contextResolver.getSchemaContext();
+            if (potentialCtx.isPresent()) {
+                listener.onGlobalContextUpdated(potentialCtx.get());
+            }
+            return listeners.register(listener);
         }
-        return listeners.register(listener);
     }
 
     @Override
     public void close() {
-        if (bundleTracker != null) {
-            bundleTracker.close();
-        }
-        if (listenerTracker != null) {
-            listenerTracker.close();
-        }
+        synchronized (lock) {
+            stopping = true;
+            if (bundleTracker != null) {
+                bundleTracker.close();
+                bundleTracker = null;
+            }
+            if (listenerTracker != null) {
+                listenerTracker.close();
+                listenerTracker = null;
+            }
 
-        for (final ListenerRegistration<SchemaContextListener> l : listeners.getListeners()) {
-            l.close();
+            for (final ListenerRegistration<SchemaContextListener> l : listeners.getListeners()) {
+                l.close();
+            }
         }
     }
 
-    private synchronized void updateContext(final SchemaContext snapshot) {
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    @VisibleForTesting
+    @GuardedBy("lock")
+    void notifyListeners(final SchemaContext snapshot) {
         final Object[] services = listenerTracker.getServices();
         for (final ListenerRegistration<SchemaContextListener> listener : listeners) {
             try {
@@ -157,11 +194,12 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
         }
     }
 
+    @SuppressWarnings("checkstyle:IllegalCatch")
     private class BundleScanner implements BundleTrackerCustomizer<Iterable<Registration>> {
         @Override
         public Iterable<Registration> addingBundle(final Bundle bundle, final BundleEvent event) {
 
-            if (bundle.getBundleId() == 0) {
+            if (bundle.getBundleId() == FRAMEWORK_BUNDLE_ID) {
                 return Collections.emptyList();
             }
 
@@ -192,6 +230,13 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
 
         @Override
         public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> object) {
+            if (bundle.getBundleId() == FRAMEWORK_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;
+                }
+            }
         }
 
         /**
@@ -199,9 +244,9 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
          * {@link #getYangStoreSnapshot()} will throw exception. There is no
          * rollback.
          */
-
+        @SuppressWarnings("checkstyle:IllegalCatch")
         @Override
-        public synchronized void removedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> urls) {
+        public void removedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> urls) {
             for (final Registration url : urls) {
                 try {
                     url.close();
@@ -211,9 +256,10 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
             }
 
             final int numUrls = Iterables.size(urls);
-            if(numUrls > 0 ) {
-                if(LOG.isDebugEnabled()) {
-                    LOG.debug("removedBundle: {}, state: {}, # urls: {}", bundle.getSymbolicName(), bundle.getState(), numUrls);
+            if (numUrls > 0 ) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("removedBundle: {}, state: {}, # urls: {}", bundle.getSymbolicName(),
+                            bundle.getState(), numUrls);
                 }
 
                 tryToUpdateSchemaContext();
@@ -222,7 +268,7 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
     }
 
     @Override
-    public synchronized SchemaContextListener addingService(final ServiceReference<SchemaContextListener> reference) {
+    public SchemaContextListener addingService(final ServiceReference<SchemaContextListener> reference) {
 
         final SchemaContextListener listener = context.getService(reference);
         final SchemaContext _ctxContext = getGlobalContext();
@@ -232,27 +278,43 @@ public class OsgiBundleScanningSchemaService implements SchemaContextProvider, D
         return listener;
     }
 
-    public synchronized void tryToUpdateSchemaContext() {
-        if (starting) {
+    public void tryToUpdateSchemaContext() {
+        if (starting || stopping) {
             return;
         }
-        final Optional<SchemaContext> schema = contextResolver.getSchemaContext();
-        if(schema.isPresent()) {
-            if(LOG.isDebugEnabled()) {
-                LOG.debug("Got new SchemaContext: # of modules {}", schema.get().getAllModuleIdentifiers().size());
-            }
 
-            updateContext(schema.get());
+        synchronized (lock) {
+            final Optional<SchemaContext> schema = contextResolver.getSchemaContext();
+            if (schema.isPresent()) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Got new SchemaContext: # of modules {}", schema.get().getAllModuleIdentifiers().size());
+                }
+
+                notifyListeners(schema.get());
+            }
         }
     }
 
     @Override
-    public void modifiedService(final ServiceReference<SchemaContextListener> reference, final SchemaContextListener service) {
+    public void modifiedService(final ServiceReference<SchemaContextListener> reference,
+            final SchemaContextListener service) {
         // NOOP
     }
 
     @Override
-    public void removedService(final ServiceReference<SchemaContextListener> reference, final SchemaContextListener service) {
+    public void removedService(final ServiceReference<SchemaContextListener> reference,
+            final SchemaContextListener service) {
         context.ungetService(reference);
     }
+
+    @Override
+    public Map<Class<? extends DOMSchemaServiceExtension>, DOMSchemaServiceExtension> getSupportedExtensions() {
+        return ImmutableMap.of(DOMYangTextSourceProvider.class, this);
+    }
+
+    @Override
+    public CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> getSource(
+            final SourceIdentifier sourceIdentifier) {
+        return contextResolver.getSource(sourceIdentifier);
+    }
 }