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 }