View Javadoc

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