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