View Javadoc
1   package com.atlassian.plugin.osgi.spring;
2   
3   import com.atlassian.plugin.PluginException;
4   import com.atlassian.plugin.module.ContainerAccessor;
5   
6   import java.lang.reflect.InvocationTargetException;
7   import java.lang.reflect.Method;
8   import java.util.Collection;
9   import java.util.Map;
10  import java.util.Objects;
11  import java.util.stream.Stream;
12  
13  import static com.google.common.base.Preconditions.checkState;
14  
15  /**
16   * Manages spring context access, including autowiring.
17   *
18   * @since 2.2.0
19   */
20  public class DefaultSpringContainerAccessor implements ContainerAccessor {
21      private final Object nativeBeanFactory;
22      private final Method nativeCreateBeanMethod;
23      private final Method nativeAutowireBeanMethod;
24      private final Method nativeGetBeanMethod;
25      private final Method nativeGetBeansOfTypeMethod;
26  
27      /**
28       * The autowire strategy to use when creating and wiring a bean
29       */
30      public enum AutowireStrategy {
31          AUTOWIRE_NO,
32          /**
33           * Performs setter-based injection by name
34           */
35          AUTOWIRE_BY_NAME,
36  
37          /**
38           * Performs setter-based injection by type
39           */
40          AUTOWIRE_BY_TYPE,
41  
42          /**
43           * Performs construction-based injection by type
44           */
45          AUTOWIRE_BY_CONSTRUCTOR,
46  
47          /**
48           * Autodetects appropriate injection by first seeing if any no-arg constructors exist. If not, performs constructor
49           * injection, and if so, autowires by type then name
50           *
51           * @deprecated in 5.0 for removal in 6.0
52           */
53          @Deprecated
54          AUTOWIRE_AUTODETECT
55      }
56  
57      public DefaultSpringContainerAccessor(final Object applicationContext) {
58          Object beanFactory = null;
59          try {
60              final Method m = applicationContext.getClass().getMethod("getAutowireCapableBeanFactory");
61              beanFactory = m.invoke(applicationContext);
62          } catch (final NoSuchMethodException e) {
63              // Should never happen
64              throw new PluginException("Cannot find getAutowireCapableBeanFactory method on " + applicationContext.getClass(), e);
65          } catch (final IllegalAccessException e) {
66              // Should never happen
67              throw new PluginException("Cannot access getAutowireCapableBeanFactory method", e);
68          } catch (final InvocationTargetException e) {
69              handleSpringMethodInvocationError(e);
70          }
71  
72          nativeBeanFactory = beanFactory;
73          try {
74              nativeCreateBeanMethod = beanFactory.getClass().getMethod("createBean", Class.class, int.class, boolean.class);
75              nativeAutowireBeanMethod = beanFactory.getClass().getMethod("autowireBean", Object.class);
76              nativeGetBeanMethod = beanFactory.getClass().getMethod("getBean", String.class);
77              nativeGetBeansOfTypeMethod = beanFactory.getClass().getMethod("getBeansOfType", Class.class);
78  
79              checkState(Stream.of(nativeGetBeansOfTypeMethod, nativeAutowireBeanMethod, nativeCreateBeanMethod, nativeGetBeanMethod).allMatch(Objects::nonNull));
80          } catch (final NoSuchMethodException e) {
81              // Should never happen
82              throw new PluginException("Cannot find one or more methods on registered bean factory: " + nativeBeanFactory, e);
83          }
84      }
85  
86      private void handleSpringMethodInvocationError(final InvocationTargetException e) {
87          if (e.getCause() instanceof Error) {
88              throw (Error) e.getCause();
89          } else if (e.getCause() instanceof RuntimeException) {
90              throw (RuntimeException) e.getCause();
91          } else {
92              // Should never happen as Spring methods only throw runtime exceptions
93              throw new PluginException("Unable to invoke createBean", e.getCause());
94          }
95      }
96  
97      public <T> T createBean(final Class<T> clazz) {
98          try {
99              return clazz.cast(nativeCreateBeanMethod.invoke(nativeBeanFactory, clazz, AutowireStrategy.AUTOWIRE_BY_CONSTRUCTOR.ordinal(), false));
100         } catch (final IllegalAccessException e) {
101             // Should never happen
102             throw new PluginException("Unable to access createBean method", e);
103         } catch (final InvocationTargetException e) {
104             handleSpringMethodInvocationError(e);
105             return null;
106         }
107     }
108 
109     @Override
110     public <T> T injectBean(T bean) {
111         try {
112             nativeAutowireBeanMethod.invoke(nativeBeanFactory, bean);
113         } catch (final IllegalAccessException e) {
114             // Should never happen
115             throw new PluginException("Unable to access autowireBean method", e);
116         } catch (final InvocationTargetException e) {
117             handleSpringMethodInvocationError(e);
118         }
119         return bean;
120     }
121 
122     public <T> Collection<T> getBeansOfType(Class<T> interfaceClass) {
123         try {
124             //noinspection unchecked
125             Map<String, T> beans = (Map<String, T>) nativeGetBeansOfTypeMethod.invoke(nativeBeanFactory, interfaceClass);
126             return beans.values();
127         } catch (final IllegalAccessException e) {
128             // Should never happen
129             throw new PluginException("Unable to access getBeansOfType method", e);
130         } catch (final InvocationTargetException e) {
131             handleSpringMethodInvocationError(e);
132             return null;
133         }
134     }
135 
136     @Override
137     public <T> T getBean(final String id) {
138         try {
139             //noinspection unchecked
140             return (T) nativeGetBeanMethod.invoke(nativeBeanFactory, id);
141         } catch (final IllegalAccessException e) {
142             // Should never happen
143             throw new PluginException("Unable to access getBean method", e);
144         } catch (final InvocationTargetException e) {
145             handleSpringMethodInvocationError(e);
146             return null;
147         }
148     }
149 }