View Javadoc

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   * Plugin that wraps an OSGi bundle that does contain a plugin descriptor.
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       * This plugin is dynamically loaded, so returns true.
99       * @return true
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             // TODO: do something with the exception more than logging
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             // Only send refresh event on second creation
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                     // ensure the bean factory is removed when the bundle is stopped
214                     // Do we need to unregister this?
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      * Tracks module descriptors registered as services, then updates the descriptors map accordingly
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      * Service tracker that tracks {@link ListableModuleDescriptorFactory} instances and handles transforming
365      * {@link UnrecognisedModuleDescriptor}} instances into modules if the new factory supports them.  Updates to factories
366      * and removal are also handled.
367      *
368      * @since 2.1.2
369      */
370     private class UnrecognisedServiceTrackerCustomizer implements ServiceTrackerCustomizer
371     {
372 
373         /**
374          * Turns any {@link UnrecognisedModuleDescriptor} modules that can be handled by the new factory into real
375          * modules
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          * Updates any local module descriptors that were created from the modified factory
411          */
412         public void modifiedService(final ServiceReference serviceReference, final Object o)
413         {
414             removedService(serviceReference, o);
415             addingService(serviceReference);
416         }
417 
418         /**
419          * Reverts any current module descriptors that were provided from the factory being removed into {@link
420          * UnrecognisedModuleDescriptor} instances.
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      * Manages spring context access, including autowiring.
445      *
446      * @since 2.2.0
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                 // Should never happen
465                 throw new PluginException("Cannot find createBean method on registered bean factory: " + beanFactory, e);
466             }
467             catch (final IllegalAccessException e)
468             {
469                 // Should never happen
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                 // Should never happen
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                 // Should never happen as Spring methods only throw runtime exceptions
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                 // Should never happen
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                 // Should never happen
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 }