Reduce SchemaContext updates on shutdown
[controller.git] / opendaylight / md-sal / sal-dom-broker / src / main / java / org / opendaylight / controller / sal / dom / broker / GlobalBundleScanningSchemaServiceImpl.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.sal.dom.broker;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.Iterables;
16 import java.net.URL;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Enumeration;
20 import java.util.List;
21 import org.opendaylight.controller.sal.core.api.model.SchemaService;
22 import org.opendaylight.yangtools.concepts.ListenerRegistration;
23 import org.opendaylight.yangtools.concepts.Registration;
24 import org.opendaylight.yangtools.util.ListenerRegistry;
25 import org.opendaylight.yangtools.yang.model.api.Module;
26 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
27 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
28 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
29 import org.opendaylight.yangtools.yang.parser.repo.URLSchemaContextResolver;
30 import org.osgi.framework.Bundle;
31 import org.osgi.framework.BundleContext;
32 import org.osgi.framework.BundleEvent;
33 import org.osgi.framework.ServiceReference;
34 import org.osgi.util.tracker.BundleTracker;
35 import org.osgi.util.tracker.BundleTrackerCustomizer;
36 import org.osgi.util.tracker.ServiceTracker;
37 import org.osgi.util.tracker.ServiceTrackerCustomizer;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvider, SchemaService, ServiceTrackerCustomizer<SchemaContextListener, SchemaContextListener>, AutoCloseable {
42     private static final Logger LOG = LoggerFactory.getLogger(GlobalBundleScanningSchemaServiceImpl.class);
43
44     private final ListenerRegistry<SchemaContextListener> listeners = new ListenerRegistry<>();
45     private final URLSchemaContextResolver contextResolver = URLSchemaContextResolver.create("global-bundle");
46     private final BundleScanner scanner = new BundleScanner();
47     private final BundleContext context;
48
49     private ServiceTracker<SchemaContextListener, SchemaContextListener> listenerTracker;
50     private BundleTracker<Iterable<Registration>> bundleTracker;
51     private boolean starting = true;
52     private static GlobalBundleScanningSchemaServiceImpl instance;
53
54     private GlobalBundleScanningSchemaServiceImpl(final BundleContext context) {
55         this.context = Preconditions.checkNotNull(context);
56     }
57
58     public synchronized static GlobalBundleScanningSchemaServiceImpl createInstance(final BundleContext ctx) {
59         Preconditions.checkState(instance == null);
60         instance = new GlobalBundleScanningSchemaServiceImpl(ctx);
61         instance.start();
62         return instance;
63     }
64
65     public synchronized static GlobalBundleScanningSchemaServiceImpl getInstance() {
66         Preconditions.checkState(instance != null, "Global Instance was not instantiated");
67         return instance;
68     }
69
70     @VisibleForTesting
71     public static synchronized void destroyInstance() {
72         try {
73             instance.close();
74         } finally {
75             instance = null;
76         }
77     }
78
79     public BundleContext getContext() {
80         return context;
81     }
82
83     public void start() {
84         checkState(context != null);
85         LOG.debug("start() starting");
86
87         listenerTracker = new ServiceTracker<>(context, SchemaContextListener.class, GlobalBundleScanningSchemaServiceImpl.this);
88         bundleTracker = new BundleTracker<>(context, Bundle.RESOLVED | Bundle.STARTING |
89                 Bundle.STOPPING | Bundle.ACTIVE, scanner);
90         bundleTracker.open();
91
92         LOG.debug("BundleTracker.open() complete");
93
94         listenerTracker.open();
95         starting = false;
96         tryToUpdateSchemaContext();
97
98         LOG.debug("start() complete");
99     }
100
101     @Override
102     public SchemaContext getSchemaContext() {
103         return getGlobalContext();
104     }
105
106     @Override
107     public SchemaContext getGlobalContext() {
108         return contextResolver.getSchemaContext().orNull();
109     }
110
111     @Override
112     public void addModule(final Module module) {
113         throw new UnsupportedOperationException();
114     }
115
116     @Override
117     public SchemaContext getSessionContext() {
118         throw new UnsupportedOperationException();
119     }
120
121     @Override
122     public void removeModule(final Module module) {
123         throw new UnsupportedOperationException();
124     }
125
126     @Override
127     public synchronized ListenerRegistration<SchemaContextListener> registerSchemaContextListener(final SchemaContextListener listener) {
128         Optional<SchemaContext> potentialCtx = contextResolver.getSchemaContext();
129         if(potentialCtx.isPresent()) {
130             listener.onGlobalContextUpdated(potentialCtx.get());
131         }
132         return listeners.register(listener);
133     }
134
135     @Override
136     public void close() {
137         if (bundleTracker != null) {
138             bundleTracker.close();
139         }
140         if (listenerTracker != null) {
141             listenerTracker.close();
142         }
143
144         for (ListenerRegistration<SchemaContextListener> l : listeners.getListeners()) {
145             l.close();
146         }
147     }
148
149     private synchronized void updateContext(final SchemaContext snapshot) {
150         Object[] services = listenerTracker.getServices();
151         for (ListenerRegistration<SchemaContextListener> listener : listeners) {
152             try {
153                 listener.getInstance().onGlobalContextUpdated(snapshot);
154             } catch (Exception e) {
155                 LOG.error("Exception occured during invoking listener", e);
156             }
157         }
158         if (services != null) {
159             for (Object rawListener : services) {
160                 final SchemaContextListener listener = (SchemaContextListener) rawListener;
161                 try {
162                     listener.onGlobalContextUpdated(snapshot);
163                 } catch (Exception e) {
164                     LOG.error("Exception occured during invoking listener {}", listener, e);
165                 }
166             }
167         }
168     }
169
170     private class BundleScanner implements BundleTrackerCustomizer<Iterable<Registration>> {
171         @Override
172         public Iterable<Registration> addingBundle(final Bundle bundle, final BundleEvent event) {
173
174             if (bundle.getBundleId() == 0) {
175                 return Collections.emptyList();
176             }
177
178             final Enumeration<URL> enumeration = bundle.findEntries("META-INF/yang", "*.yang", false);
179             if (enumeration == null) {
180                 return Collections.emptyList();
181             }
182
183             final List<Registration> urls = new ArrayList<>();
184             while (enumeration.hasMoreElements()) {
185                 final URL u = enumeration.nextElement();
186                 try {
187                     urls.add(contextResolver.registerSource(u));
188                     LOG.debug("Registered {}", u);
189                 } catch (Exception e) {
190                     LOG.warn("Failed to register {}, ignoring it", e);
191                 }
192             }
193
194             if (!urls.isEmpty()) {
195                 LOG.debug("Loaded {} new URLs from bundle {}, attempting to rebuild schema context",
196                         urls.size(), bundle.getSymbolicName());
197                 tryToUpdateSchemaContext();
198             }
199
200             return ImmutableList.copyOf(urls);
201         }
202
203         @Override
204         public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> object) {
205         }
206
207         /**
208          * If removing YANG files makes yang store inconsistent, method
209          * {@link #getYangStoreSnapshot()} will throw exception. There is no
210          * rollback.
211          */
212
213         @Override
214         public synchronized void removedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> urls) {
215             for (Registration url : urls) {
216                 try {
217                     url.close();
218                 } catch (Exception e) {
219                     LOG.warn("Failed do unregister URL {}, proceeding", url, e);
220                 }
221             }
222
223             int numUrls = Iterables.size(urls);
224             if(numUrls > 0 ) {
225                 if(LOG.isDebugEnabled()) {
226                     LOG.debug("removedBundle: {}, state: {}, # urls: {}", bundle.getSymbolicName(), bundle.getState(), numUrls);
227                 }
228
229                 tryToUpdateSchemaContext();
230             }
231         }
232     }
233
234     @Override
235     public synchronized SchemaContextListener addingService(final ServiceReference<SchemaContextListener> reference) {
236
237         SchemaContextListener listener = context.getService(reference);
238         SchemaContext _ctxContext = getGlobalContext();
239         if (getContext() != null && _ctxContext != null) {
240             listener.onGlobalContextUpdated(_ctxContext);
241         }
242         return listener;
243     }
244
245     public synchronized void tryToUpdateSchemaContext() {
246         if (starting) {
247             return;
248         }
249         Optional<SchemaContext> schema = contextResolver.getSchemaContext();
250         if(schema.isPresent()) {
251             if(LOG.isDebugEnabled()) {
252                 LOG.debug("Got new SchemaContext: # of modules {}", schema.get().getAllModuleIdentifiers().size());
253             }
254
255             updateContext(schema.get());
256         }
257     }
258
259     @Override
260     public void modifiedService(final ServiceReference<SchemaContextListener> reference, final SchemaContextListener service) {
261         // NOOP
262     }
263
264     @Override
265     public void removedService(final ServiceReference<SchemaContextListener> reference, final SchemaContextListener service) {
266         context.ungetService(reference);
267     }
268 }