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