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