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