1 package com.atlassian.plugin.spring;
2
3 import com.atlassian.plugin.osgi.hostcomponents.ComponentRegistrar;
4 import com.atlassian.plugin.osgi.hostcomponents.ContextClassLoaderStrategy;
5 import com.atlassian.plugin.osgi.hostcomponents.HostComponentProvider;
6 import com.google.common.collect.ImmutableMap;
7 import com.google.common.collect.ImmutableSet;
8 import com.google.common.collect.Sets;
9 import org.apache.commons.lang.ClassUtils;
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12 import org.springframework.aop.support.AopUtils;
13 import org.springframework.beans.factory.BeanFactory;
14 import org.springframework.beans.factory.BeanIsAbstractException;
15 import org.springframework.beans.factory.HierarchicalBeanFactory;
16 import org.springframework.beans.factory.ListableBeanFactory;
17 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
18 import org.springframework.beans.factory.annotation.Autowired;
19 import org.springframework.beans.factory.config.AbstractFactoryBean;
20 import org.springframework.core.annotation.AnnotationUtils;
21
22 import java.util.*;
23
24 import static com.atlassian.plugin.osgi.hostcomponents.ContextClassLoaderStrategy.USE_HOST;
25 import static com.atlassian.plugin.util.Assertions.notNull;
26
27 public class SpringHostComponentProviderFactoryBean extends AbstractFactoryBean
28 {
29 private static final Logger log = LoggerFactory.getLogger(SpringHostComponentProviderFactoryBean.class);
30
31 private SpringHostComponentProviderConfig springHostComponentProviderConfig;
32
33
34
35
36 private Set<String> beanNames = Collections.emptySet();
37
38
39
40
41
42 private Map<String, Class[]> beanInterfaces = Collections.emptyMap();
43
44
45
46
47
48 private Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies = Collections.emptyMap();
49
50 private Set<String> bundleTrackingBeans = Collections.emptySet();
51
52 public Class getObjectType()
53 {
54 return HostComponentProvider.class;
55 }
56
57 protected Object createInstance() throws Exception
58 {
59 if (springHostComponentProviderConfig == null)
60 {
61 return new SpringHostComponentProvider(getBeanFactory(), beanNames, beanInterfaces,
62 beanContextClassLoaderStrategies, bundleTrackingBeans, false);
63 }
64 else
65 {
66
67 return new SpringHostComponentProvider(getBeanFactory(),
68 Sets.union(beanNames, springHostComponentProviderConfig.getBeanNames()),
69 new ImmutableMap.Builder().putAll(beanInterfaces).putAll(springHostComponentProviderConfig.getBeanInterfaces()).build(),
70 new ImmutableMap.Builder().putAll(beanContextClassLoaderStrategies).putAll(springHostComponentProviderConfig.getBeanContextClassLoaderStrategies()).build(),
71 new ImmutableSet.Builder().addAll(bundleTrackingBeans).addAll(springHostComponentProviderConfig.getBundleTrackingBeans()).build(),
72 springHostComponentProviderConfig.isUseAnnotation());
73 }
74 }
75
76 @Autowired(required = false)
77 public void setSpringHostComponentProviderConfig(SpringHostComponentProviderConfig springHostComponentProviderConfig)
78 {
79 this.springHostComponentProviderConfig = springHostComponentProviderConfig;
80 }
81
82
83 public void setBeanNames(Set<String> beanNames)
84 {
85 this.beanNames = beanNames;
86 }
87
88 public void setBeanInterfaces(Map<String, Class[]> beanInterfaces)
89 {
90 this.beanInterfaces = beanInterfaces;
91 }
92
93 public void setBeanContextClassLoaderStrategies(Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies)
94 {
95 this.beanContextClassLoaderStrategies = beanContextClassLoaderStrategies;
96 }
97
98 public void setBundleTrackingBeans(Set<String> bundleTrackingBeans)
99 {
100 this.bundleTrackingBeans = bundleTrackingBeans;
101 }
102
103 private static class SpringHostComponentProvider implements HostComponentProvider
104 {
105 private final BeanFactory beanFactory;
106 private boolean useAnnotation;
107 private final Set<String> beanNames;
108 private final Map<String, Class[]> beanInterfaces;
109 private final Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies;
110 private final Set<String> bundleTrackingBeans;
111
112 public SpringHostComponentProvider(BeanFactory beanFactory, Set<String> beanNames, Map<String, Class[]> beanInterfaces,
113 Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies,
114 Set<String> bundleTrackingBeans, boolean useAnnotation)
115 {
116 this.beanFactory = notNull("beanFactory", beanFactory);
117 this.useAnnotation = useAnnotation;
118 this.beanNames = beanNames != null ? beanNames : new HashSet<String>();
119 this.beanInterfaces = beanInterfaces != null ? beanInterfaces : new HashMap<String, Class[]>();
120 this.beanContextClassLoaderStrategies = beanContextClassLoaderStrategies != null ? beanContextClassLoaderStrategies : new HashMap<String, ContextClassLoaderStrategy>();
121 this.bundleTrackingBeans = bundleTrackingBeans != null ? bundleTrackingBeans : new HashSet<String>();
122 }
123
124 public void provide(ComponentRegistrar registrar)
125 {
126 final Set<String> beansToProvide = new HashSet<String>(beanNames);
127 final Map<String, Class[]> interfacesToProvide = new HashMap<String, Class[]>(beanInterfaces);
128 final Map<String, ContextClassLoaderStrategy> contextClassLoaderStrategiesToProvide = new HashMap<String, ContextClassLoaderStrategy>(beanContextClassLoaderStrategies);
129 final Set<String> bundleTrackingBeansToProvide = new HashSet<String>(bundleTrackingBeans);
130
131 if (useAnnotation)
132 {
133 scanForAnnotatedBeans(beansToProvide, interfacesToProvide, contextClassLoaderStrategiesToProvide, bundleTrackingBeansToProvide);
134 }
135
136 provideBeans(registrar, beansToProvide, interfacesToProvide, contextClassLoaderStrategiesToProvide, bundleTrackingBeansToProvide);
137
138
139 if (beanFactory instanceof HierarchicalBeanFactory)
140 {
141 final BeanFactory parentBeanFactory = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
142 if (parentBeanFactory != null)
143 {
144 try
145 {
146 HostComponentProvider provider = (HostComponentProvider) parentBeanFactory.getBean(PluginBeanDefinitionRegistry.HOST_COMPONENT_PROVIDER);
147 if (provider != null)
148 {
149 provider.provide(registrar);
150 }
151 }
152 catch (NoSuchBeanDefinitionException e)
153 {
154 log.debug("Unable to find '" + PluginBeanDefinitionRegistry.HOST_COMPONENT_PROVIDER + "' in the parent bean factory " + parentBeanFactory);
155 }
156 }
157 }
158 }
159
160 private void provideBeans(ComponentRegistrar registrar, Set<String> beanNames, Map<String, Class[]> beanInterfaces,
161 Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies, Set<String> bundleTrackingBeans)
162 {
163 for (String beanName : beanNames)
164 {
165 if (beanFactory.isSingleton(beanName))
166 {
167 final Object bean = beanFactory.getBean(beanName);
168 Class[] interfaces = beanInterfaces.get(beanName);
169 if (interfaces == null)
170 {
171 interfaces = findInterfaces(getBeanClass(bean));
172 }
173 registrar.register(interfaces)
174 .forInstance(bean)
175 .withName(beanName)
176 .withContextClassLoaderStrategy(beanContextClassLoaderStrategies.containsKey(beanName) ? beanContextClassLoaderStrategies.get(beanName) : USE_HOST)
177 .withTrackBundleEnabled(bundleTrackingBeans.contains(beanName));
178 }
179 else
180 {
181 log.warn("Cannot register bean '{}' as it's scope is not singleton", beanName);
182 }
183 }
184 }
185
186 private void scanForAnnotatedBeans(Set<String> beansToProvide, Map<String, Class[]> interfacesToProvide,
187 Map<String, ContextClassLoaderStrategy> contextClassLoaderStrategiesToProvide,
188 Set<String> bundleTrackingBeansToProvide)
189 {
190 if (beanFactory instanceof ListableBeanFactory)
191 {
192 for (String beanName : ((ListableBeanFactory) beanFactory).getBeanDefinitionNames())
193 {
194 try
195 {
196
197 if (beanFactory.isSingleton(beanName))
198 {
199 final Class beanClass = getBeanClass(beanFactory.getBean(beanName));
200 final AvailableToPlugins annotation = AnnotationUtils.findAnnotation(beanClass, AvailableToPlugins.class);
201 if (annotation != null)
202 {
203 beansToProvide.add(beanName);
204
205
206 if (annotation.value() != Void.class || annotation.interfaces().length != 0)
207 {
208 if (!interfacesToProvide.containsKey(beanName))
209 {
210 List<Class> effectiveInterfaces = new ArrayList<Class>();
211 if (annotation.value() != Void.class)
212 {
213 effectiveInterfaces.add(annotation.value());
214 }
215
216 if (annotation.interfaces().length != 0)
217 {
218 effectiveInterfaces.addAll(Arrays.asList(annotation.interfaces()));
219 }
220
221 interfacesToProvide.put(beanName, effectiveInterfaces.toArray(new Class[0]));
222 }
223 else
224 {
225 log.debug("Interfaces for bean '{}' have been defined in XML or in a Module definition, ignoring the interface defined in the annotation", beanName);
226 }
227 }
228
229 if (!contextClassLoaderStrategiesToProvide.containsKey(beanName))
230 {
231 contextClassLoaderStrategiesToProvide.put(beanName, annotation.contextClassLoaderStrategy());
232 }
233 else
234 {
235 log.debug("Context class loader strategy for bean '{}' has been defined in XML or in a Module definition, ignoring the one defined in the annotation", beanName);
236 }
237
238 if (annotation.trackBundle())
239 {
240 bundleTrackingBeansToProvide.add(beanName);
241 }
242 }
243 }
244 else
245 {
246 log.debug("Bean: " + beanName + " skipped during @AvailableToPlugins scanning since it's not a singleton bean");
247 }
248 }
249 catch (BeanIsAbstractException ex)
250 {
251
252 }
253 }
254 }
255 else
256 {
257 log.warn("Could not scan bean factory for beans to make available to plugins, bean factory is not 'listable'");
258 }
259 }
260
261 private Class[] findInterfaces(Class cls)
262 {
263 final List<Class> validInterfaces = new ArrayList<Class>();
264 for (Class inf : getAllInterfaces(cls))
265 {
266 if (!inf.getName().startsWith("org.springframework"))
267 {
268 validInterfaces.add(inf);
269 }
270 }
271 return validInterfaces.toArray(new Class[validInterfaces.size()]);
272 }
273
274 @SuppressWarnings("unchecked")
275 private List<Class> getAllInterfaces(Class cls)
276 {
277 return (List<Class>) ClassUtils.getAllInterfaces(cls);
278 }
279
280 private Class getBeanClass(Object bean)
281 {
282 return AopUtils.getTargetClass(bean);
283 }
284 }
285 }