View Javadoc

1   package com.atlassian.sal.core.lifecycle;
2   
3   import com.atlassian.plugin.PluginAccessor;
4   import com.atlassian.plugin.event.PluginEventListener;
5   import com.atlassian.plugin.event.PluginEventManager;
6   import com.atlassian.plugin.event.events.PluginDisablingEvent;
7   import com.atlassian.plugin.event.events.PluginEnabledEvent;
8   import com.atlassian.plugin.event.events.PluginFrameworkShuttingDownEvent;
9   import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
10  import com.atlassian.plugin.util.collect.Consumer;
11  import com.atlassian.plugin.util.collect.Predicate;
12  import com.atlassian.sal.api.lifecycle.LifecycleAware;
13  import com.atlassian.sal.api.lifecycle.LifecycleManager;
14  import org.osgi.framework.Bundle;
15  import org.osgi.framework.BundleContext;
16  import org.osgi.framework.ServiceEvent;
17  import org.osgi.framework.ServiceListener;
18  import org.osgi.framework.ServiceReference;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  import org.springframework.beans.factory.DisposableBean;
22  import org.springframework.beans.factory.InitializingBean;
23  
24  import java.lang.reflect.Method;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  import static com.atlassian.plugin.osgi.util.OsgiHeaderUtil.getPluginKey;
34  import static org.osgi.framework.Constants.OBJECTCLASS;
35  
36  /**
37   * Manage the tracking of {@link LifecycleAware} services registered to OSGi and call their {@link LifecycleAware#onStart}
38   * and {@link LifecycleAware#onStop()} when required.
39   * <p/>
40   * The {@code onStart} method is called when the application is setup according to {@link LifecycleManager#isApplicationSetUp}, the
41   * plugin is enabled, and the plugin framework has started.
42   * <p/>
43   * The {@code onStop} method is called if {@code onStart} was called, when plugin and it's dependencies are still present.
44   * <p/>
45   * This class has concurrency controls for the case of concurrent service registration/deregistration with plugin and application
46   * state activity. It does not account for concurrency of plugin notifications against themselves. This does not happen during
47   * normal usage.
48   */
49  public abstract class DefaultLifecycleManager implements LifecycleManager, InitializingBean, DisposableBean {
50      private static final Logger log = LoggerFactory.getLogger(DefaultLifecycleManager.class);
51  
52      private final PluginEventManager pluginEventManager;
53      private final PluginAccessor pluginAccessor;
54      private final BundleContext bundleContext;
55  
56      /**
57       * The LifecycleAware services which we know about via OSGi notifications, but which we have not yet called onStart() on.
58       * <p/>
59       * The documentation for {@link org.osgi.framework.ServiceReference} is quite explicit that while a framework may return
60       * multiple ServiceReference objects referring to the same ServiceRegistration, all such ServiceReference objects are equals()
61       * and have the same hashCode(), so it is safe to use them in a set like this.
62       * <p/>
63       * Inspection of Felix code (as at 4.2.1 of this writing) shows that in fact Felix is careful to have only a single
64       * ServiceReference for each ServiceRegistration. If we move to a framework where this is not so, or if this causes problems,
65       * we'd need to replace this with a map from the ServiceReference.getProperty(SERVICE_ID) to ServiceReference so we can key
66       * service tracking off SERVICE_ID.
67       */
68      private final Set<ServiceReference<LifecycleAware>> pendingOnStart;
69      private final Set<ServiceReference<LifecycleAware>> pendingOnStop;
70      private final ServiceListener serviceListener;
71  
72      private boolean started;
73  
74      public DefaultLifecycleManager(
75              final PluginEventManager pluginEventManager,
76              final PluginAccessor pluginAccessor,
77              final BundleContext bundleContext) {
78          this.pluginEventManager = pluginEventManager;
79          this.pluginAccessor = pluginAccessor;
80          this.bundleContext = bundleContext;
81          this.pendingOnStart = Collections.synchronizedSet(new HashSet<ServiceReference<LifecycleAware>>());
82          this.pendingOnStop = Collections.synchronizedSet(new HashSet<ServiceReference<LifecycleAware>>());
83          this.serviceListener = new LifecycleAwareServiceListener();
84          this.started = false;
85      }
86  
87      @Override
88      public void afterPropertiesSet() throws Exception {
89          pluginEventManager.register(this);
90          // Gather all the current LifecycleAware instances, and watch for new ones
91          final String filter = "(" + OBJECTCLASS + "=" + LifecycleAware.class.getName() + ")";
92          // Since OSGi doesn't (afaics) provide an atomic way to add a ServiceListener and get brought up to date, we need to be a
93          // bit careful here. If we miss an add, we won't call an onStart() we should, and if we miss a remove we'll possibly leak.
94          // So we starting listening, add everything we can see, and discard anything invalid.
95  
96          // An an aside, add everything, add listener, add everything doesn't work - a service that is removed after the snapshot
97          // for the second add everything is readded when this snapshot is added, and so you end up missing a remove.
98  
99          // Services that come and go here before this point are irrelevant
100         bundleContext.addServiceListener(serviceListener, filter);
101         // Anything added from now on is captured, so we don't lose services
102         final Collection<ServiceReference<LifecycleAware>> services =
103                 bundleContext.getServiceReferences(LifecycleAware.class, null);
104         // Our snapshot may contain services that have already made it to pending. This means we might get double adds (which the
105         // set takes care of), or we might re-add something in our snapshot that gets removed in this window. Such a service might
106         // have turned up before we started listening or after, so the listener might think the remove is spurious or normal, but
107         // the service is in our snapshot.
108         pendingOnStart.addAll(services);
109         // Any remove from now on is correctly processed - we've gathered everything and put it it pendingOnStart. However,
110         // we might have re-added some service that was removed post snapshot but before we updated pendingOnStart. To deal
111         // with these stale services, we do a sweep and chuck them out.  We can sweep the services from the getServiceReferences
112         // snapshot, because those that arrived via the listener will be present when removed. A service added before the listener
113         // is added but removed afterwards, but before the snapshot, is just ignored as a spurious remove.
114         for (final ServiceReference<LifecycleAware> service : services) {
115             // The documentation for getBundle() says this is a sanctioned way to detect deregistration
116             if (null == service.getBundle()) {
117                 pendingOnStart.remove(service);
118             }
119         }
120     }
121 
122     @SuppressWarnings("unchecked")
123     private void clearPendingOnStop() {
124         if (pendingOnStop.isEmpty()) {
125             // all onStop() delivered
126             return;
127         }
128 
129         // it's safe to cast here, we know what pendingOnStop is
130         log.warn("Failed to notify with LifecycleAware.onStop(): {}",
131                 LifecycleLog.listPluginKeys(Arrays.asList((ServiceReference<Object>[]) pendingOnStop.toArray())));
132 
133         pendingOnStop.clear();
134     }
135 
136     @Override
137     public void destroy() throws Exception {
138         bundleContext.removeServiceListener(serviceListener);
139         // We  clear out our pendingOnStart, to assist garbage collection. This is just a heuristic - if this code moves
140         // this is *not* sufficient to prevent leaks, since a thread could be in serviceListener now, having arrived before we
141         // removed the listener but not yet finished. Since this is a @PreDestroy and things are going away, the GC will save us.
142         // If we do move this code, we should probably move pendingOnStart to the serviceListener and have their lifetimes
143         // match, but I'm not going to pre-emptively solve this synchronization problem without knowledge of the lifetimes and where
144         // things are moving to.
145         pendingOnStart.clear();
146         clearPendingOnStop();
147         pluginEventManager.unregister(this);
148     }
149 
150     @PluginEventListener
151     public void onPluginFrameworkStarted(final PluginFrameworkStartedEvent event) {
152         startIfApplicationSetup();
153     }
154 
155     @PluginEventListener
156     public void onPluginEnabled(final PluginEnabledEvent event) {
157         // I don't think it's actually worth restricting to the "right" plugin here - if a whole lot of stuff is enabled when
158         // we get the event, we may as well start them all in one hit. This will stop lots of doubling handling of outstanding
159         // LifecycleAware instances. However, this is called a lot before startup, so we hoist the started check.
160         if (started) {
161             notifyStartableLifecycleAwares();
162         }
163     }
164 
165     @PluginEventListener
166     public void onPluginFrameworkShuttingDown(final PluginFrameworkShuttingDownEvent event) {
167         // We notifying all waiting onStop() here, as the next thing after this event is
168         // all plugins unregistered
169 
170         final Collection<ServiceReference<LifecycleAware>> completed = notifyLifecycleAwares(pendingOnStop,
171                 new Predicate<ServiceReference<LifecycleAware>>() {
172                     @Override
173                     public boolean evaluate(final ServiceReference<LifecycleAware> lifecycleAwareServiceReference) {
174                         return notifyOnStopIfEnabled(lifecycleAwareServiceReference);
175                     }
176                 });
177 
178         // Now remove those we handled.
179         pendingOnStop.removeAll(completed);
180     }
181 
182     private Predicate<ServiceReference<LifecycleAware>> notifyIfMyEvent(final PluginDisablingEvent event) {
183         return new Predicate<ServiceReference<LifecycleAware>>() {
184             @Override
185             public boolean evaluate(final ServiceReference<LifecycleAware> lifecycleAwareServiceReference) {
186                 final Bundle bundle = lifecycleAwareServiceReference.getBundle();
187                 if (bundle == null) {
188                     log.warn("Discarding onStop() for stale LifecycleAware");
189                     // This reference is stale, so we report true as it can be discarded
190                     return true;
191                 }
192 
193                 // Notify if the event is for a plugin that waits for onStop()
194                 return getPluginKey(bundle).equals(event.getPlugin().getKey())
195                         && notifyOnStopIfEnabled(lifecycleAwareServiceReference);
196             }
197         };
198     }
199 
200     @PluginEventListener
201     public void onPluginDisabling(final PluginDisablingEvent event) {
202         final Collection<ServiceReference<LifecycleAware>> completed =
203                 notifyLifecycleAwares(pendingOnStop, notifyIfMyEvent(event));
204 
205         // Now remove those we handled.
206         pendingOnStop.removeAll(completed);
207     }
208 
209     public void start() {
210         startIfApplicationSetup();
211     }
212 
213     private void startIfApplicationSetup() {
214         final boolean doSetup = !started && isApplicationSetUp();
215         if (doSetup) {
216             started = true;
217             // We could call the internal notify irrespective of doSetup, since it guards internally, but since we need to guard
218             // for subclasses, we may as well hoist this in here and save a bit of effort in the no-op case.
219             notifyStartableLifecycleAwares();
220             notifyOnStart();
221         }
222     }
223 
224     /**
225      * An override point for subclasses that require callback notification after the initial start of the system.
226      */
227     protected void notifyOnStart() {
228     }
229 
230     private void notifyStartableLifecycleAwares() {
231         final Collection<ServiceReference<LifecycleAware>> completed = notifyLifecycleAwares(pendingOnStart,
232                 new Predicate<ServiceReference<LifecycleAware>>() {
233                     @Override
234                     public boolean evaluate(final ServiceReference<LifecycleAware> lifecycleAwareServiceReference) {
235                         return notifyOnStartIfStartedAndEnabled(lifecycleAwareServiceReference);
236                     }
237                 });
238 
239         // Now remove those we handled.
240         pendingOnStart.removeAll(completed);
241         // and mark them waiting onStop
242         pendingOnStop.addAll(completed);
243     }
244 
245     private Collection<ServiceReference<LifecycleAware>> notifyLifecycleAwares(final Set<ServiceReference<LifecycleAware>> lifeCycleAwares,
246                                                                                final Predicate<ServiceReference<LifecycleAware>> event) {
247         // Grab a copy to operate on, so we don't call onStart()/onStop() holding a lock. We're using toArray here to keep the
248         // synchronization clear at the cost of the unchecked cast below. If we went via ImmutableList.copyOf, we'd need to either
249         // synchronize externally or depend on the implementation of copyOf - see Collections.synchronizedSet re iteration.
250         final Object[] pending = lifeCycleAwares.toArray();
251 
252         // Notify those we can, remembering what we handled.
253         final List<ServiceReference<LifecycleAware>> completed = new ArrayList<ServiceReference<LifecycleAware>>(pending.length);
254         for (final Object serviceRaw : pending) {
255             @SuppressWarnings("unchecked")
256             // serviceRaw comes from Object[] pending which is a snapshot of lifeCycleAwares so the cast is safe
257             final ServiceReference<LifecycleAware> service = (ServiceReference<LifecycleAware>) serviceRaw;
258             if (event.evaluate(service)) {
259                 completed.add(service);
260             }
261         }
262         return completed;
263     }
264 
265     /**
266      * Notify a LifecycleAware with onStart() if the system is started and the plugin publishing the service is enabled.
267      *
268      * @param service the service to attempt to start
269      * @return true iff the service was started or is stale, false if it should be retained for later processing.
270      */
271     private boolean notifyOnStartIfStartedAndEnabled(final ServiceReference<LifecycleAware> service) {
272         if (started) {
273             return notifyLifecyleAware(service,
274                     new Consumer<LifecycleAware>() {
275                         @Override
276                         public void consume(final LifecycleAware lifecycleAware) {
277                             lifecycleAware.onStart();
278                         }
279 
280                         @Override
281                         public String toString() {
282                             return "onStart()";
283                         }
284                     });
285         } else {
286             // We have not called onStart and need to later (when the system is started)
287             return false;
288         }
289     }
290 
291     private boolean notifyLifecyleAware(final ServiceReference<LifecycleAware> service,
292                                         final Consumer<LifecycleAware> event) {
293         final Bundle bundle = service.getBundle();
294         final LifecycleAware lifecycleAware = bundleContext.getService(service);
295         try {
296             // Either of bundle or lifecycleAware can be null if the service isn't registered.
297             if ((null != bundle) && (null != lifecycleAware)) {
298                 final String pluginKey = getPluginKey(bundle);
299                 if (pluginAccessor.isPluginEnabled(pluginKey)) {
300                     try {
301                         log.debug("Calling LifecycleAware.{} '{}' from plugin '{}'",
302                                 event, lifecycleAware, pluginKey);
303                         event.consume(lifecycleAware);
304                     } catch (final RuntimeException ex) {
305                         log.error("LifecycleAware.{} failed for component with class '{}' from plugin '{}'",
306                                 event, lifecycleAware.getClass().getName(), pluginKey, ex);
307                     }
308                     // We have called event
309                     return true;
310                 } else {
311                     // We have not called event and need to do it later
312                     //  - for onStart(), when the plugin enables
313                     //  - for onStop(), as the last resort, when service is unregistered
314                     return false;
315                 }
316             } else {
317                 // This can happen if we take our snapshot of services to notify, and then one is unregistered. In this case,
318                 // likely both bundle and lifecycleAware are null, so there's not much we can get in the message here, and it means
319                 // niceties like trying to safely dig data out of them is probably not worth a whole lot.
320                 log.warn("Discarding {} for stale LifecycleAware", event);
321                 // This reference is stale, so we report true as it can be discarded
322                 return true;
323             }
324         } finally {
325             if (null != lifecycleAware) {
326                 bundleContext.ungetService(service);
327             }
328         }
329     }
330 
331     private boolean notifyOnStopIfEnabled(final ServiceReference<LifecycleAware> service) {
332         return notifyLifecyleAware(service,
333                 new Consumer<LifecycleAware>() {
334                     @Override
335                     public void consume(final LifecycleAware lifecycleAware) {
336                         try {
337                             // onStop() is only @since 3.0, try to call it but if it's not there then it's OK
338                             lifecycleAware.onStop();
339                         } catch (AbstractMethodError e) {
340                             final String errorMessage = "Failed to notify with LifecycleAware.onStop()";
341                             try {
342                                 // the method will be there because at least the LifecycleAware interface has it
343                                 Method onStop = lifecycleAware.getClass().getMethod("onStop");
344                                 if (onStop.getDeclaringClass() == LifecycleAware.class) {
345                                     // expected, the message is for debug if we need it
346                                     log.debug(errorMessage, e);
347                                 } else {
348                                     // onStop() is implemented, so we need to rethrow the exception
349                                     throw e;
350                                 }
351                             } catch (NoSuchMethodException e1) {
352                                 // not expected
353                                 log.warn(errorMessage, e1);
354                             }
355                         }
356                     }
357 
358                     @Override
359                     public String toString() {
360                         return "onStop()";
361                     }
362                 });
363     }
364 
365     private class LifecycleAwareServiceListener implements ServiceListener {
366         /**
367          * Handle a service changed by updating the list of pendingOnStart/pendingOnStop or dispatching onStart/onStop as necessary.
368          *
369          * @param serviceEvent details of the changed service
370          */
371         @Override
372         public void serviceChanged(final ServiceEvent serviceEvent) {
373             @SuppressWarnings("unchecked")
374             // The cast here is safe since we filter by this object class when adding the service listener
375             final ServiceReference<LifecycleAware> service = (ServiceReference<LifecycleAware>) serviceEvent.getServiceReference();
376             switch (serviceEvent.getType()) {
377                 case ServiceEvent.REGISTERED: {
378                     if (!notifyOnStartIfStartedAndEnabled(service)) {
379                         // We didn't satisfy notification conditions, so hang on to the service for later.
380                         pendingOnStart.add(service);
381                     }
382                     // else we tried to onStart it (we're started and the plugin is enabled), or it's stale. Either way, we're done.
383                     break;
384                 }
385                 case ServiceEvent.UNREGISTERING: {
386                     // It's possible we still have a reference to the service, for example if the plugin got as far as
387                     // registering the service before failing, and hence never became enabled. See the comment on the
388                     // pendingOnStart field for an explanation of why removing the ServiceReference directly is safe.
389                     pendingOnStart.remove(service);
390 
391                     // If we failed to notify with onStop earlier, it's our last chance
392                     if (pendingOnStop.remove(service)) {
393                         log.warn("Notifying with LifecycleAware.onStop() on service unregister");
394                         if (!notifyOnStopIfEnabled(service)) {
395                             // We failed to notify onStop(), most likely due to plugin has been disabled by now
396                             final Bundle bundle = service.getBundle();
397                             log.warn("Failed to notify {} with LifecycleAware.onStop()", LifecycleLog.getPluginKeyFromBundle(bundle));
398                         }
399                     }
400                     break;
401                 }
402                 default: {
403                     // We filter on objectClass, which org.osgi.framework.ServiceRegistration.setProperties() says cannot be
404                     // changed dynamically, so we don't need to handle MODIFIED or MODIFIED_ENDMATCH.
405                     break;
406                 }
407             }
408         }
409     }
410 }