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.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   * Plugin that wraps an OSGi bundle that does contain a plugin descriptor.
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       * This plugin is dynamically loaded, so returns true.
97       * @return true
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             // Should never happen
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             // Should never happen
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                     // Should never happen
297                     throw new PluginException("Cannot find createBean method on registered bean factory: " + nativeBeanFactory, e);
298                 }
299                 catch (final IllegalAccessException e)
300                 {
301                     // Should never happen
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                 // Should never happen
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             // Should never happen as Spring methods only throw runtime exceptions
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      * Tracks module descriptors registered as services, then updates the descriptors map accordingly
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      * Service tracker that tracks {@link ListableModuleDescriptorFactory} instances and handles transforming
407      * {@link UnrecognisedModuleDescriptor}} instances into modules if the new factory supports them.  Updates to factories
408      * and removal are also handled.
409      *
410      * @since 2.1.2
411      */
412     private class UnrecognisedServiceTrackerCustomizer implements ServiceTrackerCustomizer
413     {
414 
415         /**
416          * Turns any {@link UnrecognisedModuleDescriptor} modules that can be handled by the new factory into real
417          * modules
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          * Updates any local module descriptors that were created from the modified factory
453          */
454         public void modifiedService(final ServiceReference serviceReference, final Object o)
455         {
456             removedService(serviceReference, o);
457             addingService(serviceReference);
458         }
459 
460         /**
461          * Reverts any current module descriptors that were provided from the factory being removed into {@link
462          * UnrecognisedModuleDescriptor} instances.
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 }