/* * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.test.context.support; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericApplicationContext; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Concrete implementation of {@link AbstractGenericContextLoader} that loads * bean definitions from * {@link org.springframework.context.annotation.Configuration configuration classes}. * *
Note: AnnotationConfigContextLoader
supports
* {@link org.springframework.context.annotation.Configuration configuration classes}
* rather than the String-based resource locations defined by the legacy
* {@link org.springframework.test.context.ContextLoader ContextLoader} API. Thus,
* although AnnotationConfigContextLoader
extends
* AbstractGenericContextLoader
, AnnotationConfigContextLoader
* does not support any String-based methods defined by
* AbstractContextLoader
or AbstractGenericContextLoader
.
* Consequently, AnnotationConfigContextLoader
should chiefly be
* considered a {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
* rather than a {@link org.springframework.test.context.ContextLoader ContextLoader}.
*
* @author Sam Brannen
* @since 3.1
* @see #processContextConfiguration()
* @see #generateDefaultConfigurationClasses()
* @see #loadBeanDefinitions()
*/
public class AnnotationConfigContextLoader extends AbstractGenericContextLoader {
private static final Log logger = LogFactory.getLog(AnnotationConfigContextLoader.class);
// --- SmartContextLoader -----------------------------------------------
/**
* Process configuration classes in the supplied {@link ContextConfigurationAttributes}.
*
If the configuration classes are null
or empty and
* {@link #generatesDefaults()} returns true
, this
* SmartContextLoader
will attempt to
* {@link #generateDefaultConfigurationClasses generate default configuration classes}.
* Otherwise, properties in the supplied configuration attributes will not
* be modified.
* @param configAttributes the context configuration attributes to process
* @see org.springframework.test.context.SmartContextLoader#processContextConfiguration()
* @see #generatesDefaults()
* @see #generateDefaultConfigurationClasses()
*/
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
if (ObjectUtils.isEmpty(configAttributes.getClasses()) && generatesDefaults()) {
Class>[] defaultConfigClasses = generateDefaultConfigurationClasses(configAttributes.getDeclaringClass());
configAttributes.setClasses(defaultConfigClasses);
}
}
/**
* TODO Document overridden supports(MergedContextConfiguration) implementation.
*/
@Override
public boolean supports(MergedContextConfiguration mergedConfig) {
return ObjectUtils.isEmpty(mergedConfig.getLocations()) && !ObjectUtils.isEmpty(mergedConfig.getClasses());
}
// --- AnnotationConfigContextLoader ---------------------------------------
private boolean isStaticNonPrivateAndNonFinal(Class> clazz) {
Assert.notNull(clazz, "Class must not be null");
int modifiers = clazz.getModifiers();
return (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && !Modifier.isFinal(modifiers));
}
/**
* Determine if the supplied {@link Class} meets the criteria for being
* considered as a default configuration class candidate.
*
Specifically, such candidates: *
null
private
final
static
true
if the supplied class meets the candidate criteria
*/
private boolean isDefaultConfigurationClassCandidate(Class> clazz) {
return clazz != null && isStaticNonPrivateAndNonFinal(clazz) && clazz.isAnnotationPresent(Configuration.class);
}
/**
* Generate the default configuration class array for the supplied test class.
* The generated class array will contain all static inner classes of * the supplied class that meet the requirements for {@code @Configuration} * class implementations as specified in the documentation for * {@link Configuration @Configuration}. *
The implementation of this method adheres to the contract defined in the
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
* SPI. Specifically, this method will preemptively verify that the
* generated default configuration classes exist and that such classes
* comply with the constraints required of {@code @Configuration} class
* implementations. If a candidate configuration class does meet these
* requirements, this method will log a warning, and the candidate class will
* be ignored.
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @return an array of default configuration classes, potentially empty but
* never Each class must represent an annotated configuration class or component. An
* {@link AnnotatedBeanDefinitionReader} is used to register the appropriate
* bean definitions.
* Note that this method does not call {@link #createBeanDefinitionReader}
* since null
*/
protected Class>[] generateDefaultConfigurationClasses(Class> declaringClass) {
Assert.notNull(declaringClass, "Declaring class must not be null");
ListAnnotationConfigContextLoader
should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
* @see AbstractContextLoader#modifyLocations
* @throws UnsupportedOperationException
*/
@Override
protected String[] modifyLocations(Class> clazz, String... locations) {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the modifyLocations(Class, String...) method");
}
/**
* AnnotationConfigContextLoader
should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
* @see AbstractContextLoader#generateDefaultLocations
* @throws UnsupportedOperationException
*/
@Override
protected String[] generateDefaultLocations(Class> clazz) {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the generateDefaultLocations(Class) method");
}
/**
* AnnotationConfigContextLoader
should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
* @see AbstractContextLoader#getResourceSuffix
* @throws UnsupportedOperationException
*/
@Override
protected String getResourceSuffix() {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the getResourceSuffix() method");
}
// --- AbstractGenericContextLoader ----------------------------------------
/**
* Register {@link org.springframework.context.annotation.Configuration configuration classes}
* in the supplied {@link GenericApplicationContext context} from the classes
* in the supplied {@link MergedContextConfiguration}.
* AnnotatedBeanDefinitionReader
is not an instance of
* {@link BeanDefinitionReader}.
* @param context the context in which the configuration classes should be registered
* @param mergedConfig the merged configuration from which the classes should be retrieved
* @see AbstractGenericContextLoader#loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
Class>[] configClasses = mergedConfig.getClasses();
if (logger.isDebugEnabled()) {
logger.debug("Registering configuration classes: " + ObjectUtils.nullSafeToString(configClasses));
}
new AnnotatedBeanDefinitionReader(context).register(configClasses);
}
/**
* AnnotationConfigContextLoader
should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
* @see #loadBeanDefinitions
* @see AbstractGenericContextLoader#createBeanDefinitionReader
* @throws UnsupportedOperationException
*/
@Override
protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
throw new UnsupportedOperationException(
"AnnotationConfigContextLoader does not support the createBeanDefinitionReader(GenericApplicationContext) method");
}
}