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.impl.AbstractPlugin;
7   import com.atlassian.plugin.impl.DynamicPlugin;
8   import com.atlassian.plugin.osgi.container.OsgiContainerException;
9   import org.apache.commons.lang.Validate;
10  import org.apache.commons.logging.Log;
11  import org.apache.commons.logging.LogFactory;
12  import org.osgi.framework.*;
13  import org.osgi.util.tracker.ServiceTracker;
14  import org.osgi.util.tracker.ServiceTrackerCustomizer;
15  
16  import java.io.InputStream;
17  import java.lang.reflect.InvocationTargetException;
18  import java.lang.reflect.Method;
19  import java.net.URL;
20  
21  /**
22   * Plugin that wraps an OSGi bundle that does contain a plugin descriptor.
23   */
24  public class OsgiPlugin extends AbstractPlugin implements AutowireCapablePlugin, DynamicPlugin
25  {
26      private final Bundle bundle;
27      private static final Log log = LogFactory.getLog(OsgiPlugin.class);
28      private boolean deletable = true;
29      private boolean bundled = false;
30      private Object nativeBeanFactory;
31      private Method nativeCreateBeanMethod;
32      private Method nativeAutowireBeanMethod;
33      private ServiceTracker moduleDescriptorTracker;
34  
35      public OsgiPlugin(Bundle bundle)
36      {
37          Validate.notNull(bundle, "The bundle is required");
38          this.bundle = bundle;
39      }
40  
41      public Bundle getBundle()
42      {
43          return bundle;
44      }
45  
46      public Class loadClass(String clazz, Class callingClass) throws ClassNotFoundException
47      {
48          return BundleClassLoaderAccessor.loadClass(bundle, clazz, callingClass);
49      }
50  
51      public boolean isUninstallable()
52      {
53          return true;
54      }
55  
56      public URL getResource(String name)
57      {
58          return BundleClassLoaderAccessor.getResource(bundle, name);
59      }
60  
61      public InputStream getResourceAsStream(String name)
62      {
63          return BundleClassLoaderAccessor.getResourceAsStream(bundle, name);
64      }
65  
66      public ClassLoader getClassLoader()
67      {
68          return BundleClassLoaderAccessor.getClassLoader(bundle);
69      }
70  
71      /**
72       * This plugin is dynamically loaded, so returns true.
73       * @return true
74       */
75      public boolean isDynamicallyLoaded()
76      {
77          return true;
78      }
79  
80  
81      public boolean isDeleteable()
82      {
83          return deletable;
84      }
85  
86      public void setDeletable(boolean deletable)
87      {
88          this.deletable = deletable;
89      }
90  
91      public boolean isBundledPlugin()
92      {
93          return bundled;
94      }
95  
96      public void setBundled(boolean bundled)
97      {
98          this.bundled = bundled;
99      }
100 
101     public synchronized boolean isEnabled()
102     {
103         return Bundle.ACTIVE == bundle.getState() && (!shouldHaveSpringContext() || ensureNativeBeanFactory());
104     }
105 
106     public synchronized void setEnabled(boolean enabled) throws OsgiContainerException
107     {
108         if (enabled) enable(); else disable();
109     }
110 
111     void enable() throws OsgiContainerException
112     {
113         try
114         {
115             if (bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.INSTALLED)
116             {
117                 bundle.start();
118                 if (bundle.getBundleContext() != null)
119                 {
120                     moduleDescriptorTracker = new ServiceTracker(bundle.getBundleContext(), ModuleDescriptor.class.getName(), new RegisteringServiceTrackerCustomizer());
121                     moduleDescriptorTracker.open();
122                 }
123             }
124         }
125         catch (BundleException e)
126         {
127             throw new OsgiContainerException("Cannot start plugin: "+getKey(), e);
128         }
129     }
130 
131     void disable() throws OsgiContainerException
132     {
133         try
134         {
135             if (bundle.getState() == Bundle.ACTIVE)
136             {
137                 if (moduleDescriptorTracker != null) moduleDescriptorTracker.close();
138                 bundle.stop();
139                 moduleDescriptorTracker = null;
140                 nativeBeanFactory = null;
141                 nativeCreateBeanMethod = null;
142             }
143         }
144         catch (BundleException e)
145         {
146             throw new OsgiContainerException("Cannot stop plugin: "+getKey(), e);
147         }
148     }
149 
150     public synchronized void close() throws OsgiContainerException
151     {
152         try
153         {
154             if (bundle.getState() != Bundle.UNINSTALLED)
155                 bundle.uninstall();
156         }
157         catch (BundleException e)
158         {
159             throw new OsgiContainerException("Cannot uninstall bundle " + bundle.getSymbolicName());
160         }
161     }
162 
163     private boolean shouldHaveSpringContext()
164     {
165         return bundle.getHeaders().get("Spring-Context") != null;
166     }
167 
168     public <T> T autowire(Class<T> clazz)
169     {
170         return autowire(clazz, AutowireStrategy.AUTOWIRE_AUTODETECT);
171     }
172 
173     public synchronized <T> T autowire(Class<T> clazz, AutowireStrategy autowireStrategy)
174     {
175         if (!ensureNativeBeanFactory())
176             return null;
177         
178         try
179         {
180             return (T) nativeCreateBeanMethod.invoke(nativeBeanFactory, clazz, autowireStrategy.ordinal(), false);
181         }
182         catch (IllegalAccessException e)
183         {
184             // Should never happen
185             throw new PluginException("Unable to access createBean method", e);
186         }
187         catch (InvocationTargetException e)
188         {
189             handleSpringMethodInvocationError(e);
190             return null;
191         }
192     }
193 
194     public void autowire(Object instance)
195     {
196         autowire(instance, AutowireStrategy.AUTOWIRE_AUTODETECT);
197     }
198 
199     public void autowire(Object instance, AutowireStrategy autowireStrategy)
200     {
201         if (!ensureNativeBeanFactory())
202             return;
203 
204         try
205         {
206             nativeAutowireBeanMethod.invoke(nativeBeanFactory, instance, autowireStrategy.ordinal(), false);
207         }
208         catch (IllegalAccessException e)
209         {
210             // Should never happen
211             throw new PluginException("Unable to access createBean method", e);
212         }
213         catch (InvocationTargetException e)
214         {
215             handleSpringMethodInvocationError(e);
216         }
217     }
218 
219     private boolean ensureNativeBeanFactory()
220     {
221         if (nativeBeanFactory == null)
222         {
223 
224             try
225             {
226                 BundleContext ctx = bundle.getBundleContext();
227                 if (ctx == null)
228                 {
229                     log.warn("no bundle context - we are screwed");
230                     return false;
231                 }
232                 ServiceReference[] services = ctx.getServiceReferences("org.springframework.context.ApplicationContext", "(org.springframework.context.service.name="+bundle.getSymbolicName()+")");
233                 if (services == null || services.length == 0)
234                 {
235                     log.debug("No spring bean factory found...yet");
236                     return false;
237                 }
238 
239                 Object applicationContext = ctx.getService(services[0]);
240                 try
241                 {
242                     Method m = applicationContext.getClass().getMethod("getAutowireCapableBeanFactory");
243                     nativeBeanFactory = m.invoke(applicationContext);
244                 } catch (NoSuchMethodException e)
245                 {
246                     // Should never happen
247                     throw new PluginException("Cannot find createBean method on registered bean factory: "+nativeBeanFactory, e);
248                 } catch (IllegalAccessException e)
249                 {
250                     // Should never happen
251                     throw new PluginException("Cannot access createBean method", e);
252                 } catch (InvocationTargetException e)
253                 {
254                     handleSpringMethodInvocationError(e);
255                     return false;
256                 }
257             } catch (InvalidSyntaxException e)
258             {
259                 throw new OsgiContainerException("Invalid LDAP filter", e);
260             }
261 
262 
263             try
264             {
265                 nativeCreateBeanMethod = nativeBeanFactory.getClass().getMethod("createBean", Class.class, int.class, boolean.class);
266                 nativeAutowireBeanMethod = nativeBeanFactory.getClass().getMethod("autowireBeanProperties", Object.class, int.class, boolean.class);
267             } catch (NoSuchMethodException e)
268             {
269                 // Should never happen
270                 throw new PluginException("Cannot find createBean method on registered bean factory: "+nativeBeanFactory, e);
271             }
272         }
273         return nativeBeanFactory != null && nativeCreateBeanMethod != null && nativeAutowireBeanMethod != null;
274     }
275 
276     private void handleSpringMethodInvocationError(InvocationTargetException e)
277     {
278         if (e.getCause() instanceof Error)
279             throw (Error) e.getCause();
280         else if (e.getCause() instanceof RuntimeException)
281             throw (RuntimeException) e.getCause();
282         else
283         {
284             // Should never happen as Spring methods only throw runtime exceptions
285             throw new PluginException("Unable to invoke createBean", e.getCause());
286         }
287     }
288 
289     public String toString()
290     {
291         return getKey();
292     }
293 
294     /**
295      * Tracks module descriptors registered as services, then updates the descriptors map accordingly
296      */
297     private class RegisteringServiceTrackerCustomizer implements ServiceTrackerCustomizer
298     {
299 
300         public Object addingService(ServiceReference serviceReference)
301         {
302             if (serviceReference.getBundle() == bundle)
303             {
304                 ModuleDescriptor descriptor = (ModuleDescriptor) bundle.getBundleContext().getService(serviceReference);
305                 addModuleDescriptor(descriptor);
306                 return descriptor;
307             }
308             return null;
309         }
310 
311         public void modifiedService(ServiceReference serviceReference, Object o)
312         {
313             if (serviceReference.getBundle() == bundle)
314             {
315                 ModuleDescriptor descriptor = (ModuleDescriptor) o;
316                 addModuleDescriptor(descriptor);
317             }
318         }
319 
320         public void removedService(ServiceReference serviceReference, Object o)
321         {
322             if (serviceReference.getBundle() == bundle)
323             {
324                 ModuleDescriptor descriptor = (ModuleDescriptor) o;
325                 removeModuleDescriptor(descriptor.getKey());
326             }
327         }
328     }
329 }