View Javadoc
1   package com.atlassian.plugin.spring;
2   
3   import com.atlassian.plugin.osgi.hostcomponents.ContextClassLoaderStrategy;
4   import com.google.common.annotations.VisibleForTesting;
5   import org.slf4j.Logger;
6   import org.springframework.beans.BeansException;
7   import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
8   import org.springframework.beans.factory.config.BeanDefinition;
9   import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
10  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
11  import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
12  import org.springframework.core.type.MethodMetadata;
13  
14  import java.util.Map;
15  import java.util.function.Function;
16  
17  import static org.slf4j.LoggerFactory.getLogger;
18  
19  /**
20   * A Spring {@link BeanDefinitionRegistryPostProcessor} which scans for {@link AnnotatedBeanDefinition}s in the
21   * registry (i.e. beans defined by {@link org.springframework.context.annotation.Bean}-annotated methods) which are
22   * also annotated with {@link AvailableToPlugins}. These bean definitions are then used to configure the spring
23   * host component provider.
24   * <p>
25   * Note that {@link AvailableToPlugins} can also be used to annotate the bean class itself, in which case this is
26   * handled by {@link SpringHostComponentProviderFactoryBean} itself.
27   * <p>
28   * Usage: simply declare a bean of this class in your application context, and Spring will invoke it appropriately.
29   *
30   * @see PluginBeanDefinitionRegistry
31   * @see SpringHostComponentProviderFactoryBean
32   * @since 4.2
33   */
34  public class AvailableToPluginsBeanDefinitionRegistryProcessor implements BeanDefinitionRegistryPostProcessor {
35      private static final Logger log = getLogger(AvailableToPluginsBeanDefinitionRegistryProcessor.class);
36  
37      private final Function<BeanDefinitionRegistry, PluginBeanDefinitionRegistry> registryFactory;
38  
39      public AvailableToPluginsBeanDefinitionRegistryProcessor() {
40          this(PluginBeanDefinitionRegistry::new);
41      }
42  
43      @VisibleForTesting
44      AvailableToPluginsBeanDefinitionRegistryProcessor(Function<BeanDefinitionRegistry, PluginBeanDefinitionRegistry> registryFactory) {
45          this.registryFactory = registryFactory;
46      }
47  
48      @Override
49      public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
50          log.debug("Scanning al;l bean definitions for plugin-available @Bean methods");
51          final PluginBeanDefinitionRegistry pluginBeanDefinitionRegistry = registryFactory.apply(registry);
52          for (String beanName : registry.getBeanDefinitionNames()) {
53              final BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
54              if (beanDefinition instanceof AnnotatedBeanDefinition) {
55                  final MethodMetadata factoryMethodMetadata = ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata();
56                  if (factoryMethodMetadata != null) {
57                      final boolean isPluginAvailable = factoryMethodMetadata.isAnnotated(AvailableToPlugins.class.getName());
58                      if (isPluginAvailable) {
59                          if (beanDefinition.isSingleton()) {
60                              log.debug("Registering bean '{}' as plugin-available", beanName);
61                              registerPluginAvailableBean(beanName, factoryMethodMetadata, pluginBeanDefinitionRegistry);
62                          } else {
63                              log.warn("Bean '{}' is not singleton-scoped, and cannot be made available to plugins", beanName);
64                          }
65                      }
66                  }
67              }
68          }
69      }
70  
71      /**
72       * The logic in this method is very similar to the logic in {@link SpringHostComponentProviderFactoryBean}, but
73       * where that class works off the AvailableToPlugins annotation object directly, this class has to work off the
74       * lower-level MethodMetadata interface. If the logic in here is altered, then it should most likely be altered
75       * there also.
76       */
77      private void registerPluginAvailableBean(String beanName, MethodMetadata methodMetadata, PluginBeanDefinitionRegistry registry) {
78          registry.addBeanName(beanName);
79          final Map<String, Object> annotationAttributes = methodMetadata.getAnnotationAttributes(AvailableToPlugins.class.getName(), true);
80          final String valueAttribute = (String) annotationAttributes.get("value");
81          if (!valueAttribute.equals(Void.class.getName())) {
82              registry.addBeanInterface(beanName, valueAttribute);
83          }
84          final String[] interfacesAttribute = (String[]) annotationAttributes.get("interfaces");
85          for (String interfaceName : interfacesAttribute) {
86              registry.addBeanInterface(beanName, interfaceName);
87          }
88  
89          registry.addContextClassLoaderStrategy(beanName, (ContextClassLoaderStrategy) annotationAttributes.get("contextClassLoaderStrategy"));
90          final boolean trackBundle = (Boolean) annotationAttributes.get("trackBundle");
91          if (trackBundle)
92              registry.addBundleTrackingBean(beanName);
93      }
94  
95      @Override
96      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
97          // nothing doing
98      }
99  }