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