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