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