View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.context.annotation;
18  
19  import java.io.FileNotFoundException;
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.Comparator;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedHashMap;
28  import java.util.LinkedHashSet;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.Stack;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  import org.springframework.beans.BeanUtils;
39  import org.springframework.beans.factory.Aware;
40  import org.springframework.beans.factory.BeanClassLoaderAware;
41  import org.springframework.beans.factory.BeanDefinitionStoreException;
42  import org.springframework.beans.factory.BeanFactory;
43  import org.springframework.beans.factory.BeanFactoryAware;
44  import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
45  import org.springframework.beans.factory.config.BeanDefinition;
46  import org.springframework.beans.factory.config.BeanDefinitionHolder;
47  import org.springframework.beans.factory.config.ConfigurableBeanFactory;
48  import org.springframework.beans.factory.parsing.Location;
49  import org.springframework.beans.factory.parsing.Problem;
50  import org.springframework.beans.factory.parsing.ProblemReporter;
51  import org.springframework.beans.factory.support.AbstractBeanDefinition;
52  import org.springframework.beans.factory.support.BeanDefinitionReader;
53  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
54  import org.springframework.beans.factory.support.BeanNameGenerator;
55  import org.springframework.context.EnvironmentAware;
56  import org.springframework.context.ResourceLoaderAware;
57  import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
58  import org.springframework.core.NestedIOException;
59  import org.springframework.core.annotation.AnnotationAttributes;
60  import org.springframework.core.annotation.AnnotationAwareOrderComparator;
61  import org.springframework.core.env.CompositePropertySource;
62  import org.springframework.core.env.ConfigurableEnvironment;
63  import org.springframework.core.env.Environment;
64  import org.springframework.core.env.MutablePropertySources;
65  import org.springframework.core.env.PropertySource;
66  import org.springframework.core.io.Resource;
67  import org.springframework.core.io.ResourceLoader;
68  import org.springframework.core.io.support.ResourcePropertySource;
69  import org.springframework.core.type.AnnotationMetadata;
70  import org.springframework.core.type.MethodMetadata;
71  import org.springframework.core.type.StandardAnnotationMetadata;
72  import org.springframework.core.type.classreading.MetadataReader;
73  import org.springframework.core.type.classreading.MetadataReaderFactory;
74  import org.springframework.core.type.filter.AssignableTypeFilter;
75  import org.springframework.util.Assert;
76  import org.springframework.util.LinkedMultiValueMap;
77  import org.springframework.util.MultiValueMap;
78  import org.springframework.util.StringUtils;
79  
80  /**
81   * Parses a {@link Configuration} class definition, populating a collection of
82   * {@link ConfigurationClass} objects (parsing a single Configuration class may result in
83   * any number of ConfigurationClass objects because one Configuration class may import
84   * another using the {@link Import} annotation).
85   *
86   * <p>This class helps separate the concern of parsing the structure of a Configuration
87   * class from the concern of registering BeanDefinition objects based on the
88   * content of that model (with the exception of {@code @ComponentScan} annotations which
89   * need to be registered immediately).
90   *
91   * <p>This ASM-based implementation avoids reflection and eager class loading in order to
92   * interoperate effectively with lazy class loading in a Spring ApplicationContext.
93   *
94   * @author Chris Beams
95   * @author Juergen Hoeller
96   * @author Phillip Webb
97   * @since 3.0
98   * @see ConfigurationClassBeanDefinitionReader
99   */
100 class ConfigurationClassParser {
101 
102 	private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
103 			new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
104 				@Override
105 				public int compare(DeferredImportSelectorHolder o1, DeferredImportSelectorHolder o2) {
106 					return AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());
107 				}
108 			};
109 
110 
111 	private final Log logger = LogFactory.getLog(getClass());
112 
113 	private final MetadataReaderFactory metadataReaderFactory;
114 
115 	private final ProblemReporter problemReporter;
116 
117 	private final Environment environment;
118 
119 	private final ResourceLoader resourceLoader;
120 
121 	private final BeanDefinitionRegistry registry;
122 
123 	private final ComponentScanAnnotationParser componentScanParser;
124 
125 	private final ConditionEvaluator conditionEvaluator;
126 
127 	private final Map<ConfigurationClass, ConfigurationClass> configurationClasses =
128 			new LinkedHashMap<ConfigurationClass, ConfigurationClass>();
129 
130 	private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();
131 
132 	private final List<String> propertySourceNames = new ArrayList<String>();
133 
134 	private final ImportStack importStack = new ImportStack();
135 
136 	private List<DeferredImportSelectorHolder> deferredImportSelectors;
137 
138 
139 	/**
140 	 * Create a new {@link ConfigurationClassParser} instance that will be used
141 	 * to populate the set of configuration classes.
142 	 */
143 	public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
144 			ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
145 			BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
146 
147 		this.metadataReaderFactory = metadataReaderFactory;
148 		this.problemReporter = problemReporter;
149 		this.environment = environment;
150 		this.resourceLoader = resourceLoader;
151 		this.registry = registry;
152 		this.componentScanParser = new ComponentScanAnnotationParser(
153 				resourceLoader, environment, componentScanBeanNameGenerator, registry);
154 		this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
155 	}
156 
157 
158 	public void parse(Set<BeanDefinitionHolder> configCandidates) {
159 		this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
160 
161 		for (BeanDefinitionHolder holder : configCandidates) {
162 			BeanDefinition bd = holder.getBeanDefinition();
163 			try {
164 				if (bd instanceof AnnotatedBeanDefinition) {
165 					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
166 				}
167 				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
168 					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
169 				}
170 				else {
171 					parse(bd.getBeanClassName(), holder.getBeanName());
172 				}
173 			}
174 			catch (BeanDefinitionStoreException ex) {
175 				throw ex;
176 			}
177 			catch (Exception ex) {
178 				throw new BeanDefinitionStoreException(
179 						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
180 			}
181 		}
182 
183 		processDeferredImportSelectors();
184 	}
185 
186 	protected final void parse(String className, String beanName) throws IOException {
187 		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
188 		processConfigurationClass(new ConfigurationClass(reader, beanName));
189 	}
190 
191 	protected final void parse(Class<?> clazz, String beanName) throws IOException {
192 		processConfigurationClass(new ConfigurationClass(clazz, beanName));
193 	}
194 
195 	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
196 		processConfigurationClass(new ConfigurationClass(metadata, beanName));
197 	}
198 
199 
200 	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
201 		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
202 			return;
203 		}
204 
205 		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
206 		if (existingClass != null) {
207 			if (configClass.isImported()) {
208 				if (existingClass.isImported()) {
209 					existingClass.mergeImportedBy(configClass);
210 				}
211 				// Otherwise ignore new imported config class; existing non-imported class overrides it.
212 				return;
213 			}
214 			else {
215 				// Explicit bean definition found, probably replacing an import.
216 				// Let's remove the old one and go with the new one.
217 				this.configurationClasses.remove(configClass);
218 				for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext(); ) {
219 					if (configClass.equals(it.next())) {
220 						it.remove();
221 					}
222 				}
223 			}
224 		}
225 
226 		// Recursively process the configuration class and its superclass hierarchy.
227 		SourceClass sourceClass = asSourceClass(configClass);
228 		do {
229 			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
230 		}
231 		while (sourceClass != null);
232 
233 		this.configurationClasses.put(configClass, configClass);
234 	}
235 
236 	/**
237 	 * Apply processing and build a complete {@link ConfigurationClass} by reading the
238 	 * annotations, members and methods from the source class. This method can be called
239 	 * multiple times as relevant sources are discovered.
240 	 * @param configClass the configuration class being build
241 	 * @param sourceClass a source class
242 	 * @return the superclass, or {@code null} if none found or previously processed
243 	 */
244 	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
245 		// Recursively process any member (nested) classes first
246 		processMemberClasses(configClass, sourceClass);
247 
248 		// Process any @PropertySource annotations
249 		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
250 				sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
251 			if (this.environment instanceof ConfigurableEnvironment) {
252 				processPropertySource(propertySource);
253 			}
254 			else {
255 				logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
256 						"]. Reason: Environment must implement ConfigurableEnvironment");
257 			}
258 		}
259 
260 		// Process any @ComponentScan annotations
261 		AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class);
262 		if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
263 			// The config class is annotated with @ComponentScan -> perform the scan immediately
264 			Set<BeanDefinitionHolder> scannedBeanDefinitions =
265 					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
266 			// Check the set of scanned definitions for any further config classes and parse recursively if necessary
267 			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
268 				if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
269 					parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
270 				}
271 			}
272 		}
273 
274 		// Process any @Import annotations
275 		processImports(configClass, sourceClass, getImports(sourceClass), true);
276 
277 		// Process any @ImportResource annotations
278 		if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
279 			AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
280 			String[] resources = importResource.getStringArray("value");
281 			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
282 			for (String resource : resources) {
283 				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
284 				configClass.addImportedResource(resolvedResource, readerClass);
285 			}
286 		}
287 
288 		// Process individual @Bean methods
289 		Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
290 		for (MethodMetadata methodMetadata : beanMethods) {
291 			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
292 		}
293 
294 		// Process superclass, if any
295 		if (sourceClass.getMetadata().hasSuperClass()) {
296 			String superclass = sourceClass.getMetadata().getSuperClassName();
297 			if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
298 				this.knownSuperclasses.put(superclass, configClass);
299 				// Superclass found, return its annotation metadata and recurse
300 				return sourceClass.getSuperClass();
301 			}
302 		}
303 
304 		// No superclass -> processing is complete
305 		return null;
306 	}
307 
308 	/**
309 	 * Register member (nested) classes that happen to be configuration classes themselves.
310 	 * @param sourceClass the source class to process
311 	 * @throws IOException if there is any problem reading metadata from a member class
312 	 */
313 	private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
314 		for (SourceClass memberClass : sourceClass.getMemberClasses()) {
315 			if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
316 					!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
317 				processConfigurationClass(memberClass.asConfigClass(configClass));
318 			}
319 		}
320 	}
321 
322 	/**
323 	 * Process the given <code>@PropertySource</code> annotation metadata.
324 	 * @param propertySource metadata for the <code>@PropertySource</code> annotation found
325 	 * @throws IOException if loading a property source failed
326 	 */
327 	private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
328 		String name = propertySource.getString("name");
329 		String[] locations = propertySource.getStringArray("value");
330 		boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
331 		Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
332 		for (String location : locations) {
333 			try {
334 				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
335 				Resource resource = this.resourceLoader.getResource(resolvedLocation);
336 				ResourcePropertySource rps = (StringUtils.hasText(name) ?
337 						new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
338 				addPropertySource(rps);
339 			}
340 			catch (IllegalArgumentException ex) {
341 				// from resolveRequiredPlaceholders
342 				if (!ignoreResourceNotFound) {
343 					throw ex;
344 				}
345 			}
346 			catch (FileNotFoundException ex) {
347 				// from ResourcePropertySource constructor
348 				if (!ignoreResourceNotFound) {
349 					throw ex;
350 				}
351 			}
352 		}
353 	}
354 
355 	private void addPropertySource(ResourcePropertySource propertySource) {
356 		String name = propertySource.getName();
357 		MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
358 		if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
359 			// We've already added a version, we need to extend it
360 			PropertySource<?> existing = propertySources.get(name);
361 			if (existing instanceof CompositePropertySource) {
362 				((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName());
363 			}
364 			else {
365 				if (existing instanceof ResourcePropertySource) {
366 					existing = ((ResourcePropertySource) existing).withResourceName();
367 				}
368 				CompositePropertySource composite = new CompositePropertySource(name);
369 				composite.addPropertySource(propertySource.withResourceName());
370 				composite.addPropertySource(existing);
371 				propertySources.replace(name, composite);
372 			}
373 		}
374 		else {
375 			if (this.propertySourceNames.isEmpty()) {
376 				propertySources.addLast(propertySource);
377 			}
378 			else {
379 				String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
380 				propertySources.addBefore(firstProcessed, propertySource);
381 			}
382 		}
383 		this.propertySourceNames.add(name);
384 	}
385 
386 	/**
387 	 * Returns {@code @Import} class, considering all meta-annotations.
388 	 */
389 	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
390 		Set<SourceClass> imports = new LinkedHashSet<SourceClass>();
391 		Set<SourceClass> visited = new LinkedHashSet<SourceClass>();
392 		collectImports(sourceClass, imports, visited);
393 		return imports;
394 	}
395 
396 	/**
397 	 * Recursively collect all declared {@code @Import} values. Unlike most
398 	 * meta-annotations it is valid to have several {@code @Import}s declared with
399 	 * different values; the usual process of returning values from the first
400 	 * meta-annotation on a class is not sufficient.
401 	 * <p>For example, it is common for a {@code @Configuration} class to declare direct
402 	 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
403 	 * annotation.
404 	 * @param sourceClass the class to search
405 	 * @param imports the imports collected so far
406 	 * @param visited used to track visited classes to prevent infinite recursion
407 	 * @throws IOException if there is any problem reading metadata from the named class
408 	 */
409 	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException {
410 		if (visited.add(sourceClass)) {
411 			for (SourceClass annotation : sourceClass.getAnnotations()) {
412 				String annName = annotation.getMetadata().getClassName();
413 				if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
414 					collectImports(annotation, imports, visited);
415 				}
416 			}
417 			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
418 		}
419 	}
420 
421 	private void processDeferredImportSelectors() {
422 		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
423 		this.deferredImportSelectors = null;
424 		Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
425 
426 		for (DeferredImportSelectorHolder deferredImport : deferredImports) {
427 			ConfigurationClass configClass = deferredImport.getConfigurationClass();
428 			try {
429 				String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
430 				processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
431 			}
432 			catch (BeanDefinitionStoreException ex) {
433 				throw ex;
434 			}
435 			catch (Exception ex) {
436 				throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
437 						configClass.getMetadata().getClassName() + "]", ex);
438 			}
439 		}
440 	}
441 
442 	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
443 			Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
444 
445 		if (importCandidates.isEmpty()) {
446 			return;
447 		}
448 
449 		if (checkForCircularImports && this.importStack.contains(configClass)) {
450 			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
451 		}
452 		else {
453 			this.importStack.push(configClass);
454 			try {
455 				for (SourceClass candidate : importCandidates) {
456 					if (candidate.isAssignable(ImportSelector.class)) {
457 						// Candidate class is an ImportSelector -> delegate to it to determine imports
458 						Class<?> candidateClass = candidate.loadClass();
459 						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
460 						invokeAwareMethods(selector);
461 						if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
462 							this.deferredImportSelectors.add(
463 									new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
464 						}
465 						else {
466 							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
467 							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
468 							processImports(configClass, currentSourceClass, importSourceClasses, false);
469 						}
470 					}
471 					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
472 						// Candidate class is an ImportBeanDefinitionRegistrar ->
473 						// delegate to it to register additional bean definitions
474 						Class<?> candidateClass = candidate.loadClass();
475 						ImportBeanDefinitionRegistrar registrar =
476 								BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
477 						invokeAwareMethods(registrar);
478 						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
479 					}
480 					else {
481 						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
482 						// process it as an @Configuration class
483 						this.importStack.registerImport(
484 								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
485 						processConfigurationClass(candidate.asConfigClass(configClass));
486 					}
487 				}
488 			}
489 			catch (BeanDefinitionStoreException ex) {
490 				throw ex;
491 			}
492 			catch (Exception ex) {
493 				throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
494 						configClass.getMetadata().getClassName() + "]", ex);
495 			}
496 			finally {
497 				this.importStack.pop();
498 			}
499 		}
500 	}
501 
502 	/**
503 	 * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
504 	 * {@link BeanFactoryAware} contracts if implemented by the given {@code bean}.
505 	 */
506 	private void invokeAwareMethods(Object importStrategyBean) {
507 		if (importStrategyBean instanceof Aware) {
508 			if (importStrategyBean instanceof EnvironmentAware) {
509 				((EnvironmentAware) importStrategyBean).setEnvironment(this.environment);
510 			}
511 			if (importStrategyBean instanceof ResourceLoaderAware) {
512 				((ResourceLoaderAware) importStrategyBean).setResourceLoader(this.resourceLoader);
513 			}
514 			if (importStrategyBean instanceof BeanClassLoaderAware) {
515 				ClassLoader classLoader = (this.registry instanceof ConfigurableBeanFactory ?
516 						((ConfigurableBeanFactory) this.registry).getBeanClassLoader() :
517 						this.resourceLoader.getClassLoader());
518 				((BeanClassLoaderAware) importStrategyBean).setBeanClassLoader(classLoader);
519 			}
520 			if (importStrategyBean instanceof BeanFactoryAware && this.registry instanceof BeanFactory) {
521 				((BeanFactoryAware) importStrategyBean).setBeanFactory((BeanFactory) this.registry);
522 			}
523 		}
524 	}
525 
526 
527 	/**
528 	 * Validate each {@link ConfigurationClass} object.
529 	 * @see ConfigurationClass#validate
530 	 */
531 	public void validate() {
532 		for (ConfigurationClass configClass : this.configurationClasses.keySet()) {
533 			configClass.validate(this.problemReporter);
534 		}
535 	}
536 
537 	public Set<ConfigurationClass> getConfigurationClasses() {
538 		return this.configurationClasses.keySet();
539 	}
540 
541 
542 	ImportRegistry getImportRegistry() {
543 		return this.importStack;
544 	}
545 
546 	/**
547 	 * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}.
548 	 */
549 	public SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException {
550 		AnnotationMetadata metadata = configurationClass.getMetadata();
551 		if (metadata instanceof StandardAnnotationMetadata) {
552 			return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
553 		}
554 		return asSourceClass(configurationClass.getMetadata().getClassName());
555 	}
556 
557 	/**
558 	 * Factory method to obtain a {@link SourceClass} from a {@link Class}.
559 	 */
560 	public SourceClass asSourceClass(Class<?> classType) throws IOException {
561 		try {
562 			// Sanity test that we can read annotations, if not fall back to ASM
563 			classType.getAnnotations();
564 			return new SourceClass(classType);
565 		}
566 		catch (Throwable ex) {
567 			// Enforce ASM via class name resolution
568 			return asSourceClass(classType.getName());
569 		}
570 	}
571 
572 	/**
573 	 * Factory method to obtain {@link SourceClass}s from class names.
574 	 */
575 	public Collection<SourceClass> asSourceClasses(String[] classNames) throws IOException {
576 		List<SourceClass> annotatedClasses = new ArrayList<SourceClass>();
577 		for (String className : classNames) {
578 			annotatedClasses.add(asSourceClass(className));
579 		}
580 		return annotatedClasses;
581 	}
582 
583 	/**
584 	 * Factory method to obtain a {@link SourceClass} from a class name.
585 	 */
586 	public SourceClass asSourceClass(String className) throws IOException {
587 		if (className.startsWith("java")) {
588 			// Never use ASM for core java types
589 			try {
590 				return new SourceClass(this.resourceLoader.getClassLoader().loadClass(className));
591 			}
592 			catch (ClassNotFoundException ex) {
593 				throw new NestedIOException("Failed to load class [" + className + "]", ex);
594 			}
595 		}
596 		return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
597 	}
598 
599 
600 	@SuppressWarnings("serial")
601 	private static class ImportStack extends Stack<ConfigurationClass> implements ImportRegistry {
602 
603 		private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<String, AnnotationMetadata>();
604 
605 		public void registerImport(AnnotationMetadata importingClass, String importedClass) {
606 			this.imports.add(importedClass, importingClass);
607 		}
608 
609 		@Override
610 		public void removeImportingClassFor(String importedClass) {
611 			for (List<AnnotationMetadata> list : this.imports.values()) {
612 				for (Iterator<AnnotationMetadata> iterator = list.iterator(); iterator.hasNext();) {
613 					if (iterator.next().getClassName().equals(importedClass)) {
614 						iterator.remove();
615 					}
616 				}
617 			}
618 		}
619 
620 		@Override
621 		public AnnotationMetadata getImportingClassFor(String importedClass) {
622 			List<AnnotationMetadata> list = this.imports.get(importedClass);
623 			return (list == null || list.isEmpty() ? null : list.get(list.size() - 1));
624 		}
625 
626 		/**
627 		 * Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
628 		 * exists within this stack that has the same name as <var>elem</var>. Elem must be of
629 		 * type ConfigurationClass.
630 		 */
631 		@Override
632 		public boolean contains(Object elem) {
633 			ConfigurationClass configClass = (ConfigurationClass) elem;
634 			Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
635 				@Override
636 				public int compare(ConfigurationClass first, ConfigurationClass second) {
637 					return first.getMetadata().getClassName().equals(second.getMetadata().getClassName()) ? 0 : 1;
638 				}
639 			};
640 			return (Collections.binarySearch(this, configClass, comparator) != -1);
641 		}
642 
643 		/**
644 		 * Given a stack containing (in order)
645 		 * <ul>
646 		 * <li>com.acme.Foo</li>
647 		 * <li>com.acme.Bar</li>
648 		 * <li>com.acme.Baz</li>
649 		 * </ul>
650 		 * return "ImportStack: [Foo->Bar->Baz]".
651 		 */
652 		@Override
653 		public String toString() {
654 			StringBuilder builder = new StringBuilder("ImportStack: [");
655 			Iterator<ConfigurationClass> iterator = iterator();
656 			while (iterator.hasNext()) {
657 				builder.append(iterator.next().getSimpleName());
658 				if (iterator.hasNext()) {
659 					builder.append("->");
660 				}
661 			}
662 			return builder.append(']').toString();
663 		}
664 	}
665 
666 
667 	private static class DeferredImportSelectorHolder {
668 
669 		private final ConfigurationClass configurationClass;
670 
671 		private final DeferredImportSelector importSelector;
672 
673 		public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) {
674 			this.configurationClass = configurationClass;
675 			this.importSelector = importSelector;
676 		}
677 
678 		public ConfigurationClass getConfigurationClass() {
679 			return this.configurationClass;
680 		}
681 
682 		public DeferredImportSelector getImportSelector() {
683 			return this.importSelector;
684 		}
685 	}
686 
687 
688 	/**
689 	 * Simple wrapper that allows annotated source classes to be dealt with
690 	 * in a uniform manner, regardless of how they are loaded.
691 	 */
692 	private class SourceClass {
693 
694 		private final Object source;  // Class or MetadataReader
695 
696 		private final AnnotationMetadata metadata;
697 
698 		public SourceClass(Object source) {
699 			this.source = source;
700 			if (source instanceof Class<?>) {
701 				this.metadata = new StandardAnnotationMetadata((Class<?>) source, true);
702 			}
703 			else {
704 				this.metadata = ((MetadataReader) source).getAnnotationMetadata();
705 			}
706 		}
707 
708 		public final AnnotationMetadata getMetadata() {
709 			return this.metadata;
710 		}
711 
712 		public Class<?> loadClass() throws ClassNotFoundException {
713 			if (this.source instanceof Class<?>) {
714 				return (Class<?>) this.source;
715 			}
716 			String className = ((MetadataReader) this.source).getClassMetadata().getClassName();
717 			return resourceLoader.getClassLoader().loadClass(className);
718 		}
719 
720 		public boolean isAssignable(Class<?> clazz) throws IOException {
721 			if (this.source instanceof Class) {
722 				return clazz.isAssignableFrom((Class<?>) this.source);
723 			}
724 			return new AssignableTypeFilter(clazz).match((MetadataReader) this.source, metadataReaderFactory);
725 		}
726 
727 		public ConfigurationClass asConfigClass(ConfigurationClass importedBy) throws IOException {
728 			if (this.source instanceof Class<?>) {
729 				return new ConfigurationClass((Class<?>) this.source, importedBy);
730 			}
731 			return new ConfigurationClass((MetadataReader) this.source, importedBy);
732 		}
733 
734 		public Collection<SourceClass> getMemberClasses() throws IOException {
735 			Object sourceToProcess = this.source;
736 			if (sourceToProcess instanceof Class<?>) {
737 				Class<?> sourceClass = (Class<?>) sourceToProcess;
738 				try {
739 					Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
740 					List<SourceClass> members = new ArrayList<SourceClass>(declaredClasses.length);
741 					for (Class<?> declaredClass : declaredClasses) {
742 						members.add(asSourceClass(declaredClass));
743 					}
744 					return members;
745 				}
746 				catch (NoClassDefFoundError err) {
747 					// getDeclaredClasses() failed because of non-resolvable dependencies
748 					// -> fall back to ASM below
749 					sourceToProcess = metadataReaderFactory.getMetadataReader(sourceClass.getName());
750 				}
751 			}
752 
753 			// ASM-based resolution - safe for non-resolvable classes as well
754 			MetadataReader sourceReader = (MetadataReader) sourceToProcess;
755 			String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames();
756 			List<SourceClass> members = new ArrayList<SourceClass>(memberClassNames.length);
757 			for (String memberClassName : memberClassNames) {
758 				members.add(asSourceClass(memberClassName));
759 			}
760 			return members;
761 		}
762 
763 		public SourceClass getSuperClass() throws IOException {
764 			if (this.source instanceof Class<?>) {
765 				return asSourceClass(((Class<?>) this.source).getSuperclass());
766 			}
767 			return asSourceClass(((MetadataReader) this.source).getClassMetadata().getSuperClassName());
768 		}
769 
770 		public Set<SourceClass> getAnnotations() throws IOException {
771 			Set<SourceClass> result = new LinkedHashSet<SourceClass>();
772 			for (String className : this.metadata.getAnnotationTypes()) {
773 				try {
774 					result.add(getRelated(className));
775 				}
776 				catch (Throwable ex) {
777 					// An annotation not present on the classpath is being ignored
778 					// by the JVM's class loading -> ignore here as well.
779 				}
780 			}
781 			return result;
782 		}
783 
784 		public Collection<SourceClass> getAnnotationAttributes(String annotationType, String attribute) throws IOException {
785 			Map<String, Object> annotationAttributes = this.metadata.getAnnotationAttributes(annotationType, true);
786 			if (annotationAttributes == null || !annotationAttributes.containsKey(attribute)) {
787 				return Collections.emptySet();
788 			}
789 			String[] classNames = (String[]) annotationAttributes.get(attribute);
790 			Set<SourceClass> result = new LinkedHashSet<SourceClass>();
791 			for (String className : classNames) {
792 				result.add(getRelated(className));
793 			}
794 			return result;
795 		}
796 
797 		private SourceClass getRelated(String className) throws IOException {
798 			if (this.source instanceof Class<?>) {
799 				try {
800 					Class<?> clazz = resourceLoader.getClassLoader().loadClass(className);
801 					return asSourceClass(clazz);
802 				}
803 				catch (ClassNotFoundException ex) {
804 					// Ignore -> fall back to ASM next, except for core java types.
805 					if (className.startsWith("java")) {
806 						throw new NestedIOException("Failed to load class [" + className + "]", ex);
807 					}
808 					return new SourceClass(metadataReaderFactory.getMetadataReader(className));
809 				}
810 			}
811 			return asSourceClass(className);
812 		}
813 
814 		@Override
815 		public boolean equals(Object other) {
816 			return (this == other || (other instanceof SourceClass &&
817 					this.metadata.getClassName().equals(((SourceClass) other).metadata.getClassName())));
818 		}
819 
820 		@Override
821 		public int hashCode() {
822 			return this.metadata.getClassName().hashCode();
823 		}
824 
825 		@Override
826 		public String toString() {
827 			return this.metadata.getClassName();
828 		}
829 	}
830 
831 
832 	/**
833 	 * {@link Problem} registered upon detection of a circular {@link Import}.
834 	 */
835 	private static class CircularImportProblem extends Problem {
836 
837 		public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack, AnnotationMetadata metadata) {
838 			super(String.format("A circular @Import has been detected: " +
839 					"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
840 					"already present in the current import stack [%s]", importStack.peek().getSimpleName(),
841 					attemptedImport.getSimpleName(), attemptedImport.getSimpleName(), importStack),
842 					new Location(importStack.peek().getResource(), metadata));
843 		}
844 	}
845 
846 }