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.sal.api.lifecycle.LifecycleAware;
11  import com.atlassian.sal.api.lifecycle.LifecycleManager;
12  import org.osgi.framework.Bundle;
13  import org.osgi.framework.BundleContext;
14  import org.osgi.framework.ServiceEvent;
15  import org.osgi.framework.ServiceListener;
16  import org.osgi.framework.ServiceReference;
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  import org.springframework.beans.factory.DisposableBean;
20  import org.springframework.beans.factory.InitializingBean;
21  
22  import java.lang.reflect.Method;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.function.Consumer;
31  import java.util.function.Predicate;
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<>());
82          this.pendingOnStop = Collections.synchronizedSet(new HashSet<>());
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 =
171                 notifyLifecycleAwares(pendingOnStop, this::notifyOnStopIfEnabled);
172 
173         // Now remove those we handled.
174         pendingOnStop.removeAll(completed);
175     }
176 
177     private Predicate<ServiceReference<LifecycleAware>> notifyIfMyEvent(final PluginDisablingEvent event) {
178         return lifecycleAwareServiceReference -> {
179             final Bundle bundle = lifecycleAwareServiceReference.getBundle();
180             if (bundle == null) {
181                 log.warn("Discarding onStop() for stale LifecycleAware");
182                 // This reference is stale, so we report true as it can be discarded
183                 return true;
184             }
185 
186             // Notify if the event is for a plugin that waits for onStop()
187             return getPluginKey(bundle).equals(event.getPlugin().getKey())
188                     && notifyOnStopIfEnabled(lifecycleAwareServiceReference);
189         };
190     }
191 
192     @PluginEventListener
193     public void onPluginDisabling(final PluginDisablingEvent event) {
194         final Collection<ServiceReference<LifecycleAware>> completed =
195                 notifyLifecycleAwares(pendingOnStop, notifyIfMyEvent(event));
196 
197         // Now remove those we handled.
198         pendingOnStop.removeAll(completed);
199     }
200 
201     public void start() {
202         startIfApplicationSetup();
203     }
204 
205     private void startIfApplicationSetup() {
206         final boolean doSetup = !started && isApplicationSetUp();
207         if (doSetup) {
208             started = true;
209             // We could call the internal notify irrespective of doSetup, since it guards internally, but since we need to guard
210             // for subclasses, we may as well hoist this in here and save a bit of effort in the no-op case.
211             notifyStartableLifecycleAwares();
212             notifyOnStart();
213         }
214     }
215 
216     /**
217      * An override point for subclasses that require callback notification after the initial start of the system.
218      */
219     protected void notifyOnStart() {
220     }
221 
222     private void notifyStartableLifecycleAwares() {
223         final Collection<ServiceReference<LifecycleAware>> completed = notifyLifecycleAwares(pendingOnStart,
224                 this::notifyOnStartIfStartedAndEnabled);
225 
226         // Now remove those we handled.
227         pendingOnStart.removeAll(completed);
228         // and mark them waiting onStop
229         pendingOnStop.addAll(completed);
230     }
231 
232     private Collection<ServiceReference<LifecycleAware>> notifyLifecycleAwares(final Set<ServiceReference<LifecycleAware>> lifeCycleAwares,
233                                                                                final Predicate<ServiceReference<LifecycleAware>> event) {
234         // Grab a copy to operate on, so we don't call onStart()/onStop() holding a lock. We're using toArray here to keep the
235         // synchronization clear at the cost of the unchecked cast below. If we went via ImmutableList.copyOf, we'd need to either
236         // synchronize externally or depend on the implementation of copyOf - see Collections.synchronizedSet re iteration.
237         final Object[] pending = lifeCycleAwares.toArray();
238 
239         // Notify those we can, remembering what we handled.
240         final List<ServiceReference<LifecycleAware>> completed = new ArrayList<ServiceReference<LifecycleAware>>(pending.length);
241         for (final Object serviceRaw : pending) {
242             @SuppressWarnings("unchecked")
243             // serviceRaw comes from Object[] pending which is a snapshot of lifeCycleAwares so the cast is safe
244             final ServiceReference<LifecycleAware> service = (ServiceReference<LifecycleAware>) serviceRaw;
245             if (event.test(service)) {
246                 completed.add(service);
247             }
248         }
249         return completed;
250     }
251 
252     /**
253      * Notify a LifecycleAware with onStart() if the system is started and the plugin publishing the service is enabled.
254      *
255      * @param service the service to attempt to start
256      * @return true iff the service was started or is stale, false if it should be retained for later processing.
257      */
258     private boolean notifyOnStartIfStartedAndEnabled(final ServiceReference<LifecycleAware> service) {
259         if (started) {
260             return notifyLifecyleAware(service,
261                     new Consumer<LifecycleAware>() {
262                         @Override
263                         public void accept(final LifecycleAware lifecycleAware) {
264                             lifecycleAware.onStart();
265                         }
266 
267                         @Override
268                         public String toString() {
269                             return "onStart()";
270                         }
271                     });
272         } else {
273             // We have not called onStart and need to later (when the system is started)
274             return false;
275         }
276     }
277 
278     private boolean notifyLifecyleAware(final ServiceReference<LifecycleAware> service,
279                                         final Consumer<LifecycleAware> event) {
280         final Bundle bundle = service.getBundle();
281         final LifecycleAware lifecycleAware = bundleContext.getService(service);
282         try {
283             // Either of bundle or lifecycleAware can be null if the service isn't registered.
284             if ((null != bundle) && (null != lifecycleAware)) {
285                 final String pluginKey = getPluginKey(bundle);
286                 if (pluginAccessor.isPluginEnabled(pluginKey)) {
287                     try {
288                         log.debug("Calling LifecycleAware.{} '{}' from plugin '{}'",
289                                 event, lifecycleAware, pluginKey);
290                         event.accept(lifecycleAware);
291                     } catch (final Throwable ex) {
292                         log.error("LifecycleAware.{} failed for component with class '{}' from plugin '{}'",
293                                 event, lifecycleAware.getClass().getName(), pluginKey, ex);
294                     }
295                     // We have called event
296                     return true;
297                 } else {
298                     // We have not called event and need to do it later
299                     //  - for onStart(), when the plugin enables
300                     //  - for onStop(), as the last resort, when service is unregistered
301                     return false;
302                 }
303             } else {
304                 // This can happen if we take our snapshot of services to notify, and then one is unregistered. In this case,
305                 // likely both bundle and lifecycleAware are null, so there's not much we can get in the message here, and it means
306                 // niceties like trying to safely dig data out of them is probably not worth a whole lot.
307                 log.warn("Discarding {} for stale LifecycleAware", event);
308                 // This reference is stale, so we report true as it can be discarded
309                 return true;
310             }
311         } finally {
312             if (null != lifecycleAware) {
313                 bundleContext.ungetService(service);
314             }
315         }
316     }
317 
318     private boolean notifyOnStopIfEnabled(final ServiceReference<LifecycleAware> service) {
319         return notifyLifecyleAware(service,
320                 new Consumer<LifecycleAware>() {
321                     @Override
322                     public void accept(final LifecycleAware lifecycleAware) {
323                         try {
324                             // onStop() is only @since 3.0, try to call it but if it's not there then it's OK
325                             lifecycleAware.onStop();
326                         } catch (AbstractMethodError e) {
327                             final String errorMessage = "Failed to notify with LifecycleAware.onStop()";
328                             try {
329                                 // the method will be there because at least the LifecycleAware interface has it
330                                 Method onStop = lifecycleAware.getClass().getMethod("onStop");
331                                 if (onStop.getDeclaringClass() == LifecycleAware.class) {
332                                     // expected, the message is for debug if we need it
333                                     log.debug(errorMessage, e);
334                                 } else {
335                                     // onStop() is implemented, so we need to rethrow the exception
336                                     throw e;
337                                 }
338                             } catch (NoSuchMethodException e1) {
339                                 // not expected
340                                 log.warn(errorMessage, e1);
341                             }
342                         }
343                     }
344 
345                     @Override
346                     public String toString() {
347                         return "onStop()";
348                     }
349                 });
350     }
351 
352     private class LifecycleAwareServiceListener implements ServiceListener {
353         /**
354          * Handle a service changed by updating the list of pendingOnStart/pendingOnStop or dispatching onStart/onStop as necessary.
355          *
356          * @param serviceEvent details of the changed service
357          */
358         @Override
359         public void serviceChanged(final ServiceEvent serviceEvent) {
360             @SuppressWarnings("unchecked")
361             // The cast here is safe since we filter by this object class when adding the service listener
362             final ServiceReference<LifecycleAware> service = (ServiceReference<LifecycleAware>) serviceEvent.getServiceReference();
363             switch (serviceEvent.getType()) {
364                 case ServiceEvent.REGISTERED: {
365                     if (!notifyOnStartIfStartedAndEnabled(service)) {
366                         // We didn't satisfy notification conditions, so hang on to the service for later.
367                         pendingOnStart.add(service);
368                     }
369                     // else we tried to onStart it (we're started and the plugin is enabled), or it's stale. Either way, we're done.
370                     break;
371                 }
372                 case ServiceEvent.UNREGISTERING: {
373                     // It's possible we still have a reference to the service, for example if the plugin got as far as
374                     // registering the service before failing, and hence never became enabled. See the comment on the
375                     // pendingOnStart field for an explanation of why removing the ServiceReference directly is safe.
376                     pendingOnStart.remove(service);
377 
378                     // If we failed to notify with onStop earlier, it's our last chance
379                     if (pendingOnStop.remove(service)) {
380                         log.warn("Notifying with LifecycleAware.onStop() on service unregister");
381                         if (!notifyOnStopIfEnabled(service)) {
382                             // We failed to notify onStop(), most likely due to plugin has been disabled by now
383                             final Bundle bundle = service.getBundle();
384                             log.warn("Failed to notify {} with LifecycleAware.onStop()", LifecycleLog.getPluginKeyFromBundle(bundle));
385                         }
386                     }
387                     break;
388                 }
389                 default: {
390                     // We filter on objectClass, which org.osgi.framework.ServiceRegistration.setProperties() says cannot be
391                     // changed dynamically, so we don't need to handle MODIFIED or MODIFIED_ENDMATCH.
392                     break;
393                 }
394             }
395         }
396     }
397 }