View Javadoc
1   package com.atlassian.plugin.spring.scanner.runtime.impl;
2   
3   import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
4   import org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext;
5   import org.osgi.framework.Bundle;
6   import org.osgi.framework.BundleContext;
7   import org.osgi.framework.FrameworkUtil;
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  import org.springframework.beans.MutablePropertyValues;
11  import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
12  import org.springframework.beans.factory.config.BeanDefinition;
13  import org.springframework.beans.factory.config.BeanDefinitionHolder;
14  import org.springframework.beans.factory.parsing.BeanComponentDefinition;
15  import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
16  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
17  import org.springframework.beans.factory.support.RootBeanDefinition;
18  import org.springframework.beans.factory.xml.BeanDefinitionParser;
19  import org.springframework.beans.factory.xml.ParserContext;
20  import org.springframework.beans.factory.xml.XmlReaderContext;
21  import org.springframework.context.annotation.AnnotationConfigUtils;
22  import org.springframework.core.io.ResourceLoader;
23  import org.springframework.util.ClassUtils;
24  import org.w3c.dom.Element;
25  
26  import java.util.HashMap;
27  import java.util.LinkedHashSet;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import static org.springframework.beans.factory.support.AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR;
32  import static org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.AUTOWIRE_ATTRIBUTE;
33  
34  /**
35   * This class is responsible for handling the "parsing" of the scan-indexes element in the spring beans file.
36   * Ultimately, this is what kicks off the index scanner and is the starting point for registering bean definitions
37   */
38  public class AtlassianScannerBeanDefinitionParser implements BeanDefinitionParser {
39      private static final String PROFILE_ATTRIBUTE = "profile";
40      public static final String JAVAX_INJECT_CLASSNAME = "javax.inject.Inject";
41  
42      private static final Logger log = LoggerFactory.getLogger(AtlassianScannerBeanDefinitionParser.class);
43  
44      @Override
45      public BeanDefinition parse(final Element element, final ParserContext parserContext) {
46          String profileName = null;
47          Integer autowireDefault = null;
48          if (element.hasAttribute(PROFILE_ATTRIBUTE)) {
49              profileName = element.getAttribute(PROFILE_ATTRIBUTE);
50          }
51          if (element.hasAttribute(AUTOWIRE_ATTRIBUTE)) {
52              // Pass the AUTOWIRE_ATTRIBUTE to the delegate for parsing.
53              autowireDefault = parserContext.getDelegate().getAutowireMode(element.getAttribute(AUTOWIRE_ATTRIBUTE));
54          }
55  
56          // Actually scan for bean definitions and register them.
57  
58          final BundleContext targetPluginBundleContext = getBundleContext(parserContext);
59  
60          checkScannerRuntimeIsNotEmbeddedInBundle(targetPluginBundleContext);
61  
62          final ClassIndexBeanDefinitionScanner scanner = new ClassIndexBeanDefinitionScanner(
63                  parserContext.getReaderContext().getRegistry(), profileName, autowireDefault, targetPluginBundleContext);
64          final Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan();
65  
66          registerComponents(parserContext.getReaderContext(), beanDefinitions, element, profileName);
67  
68          return null;
69      }
70  
71      /**
72       * Takes the scanned bean definitions and adds them to a root component. Also adds in the post-processors required
73       * to import/export OSGi services. Finally fires the component registered event with our root component.
74       *
75       * @param readerContext   the xml context
76       * @param beanDefinitions the set of bean definitions
77       * @param element         the xml element of definition
78       * @param profileName     the name of the profile to read definitions from
79       */
80      protected void registerComponents(
81              final XmlReaderContext readerContext,
82              final Set<BeanDefinitionHolder> beanDefinitions,
83              final Element element,
84              final String profileName) {
85          final Object source = readerContext.extractSource(element);
86          final CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
87  
88          //Add the beans we found to our root component
89          for (final BeanDefinitionHolder beanDefHolder : beanDefinitions) {
90              compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
91          }
92  
93          //add our custom post-processors along with the standard @Autowired processor
94          final Set<BeanDefinitionHolder> processorDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
95          processorDefinitions.addAll(AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source));
96  
97          //Let's be nice and support javax.inject.Inject annotations
98          final BeanDefinitionHolder javaxInject = getJavaxInjectPostProcessor(readerContext.getRegistry(), source);
99          if (null != javaxInject) {
100             processorDefinitions.add(javaxInject);
101         }
102         processorDefinitions.add(getComponentImportPostProcessor(readerContext.getRegistry(), source, profileName));
103         processorDefinitions.add(getServiceExportPostProcessor(readerContext.getRegistry(), source, profileName));
104         processorDefinitions.add(getDevModeBeanInitialisationLoggerPostProcessor(readerContext.getRegistry(), source));
105 
106         for (final BeanDefinitionHolder processorDefinition : processorDefinitions) {
107             compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
108         }
109 
110         readerContext.fireComponentRegistered(compositeDef);
111     }
112 
113     /**
114      * Get the plugin BundleContext for the spring application context we're initializing,
115      * by casting the ResourceLoader to the gemini ConfigurableOsgiBundleApplicationContext class.
116      * <p>
117      * This is necessary because AtlassianScannerNamespaceHandler has to have a no-arg constructor
118      * so can't get a BundleContext by constructor injection like ComponentImportBeanFactoryPostProcessor does.
119      */
120     private BundleContext getBundleContext(ParserContext parserContext) {
121         ResourceLoader resourceLoader = parserContext.getReaderContext().getResourceLoader();
122 
123         if (!(resourceLoader instanceof ConfigurableOsgiBundleApplicationContext)) {
124             throw new IllegalStateException("Could not access BundleContext from ResourceLoader: "
125                     + "expected resourceLoader to be an instance of "
126                     + ConfigurableOsgiBundleApplicationContext.class.getName() + ": got "
127                     + resourceLoader.getClass().getName());
128         }
129 
130         final BundleContext bundleContext = ((ConfigurableOsgiBundleApplicationContext) resourceLoader).getBundleContext();
131         if (bundleContext == null) {
132             throw new IllegalStateException("Could not access BundleContext from ResourceLoader: "
133                     + "ConfigurableOsgiBundleApplicationContext.getBundleContext returned null");
134         }
135 
136         return bundleContext;
137     }
138 
139     /**
140      * See SCANNER-17, SCANNER-54, SCANNER-57.
141      * Only allow scanner-runtime to work when used with 'provided' scope; refuse to work if embedded in a plugin
142      * with 'runtime' scope.
143      * This means we know there's only one copy in the system - the 'provided' bundle,
144      * hence we avoid the SCANNER-54 issue where gemini's namespace handling finds copies that aren't ready to execute.
145      */
146     private void checkScannerRuntimeIsNotEmbeddedInBundle(BundleContext targetPluginBundleContext) {
147         final Bundle pluginInvokingScannerRuntime = targetPluginBundleContext.getBundle();
148         if (pluginInvokingScannerRuntime == null) {
149             throw new IllegalStateException("Cannot execute atlassian-spring-scanner-runtime from a plugin that is not in a valid state: "
150                     + "bundleContext.getBundle() returned null for plugin bundle.");
151         }
152 
153         final String howToFixScope = "Use 'mvn dependency:tree' and ensure the atlassian-spring-scanner-annotation "
154                 + "dependency in your plugin has <scope>provided</scope>, not 'runtime' or 'compile', "
155                 + "and you have NO dependency on atlassian-spring-scanner-runtime.";
156 
157         final Bundle bundleContainingThisScannerRuntime = FrameworkUtil.getBundle(AtlassianScannerBeanDefinitionParser.class);
158         if (bundleContainingThisScannerRuntime == null) {
159             throw new IllegalStateException("Incorrect use of atlassian-spring-scanner-runtime: "
160                     + "atlassian-spring-scanner-runtime classes do not appear to be coming from a bundle classloader. "
161                     + howToFixScope);
162         }
163         final Bundle bundleContainingScannerAnnotationLibsAsSeenByRuntime = FrameworkUtil.getBundle(ComponentImport.class);
164         if (bundleContainingScannerAnnotationLibsAsSeenByRuntime == null) {
165             throw new IllegalStateException("Incorrect use of atlassian-spring-scanner-runtime: "
166                     + "atlassian-spring-scanner-annotation classes do not appear to be coming from a bundle classloader. "
167                     + howToFixScope);
168         }
169 
170         final long invokingPluginBundleId = pluginInvokingScannerRuntime.getBundleId();
171         final long scannerRuntimeBundleId = bundleContainingThisScannerRuntime.getBundleId();
172         if (invokingPluginBundleId == scannerRuntimeBundleId) {
173             throw new IllegalStateException("Incorrect use of atlassian-spring-scanner-runtime: "
174                     + "atlassian-spring-scanner-runtime classes are embedded inside the target plugin '"
175                     + pluginInvokingScannerRuntime.getSymbolicName()
176                     + "'; embedding scanner-runtime is not supported since scanner version 2.0. "
177                     + howToFixScope);
178         }
179 
180         // Also check whether there's an embedded copy of the scanner-annotation classes.
181         // This will PROBABLY still work (likely, scanner-runtime will ignore it and use the scanner-annotation bundle)
182         // but better to make sure people get their packaging right when they switch to 2.0-provided;
183         // will aid debugging etc.
184         Bundle bundleContainingScannerAnnotationLibsAsSeenByPlugin;
185         try {
186             bundleContainingScannerAnnotationLibsAsSeenByPlugin =
187                     FrameworkUtil.getBundle(pluginInvokingScannerRuntime.loadClass(ComponentImport.class.getName()));
188         } catch (ClassNotFoundException e) {
189             // Plugin doesn't import the annotation classes, and also doesn't embed them.
190             // Weird, but no problem. Runtime still works.
191             return;
192         }
193         final long scannerAnnotationBundleId = bundleContainingScannerAnnotationLibsAsSeenByRuntime.getBundleId();
194         if (bundleContainingScannerAnnotationLibsAsSeenByPlugin == null
195                 || bundleContainingScannerAnnotationLibsAsSeenByPlugin.getBundleId() != scannerAnnotationBundleId) {
196             throw new IllegalStateException("Cannot execute atlassian-spring-scanner-runtime: "
197                     + "plugin has an extra copy of atlassian-spring-scanner-annotation classes, perhaps embedded inside the target plugin '"
198                     + pluginInvokingScannerRuntime.getSymbolicName()
199                     + "'; embedding scanner-annotations is not supported since scanner version 2.0. "
200                     + howToFixScope);
201         }
202     }
203 
204     private BeanDefinitionHolder getJavaxInjectPostProcessor(final BeanDefinitionRegistry registry, final Object source) {
205         if (ClassUtils.isPresent(JAVAX_INJECT_CLASSNAME, getClass().getClassLoader())) {
206             try {
207                 final Class injectClass = getClass().getClassLoader().loadClass(JAVAX_INJECT_CLASSNAME);
208                 final Map<String, Object> properties = new HashMap<String, Object>();
209                 properties.put("autowiredAnnotationType", injectClass);
210 
211                 final RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
212                 def.setSource(source);
213                 def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
214                 def.setPropertyValues(new MutablePropertyValues(properties));
215 
216                 return registerBeanPostProcessor(registry, def, "javaxInjectBeanPostProcessor");
217             } catch (final ClassNotFoundException e) {
218                 log.error("Unable to load class '" + JAVAX_INJECT_CLASSNAME + "' for javax component purposes.  Not sure how this is possible.  Skipping...");
219             }
220         }
221 
222         return null;
223     }
224 
225     /**
226      * Helper to convert a post-processor into a proper holder
227      *
228      * @param registry   the registry of bean defs
229      * @param definition the root definition
230      * @param beanName   the name of the bean to register
231      * @return a bean def holder
232      */
233     private BeanDefinitionHolder registerBeanPostProcessor(
234             final BeanDefinitionRegistry registry, final RootBeanDefinition definition, final String beanName) {
235         definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
236 
237         registry.registerBeanDefinition(beanName, definition);
238         return new BeanDefinitionHolder(definition, beanName);
239     }
240 
241     private BeanDefinitionHolder getComponentImportPostProcessor(
242             final BeanDefinitionRegistry registry, final Object source, final String profileName) {
243         // Note that we need a null capable collection here
244         final Map<String, Object> properties = new HashMap<String, Object>();
245         properties.put("profileName", profileName);
246 
247         final RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(ComponentImportBeanFactoryPostProcessor.class);
248         rootBeanDefinition.setAutowireMode(AUTOWIRE_CONSTRUCTOR);
249         rootBeanDefinition.setSource(source);
250         rootBeanDefinition.setPropertyValues(new MutablePropertyValues(properties));
251 
252         return registerBeanPostProcessor(registry, rootBeanDefinition, "componentImportBeanFactoryPostProcessor");
253     }
254 
255     private BeanDefinitionHolder getServiceExportPostProcessor(
256             final BeanDefinitionRegistry registry, final Object source, final String profileName) {
257         // Note that we need a null capable collection here
258         final HashMap<String, Object> properties = new HashMap<String, Object>();
259         properties.put("profileName", profileName);
260 
261         final RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(ServiceExporterBeanPostProcessor.class);
262         rootBeanDefinition.setSource(source);
263         rootBeanDefinition.setAutowireMode(AUTOWIRE_CONSTRUCTOR);
264         rootBeanDefinition.setPropertyValues(new MutablePropertyValues(properties));
265 
266         return registerBeanPostProcessor(registry, rootBeanDefinition, "serviceExportBeanPostProcessor");
267     }
268 
269     private BeanDefinitionHolder getDevModeBeanInitialisationLoggerPostProcessor(
270             final BeanDefinitionRegistry registry, final Object source) {
271         final RootBeanDefinition def = new RootBeanDefinition(DevModeBeanInitialisationLoggerBeanPostProcessor.class);
272         def.setSource(source);
273         def.setAutowireMode(AUTOWIRE_CONSTRUCTOR);
274 
275         return registerBeanPostProcessor(registry, def, "devModeBeanInitialisationLoggerBeanPostProcessor");
276     }
277 }