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.lang3.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() {
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 ImmutableMap.<String, Class[]>builder()
71 .putAll(beanInterfaces)
72 .putAll(springHostComponentProviderConfig.getBeanInterfaces())
73 .build(),
74 ImmutableMap.<String, ContextClassLoaderStrategy>builder()
75 .putAll(beanContextClassLoaderStrategies)
76 .putAll(springHostComponentProviderConfig.getBeanContextClassLoaderStrategies())
77 .build(),
78 ImmutableSet.<String>builder()
79 .addAll(bundleTrackingBeans)
80 .addAll(springHostComponentProviderConfig.getBundleTrackingBeans())
81 .build(),
82 springHostComponentProviderConfig.isUseAnnotation());
83 }
84 }
85
86 @Autowired(required = false)
87 public void setSpringHostComponentProviderConfig(SpringHostComponentProviderConfig springHostComponentProviderConfig) {
88 this.springHostComponentProviderConfig = springHostComponentProviderConfig;
89 }
90
91
92 public void setBeanNames(Set<String> beanNames) {
93 this.beanNames = beanNames;
94 }
95
96 public void setBeanInterfaces(Map<String, Class[]> beanInterfaces) {
97 this.beanInterfaces = beanInterfaces;
98 }
99
100 public void setBeanContextClassLoaderStrategies(Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies) {
101 this.beanContextClassLoaderStrategies = beanContextClassLoaderStrategies;
102 }
103
104 public void setBundleTrackingBeans(Set<String> bundleTrackingBeans) {
105 this.bundleTrackingBeans = bundleTrackingBeans;
106 }
107
108 private static class SpringHostComponentProvider implements HostComponentProvider {
109 private final BeanFactory beanFactory;
110 private boolean useAnnotation;
111 private final Set<String> beanNames;
112 private final Map<String, Class[]> beanInterfaces;
113 private final Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies;
114 private final Set<String> bundleTrackingBeans;
115
116 public SpringHostComponentProvider(BeanFactory beanFactory, Set<String> beanNames, Map<String, Class[]> beanInterfaces,
117 Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies,
118 Set<String> bundleTrackingBeans, boolean useAnnotation) {
119 this.beanFactory = notNull("beanFactory", beanFactory);
120 this.useAnnotation = useAnnotation;
121 this.beanNames = beanNames != null ? beanNames : new HashSet<>();
122 this.beanInterfaces = beanInterfaces != null ? beanInterfaces : new HashMap<>();
123 this.beanContextClassLoaderStrategies = beanContextClassLoaderStrategies != null ? beanContextClassLoaderStrategies : new HashMap<>();
124 this.bundleTrackingBeans = bundleTrackingBeans != null ? bundleTrackingBeans : new HashSet<>();
125 }
126
127 public void provide(ComponentRegistrar registrar) {
128 final Set<String> beansToProvide = new HashSet<>(beanNames);
129 final Map<String, Class[]> interfacesToProvide = new HashMap<>(beanInterfaces);
130 final Map<String, ContextClassLoaderStrategy> contextClassLoaderStrategiesToProvide = new HashMap<>(beanContextClassLoaderStrategies);
131 final Set<String> bundleTrackingBeansToProvide = new HashSet<>(bundleTrackingBeans);
132
133 if (useAnnotation) {
134 scanForAnnotatedBeans(beansToProvide, interfacesToProvide, contextClassLoaderStrategiesToProvide, bundleTrackingBeansToProvide);
135 }
136
137 provideBeans(registrar, beansToProvide, interfacesToProvide, contextClassLoaderStrategiesToProvide, bundleTrackingBeansToProvide);
138
139
140 if (beanFactory instanceof HierarchicalBeanFactory) {
141 final BeanFactory parentBeanFactory = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
142 if (parentBeanFactory != null) {
143 try {
144 HostComponentProvider provider = (HostComponentProvider) parentBeanFactory.getBean(PluginBeanDefinitionRegistry.HOST_COMPONENT_PROVIDER);
145 if (provider != null) {
146 provider.provide(registrar);
147 }
148 } catch (NoSuchBeanDefinitionException e) {
149 log.debug("Unable to find '" + PluginBeanDefinitionRegistry.HOST_COMPONENT_PROVIDER + "' in the parent bean factory " + parentBeanFactory);
150 }
151 }
152 }
153 }
154
155 private void provideBeans(ComponentRegistrar registrar, Set<String> beanNames, Map<String, Class[]> beanInterfaces,
156 Map<String, ContextClassLoaderStrategy> beanContextClassLoaderStrategies, Set<String> bundleTrackingBeans) {
157 for (String beanName : beanNames) {
158 if (beanFactory.isSingleton(beanName)) {
159 final Object bean = beanFactory.getBean(beanName);
160 Class[] interfaces = beanInterfaces.get(beanName);
161 if (interfaces == null) {
162 interfaces = findInterfaces(getBeanClass(bean));
163 }
164 registrar.register(interfaces)
165 .forInstance(bean)
166 .withName(beanName)
167 .withContextClassLoaderStrategy(beanContextClassLoaderStrategies.getOrDefault(beanName, USE_HOST))
168 .withTrackBundleEnabled(bundleTrackingBeans.contains(beanName));
169 } else {
170 log.warn("Cannot register bean '{}' as it's scope is not singleton", beanName);
171 }
172 }
173 }
174
175
176
177
178
179
180
181 private void scanForAnnotatedBeans(Set<String> beansToProvide, Map<String, Class[]> interfacesToProvide,
182 Map<String, ContextClassLoaderStrategy> contextClassLoaderStrategiesToProvide,
183 Set<String> bundleTrackingBeansToProvide) {
184 if (beanFactory instanceof ListableBeanFactory) {
185 for (String beanName : ((ListableBeanFactory) beanFactory).getBeanDefinitionNames()) {
186 try {
187
188 if (beanFactory.isSingleton(beanName)) {
189 final Class beanClass = getBeanClass(beanFactory.getBean(beanName));
190 final AvailableToPlugins annotation = AnnotationUtils.findAnnotation(beanClass, AvailableToPlugins.class);
191 if (annotation != null) {
192 beansToProvide.add(beanName);
193
194
195 if (annotation.value() != Void.class || annotation.interfaces().length != 0) {
196 if (!interfacesToProvide.containsKey(beanName)) {
197 List<Class> effectiveInterfaces = new ArrayList<>();
198 if (annotation.value() != Void.class) {
199 effectiveInterfaces.add(annotation.value());
200 }
201
202 if (annotation.interfaces().length != 0) {
203 effectiveInterfaces.addAll(Arrays.asList(annotation.interfaces()));
204 }
205
206 interfacesToProvide.put(beanName, effectiveInterfaces.toArray(new Class[0]));
207 } else {
208 log.debug("Interfaces for bean '{}' have been defined in XML or in a Module definition, ignoring the interface defined in the annotation", beanName);
209 }
210 }
211
212 if (!contextClassLoaderStrategiesToProvide.containsKey(beanName)) {
213 contextClassLoaderStrategiesToProvide.put(beanName, annotation.contextClassLoaderStrategy());
214 } else {
215 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);
216 }
217
218 if (annotation.trackBundle()) {
219 bundleTrackingBeansToProvide.add(beanName);
220 }
221 }
222 } else {
223 log.debug("Bean: " + beanName + " skipped during @AvailableToPlugins scanning since it's not a singleton bean");
224 }
225 } catch (BeanIsAbstractException ex) {
226
227 }
228 }
229 } else {
230 log.warn("Could not scan bean factory for beans to make available to plugins, bean factory is not 'listable'");
231 }
232 }
233
234 private Class[] findInterfaces(Class cls) {
235 final List<Class> validInterfaces = new ArrayList<>();
236 for (Class inf : getAllInterfaces(cls)) {
237 if (!inf.getName().startsWith("org.springframework")) {
238 validInterfaces.add(inf);
239 }
240 }
241 return validInterfaces.toArray(new Class[0]);
242 }
243
244 private List<Class<?>> getAllInterfaces(Class cls) {
245 return ClassUtils.getAllInterfaces(cls);
246 }
247
248 private Class getBeanClass(Object bean) {
249 return AopUtils.getTargetClass(bean);
250 }
251 }
252 }