Introduced advanced strategy for parsing of YANG schemas
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / sal / dom / broker / SchemaServiceImpl.java
index 531f9e86c2dd76f55116a46a8d347d9b2daa1f6a..428521b72f28bfbb719a3bd0fe4f214e5766b46f 100644 (file)
@@ -1,67 +1,60 @@
+/*
+ * 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.controller.sal.dom.broker;
 
-import java.io.IOException;
-import java.io.InputStream;
+import static com.google.common.base.Preconditions.checkState;
+
 import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Enumeration;
-import java.util.List;
-import java.util.Set;
-import java.util.zip.Checksum;
 
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-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.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.controller.sal.core.api.model.SchemaServiceListener;
+import org.opendaylight.controller.sal.dom.broker.impl.SchemaContextProvider;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.concepts.util.ListenerRegistry;
 import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.parser.api.YangModelParser;
-import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.util.URLSchemaContextResolver;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
 import org.osgi.framework.ServiceReference;
-import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.concepts.util.ListenerRegistry;
-import org.opendaylight.controller.sal.core.api.model.SchemaService;
-import org.opendaylight.controller.sal.core.api.model.SchemaServiceListener;
+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;
 
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import static com.google.common.base.Preconditions.*;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
 
 public class SchemaServiceImpl implements //
-SchemaService, //
-ServiceTrackerCustomizer<SchemaServiceListener, SchemaServiceListener>, //
-AutoCloseable {
+        SchemaContextProvider, //
+        SchemaService, //
+        ServiceTrackerCustomizer<SchemaServiceListener, SchemaServiceListener>, //
+        AutoCloseable {
     private static final Logger logger = LoggerFactory.getLogger(SchemaServiceImpl.class);
 
     private ListenerRegistry<SchemaServiceListener> listeners;
-    private YangModelParser parser;
-
+    
     private BundleContext context;
     private BundleScanner scanner = new BundleScanner();
 
-    /**
-     * Map of currently problematic yang files that should get fixed eventually
-     * after all events are received.
-     */
-    private final Multimap<Bundle, URL> inconsistentBundlesToYangURLs = HashMultimap.create();
-    private final Multimap<Bundle, URL> consistentBundlesToYangURLs = HashMultimap.create();
-    private BundleTracker<Object> bundleTracker;
-    private final YangStoreCache cache = new YangStoreCache();
+    private BundleTracker<ImmutableSet<Registration<URL>>> bundleTracker;
+
+    private final URLSchemaContextResolver contextResolver = new URLSchemaContextResolver();
 
