4ba62e4fc40fe62a6c7c4a68fe1d77b12e1f4475
[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 com.google.common.util.concurrent.CheckedFuture;
17 import java.net.URL;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Enumeration;
21 import java.util.List;
22 import java.util.concurrent.atomic.AtomicReference;
23 import javax.annotation.concurrent.GuardedBy;
24 import org.opendaylight.controller.sal.core.api.model.SchemaService;
25 import org.opendaylight.controller.sal.core.api.model.YangTextSourceProvider;
26 import org.opendaylight.yangtools.concepts.ListenerRegistration;
27 import org.opendaylight.yangtools.concepts.Registration;
28 import org.opendaylight.yangtools.util.ListenerRegistry;
29 import org.opendaylight.yangtools.yang.model.api.Module;
30 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
31 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
32 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
33 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
34 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
35 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
36 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
37 import org.osgi.framework.Bundle;
38 import org.osgi.framework.BundleContext;
39 import org.osgi.framework.BundleEvent;
40 import org.osgi.framework.ServiceReference;
41 import org.osgi.util.tracker.BundleTracker;
42 import org.osgi.util.tracker.BundleTrackerCustomizer;
43 import org.osgi.util.tracker.ServiceTracker;
44 import org.osgi.util.tracker.ServiceTrackerCustomizer;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvider, SchemaService, ServiceTrackerCustomizer<SchemaContextListener, SchemaContextListener>, YangTextSourceProvider, AutoCloseable {
49     private static final Logger LOG = LoggerFactory.getLogger(GlobalBundleScanningSchemaServiceImpl.class);
50
51     private static AtomicReference<GlobalBundleScanningSchemaServiceImpl> globalInstance = new AtomicReference<>();
52
53     @GuardedBy(value = "lock")
54     private final ListenerRegistry<SchemaContextListener> listeners = new ListenerRegistry<>();
55     private final YangTextSchemaContextResolver contextResolver = YangTextSchemaContextResolver.create("global-bundle");
56     private final BundleScanner scanner = new BundleScanner();
57     private final BundleContext context;
58
59     private ServiceTracker<SchemaContextListener, SchemaContextListener> listenerTracker;
60     private BundleTracker<Iterable<Registration>> bundleTracker;
61     private boolean starting = true;
62     private volatile boolean stopping;
63     private final Object lock = new Object();
64
65     private GlobalBundleScanningSchemaServiceImpl(final BundleContext context) {
66         this.context = Preconditions.checkNotNull(context);
67     }
68
69     public static GlobalBundleScanningSchemaServiceImpl createInstance(final BundleContext ctx) {
70         GlobalBundleScanningSchemaServiceImpl instance = new GlobalBundleScanningSchemaServiceImpl(ctx);
71         Preconditions.checkState(globalInstance.compareAndSet(null, instance));
72         instance.start();
73         return instance;
74     }
75
76     public static GlobalBundleScanningSchemaServiceImpl getInstance() {
77         GlobalBundleScanningSchemaServiceImpl instance = globalInstance.get();
78         Preconditions.checkState(instance != null, "Global Instance was not instantiated");
79         return instance;
80     }
81
82     @VisibleForTesting
83     public static void destroyInstance() {
84         GlobalBundleScanningSchemaServiceImpl instance = globalInstance.getAndSet(null);
85         if(instance != null) {
86             instance.close();
87         }
88     }
89
90     public BundleContext getContext() {
91         return context;
92     }
93
94     public void start() {
95         checkState(context != null);
96         LOG.debug("start() starting");
97
98         listenerTracker = new ServiceTracker<>(context, SchemaContextListener.class, GlobalBundleScanningSchemaServiceImpl.this);
99         bundleTracker = new BundleTracker<>(context, Bundle.RESOLVED | Bundle.STARTING |
100                 Bundle.STOPPING | Bundle.ACTIVE, scanner);
101
102         synchronized(lock) {
103             bundleTracker.open();
104
105             LOG.debug("BundleTracker.open() complete");
106
107             boolean hasExistingListeners = Iterables.size(listeners.getListeners()) > 0;
108             if(hasExistingListeners) {
109                 tryToUpdateSchemaContext();
110             }
111         }
112
113         listenerTracker.open();
114         starting = false;
115
116         LOG.debug("start() complete");
117     }
118
119     @Override
120     public SchemaContext getSchemaContext() {
121         return getGlobalContext();
122     }
123
124     @Override
125     public SchemaContext getGlobalContext() {
126         return contextResolver.getSchemaContext().orNull();
127     }
128
129     @Override
130     public void addModule(final Module module) {
131         throw new UnsupportedOperationException();
132     }
133
134     @Override
135     public SchemaContext getSessionContext() {
136         throw new UnsupportedOperationException();
137     }
138
139     @Override
140     public void removeModule(final Module module) {
141         throw new UnsupportedOperationException();
142     }
143
144     @Override
145     public ListenerRegistration<SchemaContextListener> registerSchemaContextListener(final SchemaContextListener listener) {
146         synchronized(lock) {
147             Optional<SchemaContext> potentialCtx = contextResolver.getSchemaContext();
148             if(potentialCtx.isPresent()) {
149                 listener.onGlobalContextUpdated(potentialCtx.get());
150             }
151             return listeners.register(listener);
152         }
153     }
154
155     @Override
156     public void close() {
157         stopping = true;
158         if (bundleTracker != null) {
159             bundleTracker.close();
160         }
161         if (listenerTracker != null) {
162             listenerTracker.close();
163         }
164
165         for (ListenerRegistration<SchemaContextListener> l : listeners.getListeners()) {
166             l.close();
167         }
168     }
169
170     @GuardedBy(value = "lock")
171     private void notifyListeners(final SchemaContext snapshot) {
172         Object[] services = listenerTracker.getServices();
173         for (ListenerRegistration<SchemaContextListener> listener : listeners) {
174             try {
175                 listener.getInstance().onGlobalContextUpdated(snapshot);
176             } catch (Exception e) {
177                 LOG.error("Exception occured during invoking listener", e);
178             }
179         }
180         if (services != null) {
181             for (Object rawListener : services) {
182                 final SchemaContextListener listener = (SchemaContextListener) rawListener;
183                 try {
184                     listener.onGlobalContextUpdated(snapshot);
185                 } catch (Exception e) {
186                     LOG.error("Exception occured during invoking listener {}", listener, e);
187                 }
188             }
189         }
190     }
191
192     @Override
193     public CheckedFuture<YangTextSchemaSource, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
194         return contextResolver.getSource(sourceIdentifier);
195
196     }
197
198     private class BundleScanner implements BundleTrackerCustomizer<Iterable<Registration>> {
199         @Override
200         public Iterable<Registration> addingBundle(final Bundle bundle, final BundleEvent event) {
201
202             if (bundle.getBundleId() == 0) {
203                 return Collections.emptyList();
204             }
205
206             final Enumeration<URL> enumeration = bundle.findEntries("META-INF/yang", "*.yang", false);
207             if (enumeration == null) {
208                 return Collections.emptyList();
209             }
210
211             final List<Registration> urls = new ArrayList<>();
212             while (enumeration.hasMoreElements()) {
213                 final URL u = enumeration.nextElement();
214                 try {
215                     urls.add(contextResolver.registerSource(u));
216                     LOG.debug("Registered {}", u);
217                 } catch (Exception e) {
218                     LOG.warn("Failed to register {}, ignoring it", e);
219                 }
220             }
221
222             if (!urls.isEmpty()) {
223                 LOG.debug("Loaded {} new URLs from bundle {}, attempting to rebuild schema context",
224                         urls.size(), bundle.getSymbolicName());
225                 tryToUpdateSchemaContext();
226             }
227
228             return ImmutableList.copyOf(urls);
229         }
230
231         @Override
232         public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> object) {
233         }
234
235         /**
236          * If removing YANG files makes yang store inconsistent, method
237          * {@link #getYangStoreSnapshot()} will throw exception. There is no
238          * rollback.
239          */
240
241         @Override
242         public void removedBundle(final Bundle bundle, final BundleEvent event, final Iterable<Registration> urls) {
243             for (Registration url : urls) {
244                 try {
245                     url.close();
246                 } catch (Exception e) {
247                     LOG.warn("Failed do unregister URL {}, proceeding", url, e);
248                 }
249             }
250
251             int numUrls = Iterables.size(urls);
252             if(numUrls > 0 ) {
253                 if(LOG.isDebugEnabled()) {
254                     LOG.debug("removedBundle: {}, state: {}, # urls: {}", bundle.getSymbolicName(), bundle.getState(), numUrls);
255                 }
256
257                 tryToUpdateSchemaContext();
258             }
259         }
260     }
261
262     @Override
263     public SchemaContextListener addingService(final ServiceReference<SchemaContextListener> reference) {
264
265         SchemaContextListener listener = context.getService(reference);
266         SchemaContext _ctxContext = getGlobalContext();
267         if (getContext() != null && _ctxContext != null) {
268             listener.onGlobalContextUpdated(_ctxContext);
269         }
270         return listener;
271     }
272
273     public void tryToUpdateSchemaContext() {
274         if (starting || stopping) {
275             return;
276         }
277
278         synchronized(lock) {
279             Optional<SchemaContext> schema = contextResolver.getSchemaContext();
280             if(schema.isPresent()) {
281                 if(LOG.isDebugEnabled()) {
282                     LOG.debug("Got new SchemaContext: # of modules {}", schema.get().getAllModuleIdentifiers().size());
283                 }
284
285                 notifyListeners(schema.get());
286             }
287         }
288     }
289
290     @Override
291     public void modifiedService(final ServiceReference<SchemaContextListener> reference, final SchemaContextListener service) {
292         // NOOP
293     }
294
295     @Override
296     public void removedService(final ServiceReference<SchemaContextListener> reference, final SchemaContextListener service) {
297         context.ungetService(reference);
298     }
299 }