1 package com.atlassian.plugin.osgi.factory;
2
3 import com.atlassian.plugin.AutowireCapablePlugin;
4 import com.atlassian.plugin.IllegalPluginStateException;
5 import com.atlassian.plugin.InstallationMode;
6 import com.atlassian.plugin.ModuleDescriptor;
7 import com.atlassian.plugin.PluginArtifact;
8 import com.atlassian.plugin.PluginArtifactBackedPlugin;
9 import com.atlassian.plugin.PluginState;
10 import com.atlassian.plugin.event.PluginEventListener;
11 import com.atlassian.plugin.event.PluginEventManager;
12 import com.atlassian.plugin.event.events.PluginContainerFailedEvent;
13 import com.atlassian.plugin.event.events.PluginContainerRefreshedEvent;
14 import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
15 import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
16 import com.atlassian.plugin.event.events.PluginRefreshedEvent;
17 import com.atlassian.plugin.impl.AbstractPlugin;
18 import com.atlassian.plugin.module.ContainerAccessor;
19 import com.atlassian.plugin.module.ContainerManagedPlugin;
20 import com.atlassian.plugin.osgi.container.OsgiContainerException;
21 import com.atlassian.plugin.osgi.container.OsgiContainerManager;
22 import com.atlassian.plugin.osgi.event.PluginServiceDependencyWaitEndedEvent;
23 import com.atlassian.plugin.osgi.event.PluginServiceDependencyWaitStartingEvent;
24 import com.atlassian.plugin.osgi.event.PluginServiceDependencyWaitTimedOutEvent;
25 import com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory;
26 import com.atlassian.plugin.osgi.util.OsgiSystemBundleUtil;
27 import com.atlassian.plugin.util.PluginUtils;
28 import org.apache.commons.lang.Validate;
29 import org.dom4j.Element;
30 import org.osgi.framework.Bundle;
31 import org.osgi.framework.BundleContext;
32 import org.osgi.framework.BundleEvent;
33 import org.osgi.framework.BundleException;
34 import org.osgi.framework.BundleListener;
35 import org.osgi.framework.ServiceReference;
36 import org.osgi.framework.SynchronousBundleListener;
37 import org.osgi.service.packageadmin.PackageAdmin;
38 import org.osgi.util.tracker.ServiceTracker;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.io.InputStream;
43 import java.net.URL;
44 import java.util.HashMap;
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.concurrent.CopyOnWriteArraySet;
48
49
50
51
52
53
54
55
56
57
58
59
60 public class OsgiPlugin extends AbstractPlugin implements AutowireCapablePlugin, ContainerManagedPlugin, PluginArtifactBackedPlugin
61 {
62
63
64
65 public static final String ATLASSIAN_PLUGIN_KEY = "Atlassian-Plugin-Key";
66
67
68
69
70 public static final String REMOTE_PLUGIN_KEY = "Remote-Plugin";
71
72 private final Map<String, Element> moduleElements = new HashMap<String, Element>();
73 private final PluginEventManager pluginEventManager;
74 private final PackageAdmin packageAdmin;
75 private final Set<OutstandingDependency> outstandingDependencies = new CopyOnWriteArraySet<OutstandingDependency>();
76 private final Logger log = LoggerFactory.getLogger(this.getClass());
77 private final BundleListener bundleStartStopListener;
78 private final PluginArtifact originalPluginArtifact;
79
80 private volatile boolean treatPluginContainerCreationAsRefresh = false;
81 private volatile OsgiPluginHelper helper;
82
83
84
85
86
87
88 private volatile boolean frameworkStarted = false;
89
90 public OsgiPlugin(final String key, final OsgiContainerManager mgr, final PluginArtifact artifact, final PluginArtifact originalPluginArtifact, final PluginEventManager pluginEventManager)
91 {
92 Validate.notNull(key, "The plugin key is required");
93 Validate.notNull(mgr, "The osgi container is required");
94 Validate.notNull(artifact, "The osgi container is required");
95 Validate.notNull(pluginEventManager, "The osgi container is required");
96
97 this.originalPluginArtifact = originalPluginArtifact;
98 this.helper = new OsgiPluginUninstalledHelper(key, mgr, artifact);
99 this.pluginEventManager = pluginEventManager;
100 this.packageAdmin = extractPackageAdminFromOsgi(mgr);
101
102 this.bundleStartStopListener = new SynchronousBundleListener()
103 {
104 public void bundleChanged(final BundleEvent bundleEvent)
105 {
106 if (bundleEvent.getBundle() == getBundle())
107 {
108 if (bundleEvent.getType() == BundleEvent.STOPPING)
109 {
110 helper.onDisable();
111 setPluginState(PluginState.DISABLED);
112 }
113 else if (bundleEvent.getType() == BundleEvent.STARTED)
114 {
115 BundleContext ctx = getBundle().getBundleContext();
116 helper.onEnable(createServiceTrackers(ctx));
117 setPluginState(PluginState.ENABLED);
118 }
119 }
120 }
121 };
122 }
123
124
125
126
127
128 OsgiPlugin(PluginEventManager pluginEventManager, OsgiPluginHelper helper)
129 {
130 this.helper = helper;
131 this.pluginEventManager = pluginEventManager;
132 this.packageAdmin = null;
133 this.bundleStartStopListener = null;
134 this.originalPluginArtifact = null;
135 }
136
137
138
139
140
141 public Bundle getBundle() throws IllegalPluginStateException
142 {
143 return helper.getBundle();
144 }
145
146 @Override
147 protected final InstallationMode getInstallationMode()
148 {
149 return isRemotePlugin() ? InstallationMode.REMOTE : InstallationMode.LOCAL;
150 }
151
152 private boolean isRemotePlugin()
153 {
154 return (getBundle().getHeaders() != null && (getBundle().getHeaders().get(REMOTE_PLUGIN_KEY) != null));
155 }
156
157
158
159
160
161 public boolean isUninstallable()
162 {
163 return true;
164 }
165
166
167
168
169 public boolean isDynamicallyLoaded()
170 {
171 return true;
172 }
173
174
175
176
177 public boolean isDeleteable()
178 {
179 return true;
180 }
181
182 public PluginArtifact getPluginArtifact()
183 {
184 return originalPluginArtifact;
185 }
186
187
188
189
190
191
192
193
194
195 public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException, IllegalPluginStateException
196 {
197 return helper.loadClass(clazz, callingClass);
198 }
199
200
201
202
203
204
205 public URL getResource(final String name) throws IllegalPluginStateException
206 {
207 return helper.getResource(name);
208 }
209
210
211
212
213
214
215 public InputStream getResourceAsStream(final String name) throws IllegalPluginStateException
216 {
217 return helper.getResourceAsStream(name);
218 }
219
220
221
222
223
224 public ClassLoader getClassLoader() throws IllegalPluginStateException
225 {
226 return helper.getClassLoader();
227 }
228
229
230
231
232
233
234
235
236 @PluginEventListener
237 public void onPluginContainerFailed(final PluginContainerFailedEvent event) throws IllegalPluginStateException
238 {
239 if (getKey() == null)
240 {
241 throw new IllegalPluginStateException("Plugin key must be set");
242 }
243 if (getKey().equals(event.getPluginKey()))
244 {
245 logAndClearOustandingDependencies();
246
247 getLog().error("Unable to start the plugin container for plugin " + getKey(), event.getCause());
248 setPluginState(PluginState.DISABLED);
249 }
250 }
251
252 @PluginEventListener
253 public void onPluginFrameworkStartedEvent(final PluginFrameworkStartedEvent event)
254 {
255 frameworkStarted = true;
256 }
257
258 @PluginEventListener
259 public void onPluginFrameworkShutdownEvent(final PluginFrameworkShutdownEvent event)
260 {
261 frameworkStarted = false;
262 }
263
264 @PluginEventListener
265 public void onServiceDependencyWaitStarting(PluginServiceDependencyWaitStartingEvent event)
266 {
267 if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
268 {
269 OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
270 outstandingDependencies.add(dep);
271 getLog().info(generateOutstandingDependencyLogMessage(dep, "Waiting for"));
272 }
273 }
274
275 @PluginEventListener
276 public void onServiceDependencyWaitEnded(PluginServiceDependencyWaitEndedEvent event)
277 {
278 if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
279 {
280 OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
281 outstandingDependencies.remove(dep);
282 getLog().info(generateOutstandingDependencyLogMessage(dep, "Found"));
283 }
284 }
285
286 @PluginEventListener
287 public void onServiceDependencyWaitEnded(PluginServiceDependencyWaitTimedOutEvent event)
288 {
289 if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
290 {
291 OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
292 outstandingDependencies.remove(dep);
293 getLog().error(generateOutstandingDependencyLogMessage(dep, "Timeout waiting for "));
294 }
295 }
296
297 private String generateOutstandingDependencyLogMessage(OutstandingDependency dep, String action)
298 {
299 StringBuilder sb = new StringBuilder();
300 sb.append(action).append(" ");
301 sb.append("service '").append(dep.getBeanName()).append("' for plugin '").append(getKey()).append("' with filter ").append(dep.getFilter());
302 return sb.toString();
303 }
304
305
306
307
308
309
310
311
312
313 @PluginEventListener
314 public void onPluginContainerRefresh(final PluginContainerRefreshedEvent event) throws IllegalPluginStateException
315 {
316 if (getKey() == null)
317 {
318 throw new IllegalPluginStateException("Plugin key must be set");
319 }
320 if (getKey().equals(event.getPluginKey()))
321 {
322 outstandingDependencies.clear();
323 helper.setPluginContainer(event.getContainer());
324 if (!compareAndSetPluginState(PluginState.ENABLING, PluginState.ENABLED) && getPluginState() != PluginState.ENABLED)
325 {
326 log.warn("Ignoring the bean container that was just created for plugin " + getKey() + ". The plugin " +
327 "is in an invalid state, " + getPluginState() + ", that doesn't support a transition to " +
328 "enabled. Most likely, it was disabled due to a timeout.");
329 helper.setPluginContainer(null);
330 return;
331 }
332
333
334 if (treatPluginContainerCreationAsRefresh)
335 {
336 pluginEventManager.broadcast(new PluginRefreshedEvent(this));
337 }
338 else
339 {
340 treatPluginContainerCreationAsRefresh = true;
341 }
342 }
343 }
344
345
346
347
348
349
350 public <T> T autowire(final Class<T> clazz) throws IllegalPluginStateException
351 {
352 return autowire(clazz, AutowireStrategy.AUTOWIRE_AUTODETECT);
353 }
354
355
356
357
358
359
360 public <T> T autowire(final Class<T> clazz, final AutowireStrategy autowireStrategy) throws IllegalPluginStateException
361 {
362 return helper.autowire(clazz, autowireStrategy);
363 }
364
365
366
367
368
369
370 public void autowire(final Object instance) throws IllegalStateException
371 {
372 autowire(instance, AutowireStrategy.AUTOWIRE_AUTODETECT);
373 }
374
375
376
377
378
379
380 public void autowire(final Object instance, final AutowireStrategy autowireStrategy) throws IllegalPluginStateException
381 {
382 helper.autowire(instance, autowireStrategy);
383 }
384
385
386
387
388
389
390
391
392 @Override
393 public Set<String> getRequiredPlugins() throws IllegalPluginStateException
394 {
395 return helper.getRequiredPlugins();
396 }
397
398 @Override
399 public String toString()
400 {
401 return getKey();
402 }
403
404
405
406
407
408
409 @Override
410 protected void installInternal() throws IllegalPluginStateException
411 {
412 Bundle bundle = helper.install();
413 helper = new OsgiPluginInstalledHelper(bundle, packageAdmin);
414 }
415
416
417
418
419
420
421
422
423
424
425 @Override
426 protected synchronized PluginState enableInternal() throws OsgiContainerException, IllegalPluginStateException
427 {
428 PluginState stateResult;
429 try
430 {
431 if (getBundle().getState() == Bundle.ACTIVE)
432 {
433 stateResult = PluginState.ENABLED;
434 }
435 else if ((getBundle().getState() == Bundle.RESOLVED) || (getBundle().getState() == Bundle.INSTALLED))
436 {
437 pluginEventManager.register(this);
438 if (!treatPluginContainerCreationAsRefresh)
439 {
440 stateResult = PluginState.ENABLING;
441
442 setPluginState(stateResult);
443 }
444 else
445 {
446 stateResult = PluginState.ENABLED;
447 }
448 getBundle().start();
449 final BundleContext ctx = getBundle().getBundleContext();
450 helper.onEnable(createServiceTrackers(ctx));
451
452
453 OsgiSystemBundleUtil.getSystemBundleContext(ctx).addBundleListener(bundleStartStopListener);
454 }
455 else
456 {
457 throw new OsgiContainerException("Cannot enable the plugin '" + getKey() + "' when the bundle is not in the resolved or installed state: "
458 + getBundle().getState() + "(" + getBundle().getBundleId() + ")");
459 }
460
461
462
463 return (getPluginState() != PluginState.ENABLED ? stateResult : PluginState.ENABLED);
464 }
465 catch (final BundleException e)
466 {
467 log.error("Detected an error (BundleException) enabling the plugin '" + getKey() + "' : " + e.getMessage() + ". " +
468 " This error usually occurs when your plugin imports a package from another bundle with a specific version constraint " +
469 "and either the bundle providing that package doesn't meet those version constraints, or there is no bundle " +
470 "available that provides the specified package. For more details on how to fix this, see " +
471 "https://developer.atlassian.com/x/mQAN");
472 throw new OsgiContainerException("Cannot start plugin: " + getKey(), e);
473 }
474 }
475
476 private ServiceTracker[] createServiceTrackers(BundleContext ctx)
477 {
478 return new ServiceTracker[] {
479 new ServiceTracker(ctx, ModuleDescriptor.class.getName(),
480 new ModuleDescriptorServiceTrackerCustomizer(this, pluginEventManager)),
481 new ServiceTracker(ctx, ListableModuleDescriptorFactory.class.getName(),
482 new UnrecognizedModuleDescriptorServiceTrackerCustomizer(this, pluginEventManager))
483 };
484 }
485
486
487
488
489
490
491
492 @Override
493 protected synchronized void disableInternal() throws OsgiContainerException, IllegalPluginStateException
494 {
495
496 if (!requiresRestart())
497 {
498 try
499 {
500 if (getPluginState() == PluginState.DISABLING)
501 {
502 logAndClearOustandingDependencies();
503 }
504 helper.onDisable();
505 pluginEventManager.unregister(this);
506 OsgiSystemBundleUtil.getSystemBundleContext(getBundle()).removeBundleListener(bundleStartStopListener);
507 getBundle().stop();
508 treatPluginContainerCreationAsRefresh = false;
509 }
510 catch (final BundleException e)
511 {
512 throw new OsgiContainerException("Cannot stop plugin: " + getKey(), e);
513 }
514 }
515 }
516
517 private boolean requiresRestart()
518 {
519 return frameworkStarted && PluginUtils.doesPluginRequireRestart(this);
520 }
521
522 private void logAndClearOustandingDependencies()
523 {
524 for (OutstandingDependency dep : outstandingDependencies)
525 {
526 getLog().error(generateOutstandingDependencyLogMessage(dep, "Never resolved"));
527 }
528 outstandingDependencies.clear();
529 }
530
531
532
533
534
535
536 @Override
537 protected void uninstallInternal() throws OsgiContainerException, IllegalPluginStateException
538 {
539 try
540 {
541 if (getBundle().getState() != Bundle.UNINSTALLED)
542 {
543 pluginEventManager.unregister(this);
544 getBundle().uninstall();
545 helper.onUninstall();
546 setPluginState(PluginState.UNINSTALLED);
547 }
548 }
549 catch (final BundleException e)
550 {
551 throw new OsgiContainerException("Cannot uninstall bundle " + getBundle().getSymbolicName());
552 }
553 }
554
555
556
557
558
559
560
561 void addModuleDescriptorElement(final String key, final Element element)
562 {
563 moduleElements.put(key, element);
564 }
565
566
567
568
569
570
571 void clearModuleDescriptor(String key)
572 {
573 removeModuleDescriptor(key);
574 }
575
576
577
578
579
580
581
582 Map<String, Element> getModuleElements()
583 {
584 return moduleElements;
585 }
586
587
588
589
590
591
592 private PackageAdmin extractPackageAdminFromOsgi(OsgiContainerManager mgr)
593 {
594
595 Bundle bundle = mgr.getBundles()[0];
596
597
598 final ServiceReference ref = bundle.getBundleContext()
599 .getServiceReference(PackageAdmin.class.getName());
600 return (PackageAdmin) bundle.getBundleContext()
601 .getService(ref);
602 }
603
604 public ContainerAccessor getContainerAccessor()
605 {
606 return helper.getContainerAccessor();
607 }
608
609 private static class OutstandingDependency
610 {
611 private final String beanName;
612 private final String filter;
613
614 public OutstandingDependency(String beanName, String filter)
615 {
616 this.beanName = beanName;
617 this.filter = filter;
618 }
619
620 public String getBeanName()
621 {
622 return beanName;
623 }
624
625 public String getFilter()
626 {
627 return filter;
628 }
629
630 @Override
631 public boolean equals(Object o)
632 {
633 if (this == o)
634 {
635 return true;
636 }
637 if (o == null || getClass() != o.getClass())
638 {
639 return false;
640 }
641
642 OutstandingDependency that = (OutstandingDependency) o;
643
644 if (beanName != null ? !beanName.equals(that.beanName) : that.beanName != null)
645 {
646 return false;
647 }
648 if (!filter.equals(that.filter))
649 {
650 return false;
651 }
652
653 return true;
654 }
655
656 @Override
657 public int hashCode()
658 {
659 int result = beanName != null ? beanName.hashCode() : 0;
660 result = 31 * result + filter.hashCode();
661 return result;
662 }
663 }
664 }