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