1 package com.atlassian.plugin.osgi.factory;
2
3 import com.atlassian.plugin.AutowireCapablePlugin;
4 import com.atlassian.plugin.ModuleDescriptor;
5 import com.atlassian.plugin.PluginException;
6 import com.atlassian.plugin.PluginState;
7 import com.atlassian.plugin.event.PluginEventManager;
8 import com.atlassian.plugin.event.PluginEventListener;
9 import com.atlassian.plugin.event.events.PluginContainerRefreshedEvent;
10 import com.atlassian.plugin.event.events.PluginContainerFailedEvent;
11 import com.atlassian.plugin.event.events.PluginRefreshedEvent;
12 import com.atlassian.plugin.util.resource.AlternativeDirectoryResourceLoader;
13 import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor;
14 import com.atlassian.plugin.impl.AbstractPlugin;
15 import com.atlassian.plugin.impl.DynamicPlugin;
16 import com.atlassian.plugin.osgi.container.OsgiContainerException;
17 import com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory;
18
19 import org.apache.commons.lang.Validate;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import org.dom4j.Element;
23 import org.osgi.framework.*;
24 import org.osgi.util.tracker.ServiceTracker;
25 import org.osgi.util.tracker.ServiceTrackerCustomizer;
26
27 import java.io.InputStream;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35
36
37
38
39 public class OsgiPlugin extends AbstractPlugin implements AutowireCapablePlugin, DynamicPlugin
40 {
41 private final Bundle bundle;
42 private static final Log log = LogFactory.getLog(OsgiPlugin.class);
43 private boolean deletable = true;
44 private boolean bundled = false;
45 private volatile SpringContextAccessor springContextAccessor;
46 private ServiceTracker moduleDescriptorTracker;
47 private ServiceTracker unrecognisedModuleTracker;
48 private final Map<String, Element> moduleElements = new HashMap<String, Element>();
49 private final ClassLoader bundleClassLoader;
50 private final PluginEventManager pluginEventManager;
51 private volatile boolean treatSpringBeanFactoryCreationAsRefresh = false;
52
53 public OsgiPlugin(final Bundle bundle, final PluginEventManager pluginEventManager)
54 {
55 Validate.notNull(bundle, "The bundle is required");
56 this.bundle = bundle;
57 this.bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
58 pluginEventManager.register(this);
59 this.pluginEventManager = pluginEventManager;
60 }
61
62 public Bundle getBundle()
63 {
64 return bundle;
65 }
66
67 public void addModuleDescriptorElement(final String key, final Element element)
68 {
69 moduleElements.put(key, element);
70 }
71
72 public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException
73 {
74 return BundleClassLoaderAccessor.loadClass(bundle, clazz, callingClass);
75 }
76
77 public boolean isUninstallable()
78 {
79 return true;
80 }
81
82 public URL getResource(final String name)
83 {
84 return bundleClassLoader.getResource(name);
85 }
86
87 public InputStream getResourceAsStream(final String name)
88 {
89 return bundleClassLoader.getResourceAsStream(name);
90 }
91
92 public ClassLoader getClassLoader()
93 {
94 return bundleClassLoader;
95 }
96
97
98
99
100
101 public boolean isDynamicallyLoaded()
102 {
103 return true;
104 }
105
106 public boolean isDeleteable()
107 {
108 return deletable;
109 }
110
111 public void setDeletable(final boolean deletable)
112 {
113 this.deletable = deletable;
114 }
115
116 public boolean isBundledPlugin()
117 {
118 return bundled;
119 }
120
121 @PluginEventListener
122 public void onSpringContextFailed(PluginContainerFailedEvent event)
123 {
124 if (getKey() == null)
125 {
126 throw new IllegalStateException("Plugin key must be set");
127 }
128 if (getKey().equals(event.getPluginKey()))
129 {
130
131 log.error("Unable to start the Spring context for plugin "+getKey(), event.getCause());
132 setEnabled(false);
133 }
134 }
135
136 @PluginEventListener
137 public void onSpringContextRefresh(PluginContainerRefreshedEvent event)
138 {
139 if (getKey() == null)
140 {
141 throw new IllegalStateException("Plugin key must be set");
142 }
143 if (getKey().equals(event.getPluginKey()))
144 {
145 springContextAccessor = new SpringContextAccessor(event.getContainer());
146
147
148
149 if (treatSpringBeanFactoryCreationAsRefresh)
150 {
151 pluginEventManager.broadcast(new PluginRefreshedEvent(this));
152 }
153 else
154 {
155 treatSpringBeanFactoryCreationAsRefresh = true;
156 }
157 }
158 }
159
160 public void setBundled(final boolean bundled)
161 {
162 this.bundled = bundled;
163 }
164
165 public PluginState getPluginState()
166 {
167 if (Bundle.ACTIVE == bundle.getState() && shouldHaveSpringContext() && springContextAccessor == null)
168 {
169 return PluginState.ENABLING;
170 }
171 else
172 {
173 return super.getPluginState();
174 }
175 }
176
177 @Override
178 public boolean isEnabled()
179 {
180 return (Bundle.ACTIVE == bundle.getState()) && (!shouldHaveSpringContext() || springContextAccessor != null);
181 }
182
183 @Override
184 public void setEnabled(final boolean enabled) throws OsgiContainerException
185 {
186 if (enabled)
187 {
188 enable();
189 }
190 else
191 {
192 disable();
193 }
194 }
195
196 void enable() throws OsgiContainerException
197 {
198 try
199 {
200 if ((bundle.getState() == Bundle.RESOLVED) || (bundle.getState() == Bundle.INSTALLED))
201 {
202 bundle.start();
203 if (bundle.getBundleContext() != null)
204 {
205 final BundleContext ctx = bundle.getBundleContext();
206 moduleDescriptorTracker = new ServiceTracker(ctx, ModuleDescriptor.class.getName(),
207 new RegisteringServiceTrackerCustomizer());
208 moduleDescriptorTracker.open();
209 unrecognisedModuleTracker = new ServiceTracker(ctx, ListableModuleDescriptorFactory.class.getName(),
210 new UnrecognisedServiceTrackerCustomizer());
211 unrecognisedModuleTracker.open();
212
213
214
215 ctx.addBundleListener(new BundleListener()
216 {
217 public void bundleChanged(BundleEvent bundleEvent)
218 {
219 if (bundleEvent.getBundle() == bundle && bundleEvent.getType() == BundleEvent.STOPPED)
220 {
221 springContextAccessor = null;
222 }
223 }
224 });
225 }
226 }
227 }
228 catch (final BundleException e)
229 {
230 throw new OsgiContainerException("Cannot start plugin: " + getKey(), e);
231 }
232 }
233
234 void disable() throws OsgiContainerException
235 {
236 try
237 {
238 if (bundle.getState() == Bundle.ACTIVE)
239 {
240 if (moduleDescriptorTracker != null)
241 {
242 moduleDescriptorTracker.close();
243 }
244 if (unrecognisedModuleTracker != null)
245 {
246 unrecognisedModuleTracker.close();
247 }
248 bundle.stop();
249 moduleDescriptorTracker = null;
250 unrecognisedModuleTracker = null;
251 }
252 }
253 catch (final BundleException e)
254 {
255 throw new OsgiContainerException("Cannot stop plugin: " + getKey(), e);
256 }
257 }
258
259 public void close() throws OsgiContainerException
260 {
261 pluginEventManager.unregister(this);
262 try
263 {
264 if (bundle.getState() != Bundle.UNINSTALLED)
265 {
266 bundle.uninstall();
267 }
268 }
269 catch (final BundleException e)
270 {
271 throw new OsgiContainerException("Cannot uninstall bundle " + bundle.getSymbolicName());
272 }
273 }
274
275 private boolean shouldHaveSpringContext()
276 {
277 return bundle.getHeaders().get("Spring-Context") != null;
278 }
279
280 public <T> T autowire(final Class<T> clazz)
281 {
282 return autowire(clazz, AutowireStrategy.AUTOWIRE_AUTODETECT);
283 }
284
285 public <T> T autowire(final Class<T> clazz, final AutowireStrategy autowireStrategy)
286 {
287 return (springContextAccessor != null ? springContextAccessor.createBean(clazz, autowireStrategy) : null);
288 }
289
290 public void autowire(final Object instance)
291 {
292 autowire(instance, AutowireStrategy.AUTOWIRE_AUTODETECT);
293 }
294
295 public void autowire(final Object instance, final AutowireStrategy autowireStrategy)
296 {
297 if (springContextAccessor != null)
298 {
299 springContextAccessor.createBean(instance, autowireStrategy);
300 }
301 }
302
303 @Override
304 public String toString()
305 {
306 return getKey();
307 }
308
309 protected <T extends ModuleDescriptor> List<T> getModuleDescriptorsByDescriptorClass(final Class<T> descriptor)
310 {
311 final List<T> result = new ArrayList<T>();
312
313 for (final ModuleDescriptor<?> moduleDescriptor : getModuleDescriptors())
314 {
315 if (moduleDescriptor.getClass().isAssignableFrom(descriptor))
316 {
317 result.add((T) moduleDescriptor);
318 }
319 }
320 return result;
321 }
322
323
324
325
326
327 private class RegisteringServiceTrackerCustomizer implements ServiceTrackerCustomizer
328 {
329
330 public Object addingService(final ServiceReference serviceReference)
331 {
332 ModuleDescriptor descriptor = null;
333 if (serviceReference.getBundle() == bundle)
334 {
335 descriptor = (ModuleDescriptor) bundle.getBundleContext().getService(serviceReference);
336 addModuleDescriptor(descriptor);
337 log.info("Dynamically registered new module descriptor: " + descriptor.getCompleteKey());
338 }
339 return descriptor;
340 }
341
342 public void modifiedService(final ServiceReference serviceReference, final Object o)
343 {
344 if (serviceReference.getBundle() == bundle)
345 {
346 final ModuleDescriptor descriptor = (ModuleDescriptor) o;
347 addModuleDescriptor(descriptor);
348 log.info("Dynamically upgraded new module descriptor: " + descriptor.getCompleteKey());
349 }
350 }
351
352 public void removedService(final ServiceReference serviceReference, final Object o)
353 {
354 if (serviceReference.getBundle() == bundle)
355 {
356 final ModuleDescriptor descriptor = (ModuleDescriptor) o;
357 removeModuleDescriptor(descriptor.getKey());
358 log.info("Dynamically removed module descriptor: " + descriptor.getCompleteKey());
359 }
360 }
361 }
362
363
364
365
366
367
368
369
370 private class UnrecognisedServiceTrackerCustomizer implements ServiceTrackerCustomizer
371 {
372
373
374
375
376
377 public Object addingService(final ServiceReference serviceReference)
378 {
379 final ListableModuleDescriptorFactory factory = (ListableModuleDescriptorFactory) bundle.getBundleContext().getService(serviceReference);
380 for (final UnrecognisedModuleDescriptor unrecognised : getModuleDescriptorsByDescriptorClass(UnrecognisedModuleDescriptor.class))
381 {
382 final Element source = moduleElements.get(unrecognised.getKey());
383 if ((source != null) && factory.hasModuleDescriptor(source.getName()))
384 {
385 try
386 {
387 final ModuleDescriptor descriptor = factory.getModuleDescriptor(source.getName());
388 descriptor.init(unrecognised.getPlugin(), source);
389 addModuleDescriptor(descriptor);
390 log.info("Turned plugin module " + descriptor.getCompleteKey() + " into module " + descriptor);
391 }
392 catch (final IllegalAccessException e)
393 {
394 log.error("Unable to transform " + unrecognised.getKey() + " into actual plugin module using factory " + factory, e);
395 }
396 catch (final InstantiationException e)
397 {
398 log.error("Unable to transform " + unrecognised.getKey() + " into actual plugin module using factory " + factory, e);
399 }
400 catch (final ClassNotFoundException e)
401 {
402 log.error("Unable to transform " + unrecognised.getKey() + " into actual plugin module using factory " + factory, e);
403 }
404 }
405 }
406 return factory;
407 }
408
409
410
411
412 public void modifiedService(final ServiceReference serviceReference, final Object o)
413 {
414 removedService(serviceReference, o);
415 addingService(serviceReference);
416 }
417
418
419
420
421
422 public void removedService(final ServiceReference serviceReference, final Object o)
423 {
424 final ListableModuleDescriptorFactory factory = (ListableModuleDescriptorFactory) o;
425 for (final Class<ModuleDescriptor<?>> moduleDescriptorClass : factory.getModuleDescriptorClasses())
426 {
427 for (final ModuleDescriptor<?> descriptor : getModuleDescriptorsByDescriptorClass(moduleDescriptorClass))
428 {
429 final UnrecognisedModuleDescriptor unrecognisedModuleDescriptor = new UnrecognisedModuleDescriptor();
430 final Element source = moduleElements.get(descriptor.getKey());
431 if (source != null)
432 {
433 unrecognisedModuleDescriptor.init(OsgiPlugin.this, source);
434 unrecognisedModuleDescriptor.setErrorText(UnrecognisedModuleDescriptorFallbackFactory.DESCRIPTOR_TEXT);
435 addModuleDescriptor(unrecognisedModuleDescriptor);
436 log.info("Removed plugin module " + unrecognisedModuleDescriptor.getCompleteKey() + " as its factory was uninstalled");
437 }
438 }
439 }
440 }
441 }
442
443
444
445
446
447
448 private static final class SpringContextAccessor
449 {
450 private final Object nativeBeanFactory;
451 private final Method nativeCreateBeanMethod;
452 private final Method nativeAutowireBeanMethod;
453
454 public SpringContextAccessor(Object applicationContext)
455 {
456 Object beanFactory = null;
457 try
458 {
459 final Method m = applicationContext.getClass().getMethod("getAutowireCapableBeanFactory");
460 beanFactory = m.invoke(applicationContext);
461 }
462 catch (final NoSuchMethodException e)
463 {
464
465 throw new PluginException("Cannot find createBean method on registered bean factory: " + beanFactory, e);
466 }
467 catch (final IllegalAccessException e)
468 {
469
470 throw new PluginException("Cannot access createBean method", e);
471 }
472 catch (final InvocationTargetException e)
473 {
474 handleSpringMethodInvocationError(e);
475 }
476
477 nativeBeanFactory = beanFactory;
478 try
479 {
480 nativeCreateBeanMethod = beanFactory.getClass().getMethod("createBean", Class.class, int.class, boolean.class);
481 nativeAutowireBeanMethod = beanFactory.getClass().getMethod("autowireBeanProperties", Object.class, int.class, boolean.class);
482 }
483 catch (final NoSuchMethodException e)
484 {
485
486 throw new PluginException("Cannot find createBean method on registered bean factory: " + nativeBeanFactory, e);
487 }
488 }
489
490 private void handleSpringMethodInvocationError(final InvocationTargetException e)
491 {
492 if (e.getCause() instanceof Error)
493 {
494 throw (Error) e.getCause();
495 }
496 else if (e.getCause() instanceof RuntimeException)
497 {
498 throw (RuntimeException) e.getCause();
499 }
500 else
501 {
502
503 throw new PluginException("Unable to invoke createBean", e.getCause());
504 }
505 }
506
507 public <T> T createBean(final Class<T> clazz, final AutowireStrategy autowireStrategy)
508 {
509 if (nativeBeanFactory == null)
510 {
511 return null;
512 }
513
514 try
515 {
516 return (T) nativeCreateBeanMethod.invoke(nativeBeanFactory, clazz, autowireStrategy.ordinal(), false);
517 }
518 catch (final IllegalAccessException e)
519 {
520
521 throw new PluginException("Unable to access createBean method", e);
522 }
523 catch (final InvocationTargetException e)
524 {
525 handleSpringMethodInvocationError(e);
526 return null;
527 }
528 }
529
530 public void createBean(final Object instance, final AutowireStrategy autowireStrategy)
531 {
532 if (nativeBeanFactory == null)
533 {
534 return;
535 }
536
537 try
538 {
539 nativeAutowireBeanMethod.invoke(nativeBeanFactory, instance, autowireStrategy.ordinal(), false);
540 }
541 catch (final IllegalAccessException e)
542 {
543
544 throw new PluginException("Unable to access createBean method", e);
545 }
546 catch (final InvocationTargetException e)
547 {
548 handleSpringMethodInvocationError(e);
549 }
550 }
551 }
552
553 }