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