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