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.util.PluginUtils;
26 import com.google.common.annotations.VisibleForTesting;
27 import org.dom4j.Element;
28 import org.osgi.framework.Bundle;
29 import org.osgi.framework.BundleContext;
30 import org.osgi.framework.BundleEvent;
31 import org.osgi.framework.BundleException;
32 import org.osgi.framework.BundleListener;
33 import org.osgi.framework.ServiceReference;
34 import org.osgi.framework.SynchronousBundleListener;
35 import org.osgi.service.packageadmin.PackageAdmin;
36 import org.osgi.util.tracker.ServiceTracker;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import java.io.InputStream;
41 import java.net.URL;
42 import java.util.Date;
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
178 @Override
179 public Date getDateInstalled()
180 {
181 long date = getPluginArtifact().toFile().lastModified();
182 if (date == 0)
183 {
184 date = getDateLoaded().getTime();
185 }
186 return new Date(date);
187 }
188
189 public PluginArtifact getPluginArtifact()
190 {
191 return originalPluginArtifact;
192 }
193
194
195
196
197
198
199
200
201
202 public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException, IllegalPluginStateException
203 {
204 return helper.loadClass(clazz, callingClass);
205 }
206
207
208
209
210
211
212 public URL getResource(final String name) throws IllegalPluginStateException
213 {
214 return helper.getResource(name);
215 }
216
217
218
219
220
221
222 public InputStream getResourceAsStream(final String name) throws IllegalPluginStateException
223 {
224 return helper.getResourceAsStream(name);
225 }
226
227
228
229
230
231 public ClassLoader getClassLoader() throws IllegalPluginStateException
232 {
233 return helper.getClassLoader();
234 }
235
236
237
238
239
240
241
242
243 @PluginEventListener
244 public void onPluginContainerFailed(final PluginContainerFailedEvent event) throws IllegalPluginStateException
245 {
246 if (getKey() == null)
247 {
248 throw new IllegalPluginStateException("Plugin key must be set");
249 }
250 if (getKey().equals(event.getPluginKey()))
251 {
252 logAndClearOustandingDependencies();
253
254 getLog().error("Unable to start the plugin container for plugin " + getKey(), event.getCause());
255 setPluginState(PluginState.DISABLED);
256 }
257 }
258
259 @PluginEventListener
260 public void onPluginFrameworkStartedEvent(final PluginFrameworkStartedEvent event)
261 {
262 frameworkStarted = true;
263 }
264
265 @PluginEventListener
266 public void onPluginFrameworkShutdownEvent(final PluginFrameworkShutdownEvent event)
267 {
268 frameworkStarted = false;
269 }
270
271 @PluginEventListener
272 public void onServiceDependencyWaitStarting(PluginServiceDependencyWaitStartingEvent event)
273 {
274 if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
275 {
276 OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
277 outstandingDependencies.add(dep);
278 getLog().info(generateOutstandingDependencyLogMessage(dep, "Waiting for"));
279 }
280 }
281
282 @PluginEventListener
283 public void onServiceDependencyWaitEnded(PluginServiceDependencyWaitEndedEvent event)
284 {
285 if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
286 {
287 OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
288 outstandingDependencies.remove(dep);
289 getLog().info(generateOutstandingDependencyLogMessage(dep, "Found"));
290 }
291 }
292
293 @PluginEventListener
294 public void onServiceDependencyWaitEnded(PluginServiceDependencyWaitTimedOutEvent event)
295 {
296 if (event.getPluginKey() != null && event.getPluginKey().equals(getKey()))
297 {
298 OutstandingDependency dep = new OutstandingDependency(event.getBeanName(), String.valueOf(event.getFilter()));
299 outstandingDependencies.remove(dep);
300 getLog().error(generateOutstandingDependencyLogMessage(dep, "Timeout waiting for "));
301 }
302 }
303
304 private String generateOutstandingDependencyLogMessage(OutstandingDependency dep, String action)
305 {
306 StringBuilder sb = new StringBuilder();
307 sb.append(action).append(" ");
308 sb.append("service '").append(dep.getBeanName()).append("' for plugin '").append(getKey()).append("' with filter ").append(dep.getFilter());
309 return sb.toString();
310 }
311
312
313
314
315
316
317
318
319
320 @PluginEventListener
321 public void onPluginContainerRefresh(final PluginContainerRefreshedEvent event) throws IllegalPluginStateException
322 {
323 if (getKey() == null)
324 {
325 throw new IllegalPluginStateException("Plugin key must be set");
326 }
327 if (getKey().equals(event.getPluginKey()))
328 {
329 outstandingDependencies.clear();
330 helper.setPluginContainer(event.getContainer());
331 if (!compareAndSetPluginState(PluginState.ENABLING, PluginState.ENABLED) && getPluginState() != PluginState.ENABLED)
332 {
333 log.warn("Ignoring the bean container that was just created for plugin " + getKey() + ". The plugin " +
334 "is in an invalid state, " + getPluginState() + ", that doesn't support a transition to " +
335 "enabled. Most likely, it was disabled due to a timeout.");
336 helper.setPluginContainer(null);
337 return;
338 }
339
340
341 if (treatPluginContainerCreationAsRefresh)
342 {
343 pluginEventManager.broadcast(new PluginRefreshedEvent(this));
344 }
345 else
346 {
347 treatPluginContainerCreationAsRefresh = true;
348 }
349 }
350 }
351
352
353
354
355
356
357 public <T> T autowire(final Class<T> clazz) throws IllegalPluginStateException
358 {
359 return autowire(clazz, AutowireStrategy.AUTOWIRE_AUTODETECT);
360 }
361
362
363
364
365
366
367 public <T> T autowire(final Class<T> clazz, final AutowireStrategy autowireStrategy) throws IllegalPluginStateException
368 {
369 return helper.getRequiredContainerAccessor().createBean(clazz);
370 }
371
372
373
374
375
376
377 public void autowire(final Object instance) throws IllegalStateException
378 {
379 autowire(instance, AutowireStrategy.AUTOWIRE_AUTODETECT);
380 }
381
382
383
384
385
386
387 public void autowire(final Object instance, final AutowireStrategy autowireStrategy) throws IllegalPluginStateException
388 {
389 helper.getRequiredContainerAccessor().injectBean(instance);
390 }
391
392
393
394
395
396
397
398
399 @Override
400 public Set<String> getRequiredPlugins() throws IllegalPluginStateException
401 {
402 return helper.getRequiredPlugins();
403 }
404
405 @Override
406 public String toString()
407 {
408 return getKey();
409 }
410
411
412
413
414
415
416 @Override
417 protected void installInternal() throws IllegalPluginStateException
418 {
419 log.debug("Installing OSGi plugin '{}'", getKey());
420 Bundle bundle = helper.install();
421 helper = new OsgiPluginInstalledHelper(bundle, packageAdmin);
422 }
423
424
425
426
427
428
429
430
431
432
433 @Override
434 protected synchronized PluginState enableInternal() throws OsgiContainerException, IllegalPluginStateException
435 {
436 log.debug("Enabling OSGi plugin '{}'", getKey());
437 PluginState stateResult;
438 try
439 {
440 if (getBundle().getState() == Bundle.ACTIVE)
441 {
442 log.debug("Plugin '{}' bundle is already active, not doing anything", getKey());
443 stateResult = PluginState.ENABLED;
444 }
445 else if ((getBundle().getState() == Bundle.RESOLVED) || (getBundle().getState() == Bundle.INSTALLED))
446 {
447 pluginEventManager.register(this);
448 if (!treatPluginContainerCreationAsRefresh)
449 {
450 stateResult = PluginState.ENABLING;
451
452 setPluginState(stateResult);
453 }
454 else
455 {
456 stateResult = PluginState.ENABLED;
457 }
458 log.debug("Plugin '{}' bundle is resolved or installed, starting.", getKey());
459 getBundle().start();
460 final BundleContext ctx = getBundle().getBundleContext();
461 helper.onEnable(createServiceTrackers(ctx));
462
463
464 ctx.addBundleListener(bundleStartStopListener);
465 }
466 else
467 {
468 throw new OsgiContainerException("Cannot enable the plugin '" + getKey() + "' when the bundle is not in the resolved or installed state: "
469 + getBundle().getState() + "(" + getBundle().getBundleId() + ")");
470 }
471
472
473
474 return (getPluginState() != PluginState.ENABLED ? stateResult : PluginState.ENABLED);
475 }
476 catch (final BundleException e)
477 {
478 log.error("Detected an error (BundleException) enabling the plugin '" + getKey() + "' : " + e.getMessage() + ". " +
479 " This error usually occurs when your plugin imports a package from another bundle with a specific version constraint " +
480 "and either the bundle providing that package doesn't meet those version constraints, or there is no bundle " +
481 "available that provides the specified package. For more details on how to fix this, see " +
482 "https://developer.atlassian.com/x/mQAN");
483 throw new OsgiContainerException("Cannot start plugin: " + getKey(), e);
484 }
485 }
486
487 private ServiceTracker[] createServiceTrackers(BundleContext ctx)
488 {
489 return new ServiceTracker[] {
490 new ServiceTracker(ctx, ModuleDescriptor.class.getName(),
491 new ModuleDescriptorServiceTrackerCustomizer(this, pluginEventManager)),
492 new ServiceTracker(ctx, ListableModuleDescriptorFactory.class.getName(),
493 new UnrecognizedModuleDescriptorServiceTrackerCustomizer(this, pluginEventManager))
494 };
495 }
496
497
498
499
500
501
502
503 @Override
504 protected synchronized void disableInternal() throws OsgiContainerException, IllegalPluginStateException
505 {
506
507 if (!requiresRestart())
508 {
509 try
510 {
511 if (getPluginState() == PluginState.DISABLING)
512 {
513 logAndClearOustandingDependencies();
514 }
515 helper.onDisable();
516 pluginEventManager.unregister(this);
517 getBundle().stop();
518 treatPluginContainerCreationAsRefresh = false;
519 }
520 catch (final BundleException e)
521 {
522 log.error("Detected an error (BundleException) disabling the plugin '" + getKey() + "' : " + e.getMessage() + ".");
523 throw new OsgiContainerException("Cannot stop plugin: " + getKey(), e);
524 }
525 }
526 }
527
528 private boolean requiresRestart()
529 {
530 return frameworkStarted && PluginUtils.doesPluginRequireRestart(this);
531 }
532
533 private void logAndClearOustandingDependencies()
534 {
535 for (OutstandingDependency dep : outstandingDependencies)
536 {
537 getLog().error(generateOutstandingDependencyLogMessage(dep, "Never resolved"));
538 }
539 outstandingDependencies.clear();
540 }
541
542
543
544
545
546
547 @Override
548 protected void uninstallInternal() throws OsgiContainerException, IllegalPluginStateException
549 {
550 int retryCount=0;
551 Exception rootCause = null;
552 final long sleepTime = 500L;
553 while (true)
554 {
555 try
556 {
557 if (getBundle().getState() != Bundle.UNINSTALLED)
558 {
559 pluginEventManager.unregister(this);
560 getBundle().uninstall();
561 helper.onUninstall();
562 setPluginState(PluginState.UNINSTALLED);
563 break;
564 }
565 else
566 {
567 log.debug("Trying to uninstall '{}', but this has already been uninstalled", getKey());
568 pluginEventManager.unregister(this);
569 break;
570 }
571 }
572 catch (final BundleException e)
573 {
574 rootCause = retryCount == 0 ? e : rootCause;
575 retryCount++;
576
577 if (retryCount < 3)
578 {
579 log.debug("Try {} will Retry again in {} mSecs", retryCount, sleepTime );
580 log.debug(e.getMessage(), e);
581 try {
582 Thread.sleep(sleepTime);
583 } catch (InterruptedException e1) {
584 throw new OsgiContainerException("Cannot uninstall bundle, as thread was interrupted");
585 }
586 }
587 else
588 {
589 log.error("Detected an error (BundleException) disabling the plugin '{}'." , getKey() );
590 log.error(rootCause.getMessage(), rootCause);
591 throw new OsgiContainerException("Cannot uninstall bundle " + getBundle().getSymbolicName());
592 }
593 }
594 }
595 }
596
597
598
599
600
601
602
603 void addModuleDescriptorElement(final String key, final Element element)
604 {
605 moduleElements.put(key, element);
606 }
607
608
609
610
611
612
613 void clearModuleDescriptor(String key)
614 {
615 removeModuleDescriptor(key);
616 }
617
618
619
620
621
622
623
624 Map<String, Element> getModuleElements()
625 {
626 return moduleElements;
627 }
628
629
630
631
632
633
634 private PackageAdmin extractPackageAdminFromOsgi(OsgiContainerManager mgr)
635 {
636
637 Bundle bundle = mgr.getBundles()[0];
638
639
640 final ServiceReference ref = bundle.getBundleContext()
641 .getServiceReference(PackageAdmin.class.getName());
642 return (PackageAdmin) bundle.getBundleContext()
643 .getService(ref);
644 }
645
646 public ContainerAccessor getContainerAccessor()
647 {
648 return helper.getContainerAccessor();
649 }
650
651 private static class OutstandingDependency
652 {
653 private final String beanName;
654 private final String filter;
655
656 public OutstandingDependency(String beanName, String filter)
657 {
658 this.beanName = beanName;
659 this.filter = filter;
660 }
661
662 public String getBeanName()
663 {
664 return beanName;
665 }
666
667 public String getFilter()
668 {
669 return filter;
670 }
671
672 @Override
673 public boolean equals(Object o)
674 {
675 if (this == o)
676 {
677 return true;
678 }
679 if (o == null || getClass() != o.getClass())
680 {
681 return false;
682 }
683
684 OutstandingDependency that = (OutstandingDependency) o;
685
686 if (beanName != null ? !beanName.equals(that.beanName) : that.beanName != null)
687 {
688 return false;
689 }
690 if (!filter.equals(that.filter))
691 {
692 return false;
693 }
694
695 return true;
696 }
697
698 @Override
699 public int hashCode()
700 {
701 int result = beanName != null ? beanName.hashCode() : 0;
702 result = 31 * result + filter.hashCode();
703 return result;
704 }
705 }
706 }