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.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   * Plugin that wraps an OSGi bundle that does contain a plugin descriptor.
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       * This plugin is dynamically loaded, so returns true.
86       * @return true
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             // Should never happen
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             // Should never happen
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                     // Should never happen
263                     throw new PluginException("Cannot find createBean method on registered bean factory: "+nativeBeanFactory, e);
264                 } catch (IllegalAccessException e)
265                 {
266                     // Should never happen
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                 // Should never happen
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             // Should never happen as Spring methods only throw runtime exceptions
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      * 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(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      * 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 DeferredServiceTrackerCustomizer 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(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          * Updates any local module descriptors that were created from the modified factory
414          */
415         public void modifiedService(ServiceReference serviceReference, Object o)
416         {
417             removedService(serviceReference, o);
418             addingService(serviceReference);
419         }
420 
421         /**
422          * Reverts any current module descriptors that were provided from the factory being removed into {@link
423          * UnrecognisedModuleDescriptor} instances.
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 }