-    private ServiceTracker<SchemaServiceListener,SchemaServiceListener> listenerTracker;
+    private ServiceTracker<SchemaServiceListener, SchemaServiceListener> listenerTracker;
+
+    private boolean starting = true;
 
     public ListenerRegistry<SchemaServiceListener> getListeners() {
         return listeners;
@@ -71,14 +64,6 @@ AutoCloseable {
         this.listeners = listeners;
     }
 
-    public YangModelParser getParser() {
-        return parser;
-    }
-
-    public void setParser(YangModelParser parser) {
-        this.parser = parser;
-    }
-
     public BundleContext getContext() {
         return context;
     }
@@ -88,123 +73,70 @@ AutoCloseable {
     }
 
     public void start() {
-        checkState(parser != null);
         checkState(context != null);
         if (listeners == null) {
             listeners = new ListenerRegistry<>();
         }
-        
-        listenerTracker = new ServiceTracker<>(context, SchemaServiceListener.class, this);
-        bundleTracker = new BundleTracker<Object>(context, BundleEvent.RESOLVED | BundleEvent.UNRESOLVED, scanner);
+
+        listenerTracker = new ServiceTracker<>(context, SchemaServiceListener.class, SchemaServiceImpl.this);
+        bundleTracker = new BundleTracker<ImmutableSet<Registration<URL>>>(context, BundleEvent.RESOLVED
+                        | BundleEvent.UNRESOLVED, scanner);
         bundleTracker.open();
         listenerTracker.open();
+        starting = false;
+        tryToUpdateSchemaContext();
     }
 
-    public SchemaContext getGlobalContext() {
-        return getSchemaContextSnapshot();
+    @Override
+    public SchemaContext getSchemaContext() {
+        return getGlobalContext();
     }
 
-    public synchronized SchemaContext getSchemaContextSnapshot() {
-        Optional<SchemaContext> yangStoreOpt = cache.getCachedSchemaContext(consistentBundlesToYangURLs);
-        if (yangStoreOpt.isPresent()) {
-            return yangStoreOpt.get();
-        }
-        SchemaContext snapshot = createSnapshot(parser, consistentBundlesToYangURLs);
-        updateCache(snapshot);
-        return snapshot;
+    public SchemaContext getGlobalContext() {
+        return contextResolver.getSchemaContext().orNull();
     }
 
     @Override
     public void addModule(Module module) {
-        // TODO Auto-generated method stub
         throw new UnsupportedOperationException();
     }
 
     @Override
     public SchemaContext getSessionContext() {
-        // TODO Auto-generated method stub
         throw new UnsupportedOperationException();
     }
 
     @Override
     public void removeModule(Module module) {
-        // TODO Auto-generated method stub
         throw new UnsupportedOperationException();
     }
 
-    
     @Override
     public ListenerRegistration<SchemaServiceListener> registerSchemaServiceListener(SchemaServiceListener listener) {
         return listeners.register(listener);
     }
-    
+
     @Override
     public void close() throws Exception {
-        bundleTracker.close();
-        // FIXME: Add listeners.close();
-
-    }
-
-    private synchronized boolean tryToUpdateState(Collection<URL> changedURLs, Multimap<Bundle, URL> proposedNewState,
-            boolean adding) {
-        Preconditions.checkArgument(changedURLs.size() > 0, "No change can occur when no URLs are changed");
-
-        try {
-            // consistent state
-            // merge into
-            SchemaContext snapshot = createSnapshot(parser, proposedNewState);
-            consistentBundlesToYangURLs.clear();
-            consistentBundlesToYangURLs.putAll(proposedNewState);
-            inconsistentBundlesToYangURLs.clear();
-            // update cache
-            updateCache(snapshot);
-            logger.info("SchemaService updated to new consistent state");
-            logger.trace("SchemaService  updated to new consistent state containing {}", consistentBundlesToYangURLs);
-
-            // notifyListeners(changedURLs, adding);
-            return true;
-        } catch (Exception e) {
-            // inconsistent state
-            logger.debug(
-                    "SchemaService is falling back on last consistent state containing {}, inconsistent yang files {}, reason {}",
-                    consistentBundlesToYangURLs, inconsistentBundlesToYangURLs, e.toString());
-            return false;
+        if (bundleTracker != null) {
+            bundleTracker.close();
         }
+        if (listenerTracker != null) {
+            listenerTracker.close();
+        }
+        // FIXME: Add listeners.close();
     }
 
-    private static Collection<InputStream> fromUrlsToInputStreams(Multimap<Bundle, URL> multimap) {
-        return Collections2.transform(multimap.values(), new Function<URL, InputStream>() {
-
-            @Override
-            public InputStream apply(URL url) {
-                try {
-                    return url.openStream();
-                } catch (IOException e) {
-                    logger.warn("Unable to open stream from {}", url);
-                    throw new IllegalStateException("Unable to open stream from " + url, e);
-                }
-            }
-        });
-    }
-
-    private static SchemaContext createSnapshot(YangModelParser parser, Multimap<Bundle, URL> multimap) {
-        List<InputStream> models = new ArrayList<>(fromUrlsToInputStreams(multimap));
-        Set<Module> modules = parser.parseYangModelsFromStreams(models);
-        SchemaContext yangStoreSnapshot = parser.resolveSchemaContext(modules);
-        return yangStoreSnapshot;
-    }
 
-    private void updateCache(SchemaContext snapshot) {
-        cache.cacheYangStore(consistentBundlesToYangURLs, snapshot);
-        
+    private void updateContext(SchemaContext snapshot) {
         Object[] services = listenerTracker.getServices();
-        if(services != null) {
-            for(Object rawListener : services) {
+        if (services != null) {
+            for (Object rawListener : services) {
                 SchemaServiceListener listener = (SchemaServiceListener) rawListener;
                 try {
                     listener.onGlobalContextUpdated(snapshot);
                 } catch (Exception e) {
-                    logger.error("Exception occured during invoking listener",e);
+                    logger.error("Exception occured during invoking listener", e);
                 }
             }
         }
@@ -212,48 +144,35 @@ AutoCloseable {
             try {
                 listener.getInstance().onGlobalContextUpdated(snapshot);
             } catch (Exception e) {
-                logger.error("Exception occured during invoking listener",e);
+                logger.error("Exception occured during invoking listener", e);
             }
         }
     }
 
-    private class BundleScanner implements BundleTrackerCustomizer<Object> {
+    private class BundleScanner implements BundleTrackerCustomizer<ImmutableSet<Registration<URL>>> {
         @Override
-        public Object addingBundle(Bundle bundle, BundleEvent event) {
+        public ImmutableSet<Registration<URL>> addingBundle(Bundle bundle, BundleEvent event) {
 
-            // Ignore system bundle:
-            // system bundle might have config-api on classpath &&
-            // config-api contains yang files =>
-            // system bundle might contain yang files from that bundle
-            if (bundle.getBundleId() == 0)
-                return bundle;
+            if (bundle.getBundleId() == 0) {
+                return ImmutableSet.of();
+            }
 
             Enumeration<URL> enumeration = bundle.findEntries("META-INF/yang", "*.yang", false);
-            if (enumeration != null && enumeration.hasMoreElements()) {
-                synchronized (this) {
-                    List<URL> addedURLs = new ArrayList<>();
-                    while (enumeration.hasMoreElements()) {
-                        URL url = enumeration.nextElement();
-                        addedURLs.add(url);
-                    }
-                    logger.trace("Bundle {} has event {}, bundle state {}, URLs {}", bundle, event, bundle.getState(),
-                            addedURLs);
-                    // test that yang store is consistent
-                    Multimap<Bundle, URL> proposedNewState = HashMultimap.create(consistentBundlesToYangURLs);
-                    proposedNewState.putAll(inconsistentBundlesToYangURLs);
-                    proposedNewState.putAll(bundle, addedURLs);
-                    boolean adding = true;
-                    
-                    if (tryToUpdateState(addedURLs, proposedNewState, adding) == false) {
-                        inconsistentBundlesToYangURLs.putAll(bundle, addedURLs);
-                    }
-                }
+            Builder<Registration<URL>> builder = ImmutableSet.<Registration<URL>> builder();
+            while (enumeration != null && enumeration.hasMoreElements()) {
+                Registration<URL> reg = contextResolver.registerSource(enumeration.nextElement());
+                builder.add(reg);
+            }
+            ImmutableSet<Registration<URL>> urls = builder.build();
+            if(urls.isEmpty()) {
+                return urls;
             }
-            return bundle;
+            tryToUpdateSchemaContext();
+            return urls;
         }
 
         @Override
-        public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
+        public void modifiedBundle(Bundle bundle, BundleEvent event, ImmutableSet<Registration<URL>> object) {
             logger.debug("Modified bundle {} {} {}", bundle, event, object);
         }
 
@@ -264,60 +183,44 @@ AutoCloseable {
          */
 
         @Override
-        public synchronized void removedBundle(Bundle bundle, BundleEvent event, Object object) {
-            inconsistentBundlesToYangURLs.removeAll(bundle);
-            Collection<URL> consistentURLsToBeRemoved = consistentBundlesToYangURLs.removeAll(bundle);
-
-            if (consistentURLsToBeRemoved.isEmpty()) {
-                return; // no change
+        public synchronized void removedBundle(Bundle bundle, BundleEvent event, ImmutableSet<Registration<URL>> urls) {
+            for (Registration<URL> url : urls) {
+                try {
+                    url.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
             }
-            boolean adding = false;
-            // notifyListeners(consistentURLsToBeRemoved, adding);
+            tryToUpdateSchemaContext();
         }
     }
 
-    private static final class YangStoreCache {
-
-        Set<URL> cachedUrls;
-        SchemaContext cachedContextSnapshot;
-
-        Optional<SchemaContext> getCachedSchemaContext(Multimap<Bundle, URL> bundlesToYangURLs) {
-            Set<URL> urls = setFromMultimapValues(bundlesToYangURLs);
-            if (cachedUrls != null && cachedUrls.equals(urls)) {
-                Preconditions.checkState(cachedContextSnapshot != null);
-                return Optional.of(cachedContextSnapshot);
-            }
-            return Optional.absent();
-        }
-
-        private static Set<URL> setFromMultimapValues(Multimap<Bundle, URL> bundlesToYangURLs) {
-            Set<URL> urls = Sets.newHashSet(bundlesToYangURLs.values());
-            Preconditions.checkState(bundlesToYangURLs.size() == urls.size());
-            return urls;
-        }
-
-        void cacheYangStore(Multimap<Bundle, URL> urls, SchemaContext ctx) {
-            this.cachedUrls = setFromMultimapValues(urls);
-            this.cachedContextSnapshot = ctx;
-        }
-    }
-    
     @Override
     public SchemaServiceListener addingService(ServiceReference<SchemaServiceListener> reference) {
-        
+
         SchemaServiceListener listener = context.getService(reference);
         SchemaContext _ctxContext = getGlobalContext();
-        if(getContext() != null) {
+        if (getContext() != null) {
             listener.onGlobalContextUpdated(_ctxContext);
         }
         return listener;
     }
-    
+
+    public synchronized void tryToUpdateSchemaContext() {
+        if(starting ) {
+            return;
+        }
+        Optional<SchemaContext> schema = contextResolver.tryToUpdateSchemaContext();
+        if(schema.isPresent()) {
+            updateContext(schema.get());
+        }
+    }
+
     @Override
     public void modifiedService(ServiceReference<SchemaServiceListener> reference, SchemaServiceListener service) {
         // NOOP
     }
-    
+
     @Override
     public void removedService(ServiceReference<SchemaServiceListener> reference, SchemaServiceListener service) {
         context.ungetService(reference);