1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.AutowireCapablePlugin;
4   import com.atlassian.plugin.IllegalPluginStateException;
5   import com.atlassian.plugin.PluginException;
6   import com.atlassian.plugin.osgi.container.OsgiContainerException;
7   import com.atlassian.plugin.osgi.util.OsgiHeaderUtil;
8   import com.atlassian.plugin.osgi.util.BundleClassLoaderAccessor;
9   import com.atlassian.plugin.util.resource.AlternativeDirectoryResourceLoader;
10  
11  import org.apache.commons.lang.Validate;
12  import org.osgi.framework.Bundle;
13  import org.osgi.framework.Constants;
14  import org.osgi.service.packageadmin.ExportedPackage;
15  import org.osgi.service.packageadmin.PackageAdmin;
16  import org.osgi.util.tracker.ServiceTracker;
17  
18  import java.io.InputStream;
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.net.URL;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  /**
26   * Helper class that implements the methods assuming the OSGi plugin has been installed
27   *
28   * @since 2.2.0
29   */
30  class OsgiPluginInstalledHelper implements OsgiPluginHelper
31  {
32      private final ClassLoader bundleClassLoader;
33      private final Bundle bundle;
34      private final PackageAdmin packageAdmin;
35      private final boolean requireSpring;
36  
37      private volatile SpringContextAccessor springContextAccessor;
38      private ServiceTracker[] serviceTrackers;
39  
40      /**
41       * @param bundle The bundle
42       * @param packageAdmin The package admin
43       * @param requireSpring Whether spring is required for autowiring
44       */
45      public OsgiPluginInstalledHelper(final Bundle bundle, final PackageAdmin packageAdmin, final boolean requireSpring)
46      {
47          Validate.notNull(bundle);
48          Validate.notNull(packageAdmin);
49          this.bundle = bundle;
50          bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
51          this.packageAdmin = packageAdmin;
52          this.requireSpring = requireSpring;
53      }
54  
55      public Bundle getBundle()
56      {
57          return bundle;
58      }
59  
60      public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException
61      {
62          return BundleClassLoaderAccessor.loadClass(getBundle(), clazz);
63      }
64  
65      public URL getResource(final String name)
66      {
67          return bundleClassLoader.getResource(name);
68      }
69  
70      public InputStream getResourceAsStream(final String name)
71      {
72          return bundleClassLoader.getResourceAsStream(name);
73      }
74  
75      public ClassLoader getClassLoader()
76      {
77          return bundleClassLoader;
78      }
79  
80      public Bundle install()
81      {
82          throw new IllegalPluginStateException("Plugin '" + bundle.getSymbolicName() + "' has already been installed");
83      }
84  
85      public void onEnable(final ServiceTracker... serviceTrackers) throws OsgiContainerException
86      {
87          Validate.notNull(serviceTrackers);
88          this.serviceTrackers = serviceTrackers;
89          for (final ServiceTracker svc : serviceTrackers)
90          {
91              svc.open();
92          }
93      }
94  
95      public void onDisable() throws OsgiContainerException
96      {
97          for (final ServiceTracker svc : serviceTrackers)
98          {
99              svc.close();
100         }
101         serviceTrackers = null;
102         setPluginContainer(null);
103     }
104 
105     public void onUninstall() throws OsgiContainerException
106     {
107     }
108 
109     /**
110      * If spring is required, it looks for the spring application context, and calls createBean().  If not, the class
111      * is instantiated with its default constructor.
112      *
113      * @param clazz The class to autowire The class to create
114      * @param autowireStrategy The autowire strategy to use The strategy to use, only respected if spring is available
115      * @param <T> The class type
116      * @return The autowired instance
117      * @throws IllegalPluginStateException If spring is required but not available
118      */
119     public <T> T autowire(final Class<T> clazz, final AutowireCapablePlugin.AutowireStrategy autowireStrategy) throws IllegalPluginStateException
120     {
121         if (requireSpring)
122         {
123             assertSpringContextAvailable();
124             return springContextAccessor.createBean(clazz, autowireStrategy);
125         }
126         else
127         {
128             try
129             {
130                 return clazz.newInstance();
131             }
132             catch (final InstantiationException e)
133             {
134                 throw new PluginException("Unable to instantiate " + clazz, e);
135             }
136             catch (final IllegalAccessException e)
137             {
138                 throw new PluginException("Unable to access " + clazz, e);
139             }
140         }
141     }
142 
143     /**
144      * If spring is required, it looks for the spring application context and calls autowire().  If not, the object
145      * is untouched.
146      *
147      * @param instance The instance to autowire
148      * @param autowireStrategy The autowire strategy to use The strategy to use, only respected if spring is available
149      * @return The autowired instance
150      * @throws IllegalPluginStateException If spring is required but not available
151      */
152     public void autowire(final Object instance, final AutowireCapablePlugin.AutowireStrategy autowireStrategy) throws IllegalPluginStateException
153     {
154         // Only do anything if spring is required
155         if (requireSpring)
156         {
157             assertSpringContextAvailable();
158             springContextAccessor.createBean(instance, autowireStrategy);
159         }
160     }
161 
162     public Set<String> getRequiredPlugins()
163     {
164         final Set<String> keys = new HashSet<String>();
165 
166         // Get a set of all packages that this plugin imports
167         final Set<String> imports = OsgiHeaderUtil.parseHeader((String) getBundle().getHeaders().get(Constants.IMPORT_PACKAGE)).keySet();
168 
169         // For each import, determine what bundle provides the package
170         for (final String imp : imports)
171         {
172             // Get a list of package exports for this package
173             final ExportedPackage[] exports = packageAdmin.getExportedPackages(imp);
174             if (exports != null)
175             {
176                 // For each exported package, determine if we are a consumer
177                 for (final ExportedPackage export : exports)
178                 {
179                     // Get a list of bundles that consume that package
180                     final Bundle[] importingBundles = export.getImportingBundles();
181                     if (importingBundles != null)
182                     {
183                         // For each importing bundle, determine if it is us
184                         for (final Bundle importingBundle : importingBundles)
185                         {
186                             // If we are the bundle consumer, or importer, then add the exporter as a required plugin
187                             if (getBundle() == importingBundle)
188                             {
189                                 keys.add(OsgiHeaderUtil.getPluginKey(export.getExportingBundle()));
190                                 break;
191                             }
192                         }
193                     }
194                 }
195             }
196         }
197         return keys;
198     }
199 
200     public void setPluginContainer(final Object container)
201     {
202         if (container == null)
203         {
204             springContextAccessor = null;
205         }
206         else
207         {
208             springContextAccessor = new SpringContextAccessor(container);
209         }
210     }
211 
212     /**
213      * @throws IllegalPluginStateException if the spring context is not initialized
214      */
215     private void assertSpringContextAvailable() throws IllegalPluginStateException
216     {
217         if (springContextAccessor == null)
218         {
219             throw new IllegalStateException("Cannot autowire object because the Spring context is unavailable.  " +
220                 "Ensure your OSGi bundle contains the 'Spring-Context' header.");
221         }
222     }
223 
224     /**
225      * Manages spring context access, including autowiring.
226      *
227      * @since 2.2.0
228      */
229     private static final class SpringContextAccessor
230     {
231         private final Object nativeBeanFactory;
232         private final Method nativeCreateBeanMethod;
233         private final Method nativeAutowireBeanMethod;
234 
235         public SpringContextAccessor(final Object applicationContext)
236         {
237             Object beanFactory = null;
238             try
239             {
240                 final Method m = applicationContext.getClass().getMethod("getAutowireCapableBeanFactory");
241                 beanFactory = m.invoke(applicationContext);
242             }
243             catch (final NoSuchMethodException e)
244             {
245                 // Should never happen
246                 throw new PluginException("Cannot find createBean method on registered bean factory: " + beanFactory, e);
247             }
248             catch (final IllegalAccessException e)
249             {
250                 // Should never happen
251                 throw new PluginException("Cannot access createBean method", e);
252             }
253             catch (final InvocationTargetException e)
254             {
255                 handleSpringMethodInvocationError(e);
256             }
257 
258             nativeBeanFactory = beanFactory;
259             try
260             {
261                 nativeCreateBeanMethod = beanFactory.getClass().getMethod("createBean", Class.class, int.class, boolean.class);
262                 nativeAutowireBeanMethod = beanFactory.getClass().getMethod("autowireBeanProperties", Object.class, int.class, boolean.class);
263             }
264             catch (final NoSuchMethodException e)
265             {
266                 // Should never happen
267                 throw new PluginException("Cannot find createBean method on registered bean factory: " + nativeBeanFactory, e);
268             }
269         }
270 
271         private void handleSpringMethodInvocationError(final InvocationTargetException e)
272         {
273             if (e.getCause() instanceof Error)
274             {
275                 throw (Error) e.getCause();
276             }
277             else if (e.getCause() instanceof RuntimeException)
278             {
279                 throw (RuntimeException) e.getCause();
280             }
281             else
282             {
283                 // Should never happen as Spring methods only throw runtime exceptions
284                 throw new PluginException("Unable to invoke createBean", e.getCause());
285             }
286         }
287 
288         public <T> T createBean(final Class<T> clazz, final AutowireCapablePlugin.AutowireStrategy autowireStrategy)
289         {
290             if (nativeBeanFactory == null)
291             {
292                 return null;
293             }
294 
295             try
296             {
297                 return clazz.cast(nativeCreateBeanMethod.invoke(nativeBeanFactory, clazz, autowireStrategy.ordinal(), false));
298             }
299             catch (final IllegalAccessException e)
300             {
301                 // Should never happen
302                 throw new PluginException("Unable to access createBean method", e);
303             }
304             catch (final InvocationTargetException e)
305             {
306                 handleSpringMethodInvocationError(e);
307                 return null;
308             }
309         }
310 
311         public void createBean(final Object instance, final AutowireCapablePlugin.AutowireStrategy autowireStrategy)
312         {
313             if (nativeBeanFactory == null)
314             {
315                 return;
316             }
317 
318             try
319             {
320                 nativeAutowireBeanMethod.invoke(nativeBeanFactory, instance, autowireStrategy.ordinal(), false);
321             }
322             catch (final IllegalAccessException e)
323             {
324                 // Should never happen
325                 throw new PluginException("Unable to access createBean method", e);
326             }
327             catch (final InvocationTargetException e)
328             {
329                 handleSpringMethodInvocationError(e);
330             }
331         }
332     }
333 
334 }