001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.context.support;
018
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021
022import org.springframework.beans.factory.support.BeanDefinitionReader;
023import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
024import org.springframework.context.support.GenericApplicationContext;
025import org.springframework.test.context.ContextConfigurationAttributes;
026import org.springframework.test.context.MergedContextConfiguration;
027import org.springframework.util.ObjectUtils;
028
029/**
030 * Concrete implementation of {@link AbstractGenericContextLoader} that loads
031 * bean definitions from annotated classes.
032 *
033 * <p>See the Javadoc for
034 * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}
035 * for a definition of <em>annotated class</em>.
036 *
037 * <p>Note: {@code AnnotationConfigContextLoader} supports <em>annotated classes</em>
038 * rather than the String-based resource locations defined by the legacy
039 * {@link org.springframework.test.context.ContextLoader ContextLoader} API. Thus,
040 * although {@code AnnotationConfigContextLoader} extends
041 * {@code AbstractGenericContextLoader}, {@code AnnotationConfigContextLoader}
042 * does <em>not</em> support any String-based methods defined by
043 * {@code AbstractContextLoader} or {@code AbstractGenericContextLoader}.
044 * Consequently, {@code AnnotationConfigContextLoader} should chiefly be
045 * considered a {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
046 * rather than a {@link org.springframework.test.context.ContextLoader ContextLoader}.
047 *
048 * @author Sam Brannen
049 * @since 3.1
050 * @see #processContextConfiguration(ContextConfigurationAttributes)
051 * @see #detectDefaultConfigurationClasses(Class)
052 * @see #loadBeanDefinitions(GenericApplicationContext, MergedContextConfiguration)
053 * @see GenericXmlContextLoader
054 * @see GenericGroovyXmlContextLoader
055 */
056public class AnnotationConfigContextLoader extends AbstractGenericContextLoader {
057
058        private static final Log logger = LogFactory.getLog(AnnotationConfigContextLoader.class);
059
060
061        // SmartContextLoader
062
063        /**
064         * Process <em>annotated classes</em> in the supplied {@link ContextConfigurationAttributes}.
065         * <p>If the <em>annotated classes</em> are {@code null} or empty and
066         * {@link #isGenerateDefaultLocations()} returns {@code true}, this
067         * {@code SmartContextLoader} will attempt to {@link
068         * #detectDefaultConfigurationClasses detect default configuration classes}.
069         * If defaults are detected they will be
070         * {@link ContextConfigurationAttributes#setClasses(Class[]) set} in the
071         * supplied configuration attributes. Otherwise, properties in the supplied
072         * configuration attributes will not be modified.
073         * @param configAttributes the context configuration attributes to process
074         * @see org.springframework.test.context.SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
075         * @see #isGenerateDefaultLocations()
076         * @see #detectDefaultConfigurationClasses(Class)
077         */
078        @Override
079        public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
080                if (!configAttributes.hasClasses() && isGenerateDefaultLocations()) {
081                        configAttributes.setClasses(detectDefaultConfigurationClasses(configAttributes.getDeclaringClass()));
082                }
083        }
084
085
086        // AnnotationConfigContextLoader
087
088        /**
089         * Detect the default configuration classes for the supplied test class.
090         * <p>The default implementation simply delegates to
091         * {@link AnnotationConfigContextLoaderUtils#detectDefaultConfigurationClasses(Class)}.
092         * @param declaringClass the test class that declared {@code @ContextConfiguration}
093         * @return an array of default configuration classes, potentially empty but
094         * never {@code null}
095         * @see AnnotationConfigContextLoaderUtils
096         */
097        protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
098                return AnnotationConfigContextLoaderUtils.detectDefaultConfigurationClasses(declaringClass);
099        }
100
101
102        // AbstractContextLoader
103
104        /**
105         * {@code AnnotationConfigContextLoader} should be used as a
106         * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
107         * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
108         * Consequently, this method is not supported.
109         * @throws UnsupportedOperationException in this implementation
110         * @see AbstractContextLoader#modifyLocations
111         */
112        @Override
113        protected String[] modifyLocations(Class<?> clazz, String... locations) {
114                throw new UnsupportedOperationException(
115                                "AnnotationConfigContextLoader does not support the modifyLocations(Class, String...) method");
116        }
117
118        /**
119         * {@code AnnotationConfigContextLoader} should be used as a
120         * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
121         * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
122         * Consequently, this method is not supported.
123         * @throws UnsupportedOperationException in this implementation
124         * @see AbstractContextLoader#generateDefaultLocations
125         */
126        @Override
127        protected String[] generateDefaultLocations(Class<?> clazz) {
128                throw new UnsupportedOperationException(
129                                "AnnotationConfigContextLoader does not support the generateDefaultLocations(Class) method");
130        }
131
132        /**
133         * {@code AnnotationConfigContextLoader} should be used as a
134         * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
135         * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
136         * Consequently, this method is not supported.
137         * @throws UnsupportedOperationException in this implementation
138         * @see AbstractContextLoader#getResourceSuffix
139         */
140        @Override
141        protected String getResourceSuffix() {
142                throw new UnsupportedOperationException(
143                                "AnnotationConfigContextLoader does not support the getResourceSuffix() method");
144        }
145
146
147        // AbstractGenericContextLoader
148
149        /**
150         * Ensure that the supplied {@link MergedContextConfiguration} does not
151         * contain {@link MergedContextConfiguration#getLocations() locations}.
152         * @since 4.0.4
153         * @see AbstractGenericContextLoader#validateMergedContextConfiguration
154         */
155        @Override
156        protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
157                if (mergedConfig.hasLocations()) {
158                        String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'locations' " +
159                                                        "(or 'value') attribute %s, but %s does not support resource locations.",
160                                        mergedConfig.getTestClass().getName(), ObjectUtils.nullSafeToString(mergedConfig.getLocations()),
161                                        getClass().getSimpleName());
162                        logger.error(msg);
163                        throw new IllegalStateException(msg);
164                }
165        }
166
167        /**
168         * Register classes in the supplied {@link GenericApplicationContext context}
169         * from the classes in the supplied {@link MergedContextConfiguration}.
170         * <p>Each class must represent an <em>annotated class</em>. An
171         * {@link AnnotatedBeanDefinitionReader} is used to register the appropriate
172         * bean definitions.
173         * <p>Note that this method does not call {@link #createBeanDefinitionReader}
174         * since {@code AnnotatedBeanDefinitionReader} is not an instance of
175         * {@link BeanDefinitionReader}.
176         * @param context the context in which the annotated classes should be registered
177         * @param mergedConfig the merged configuration from which the classes should be retrieved
178         * @see AbstractGenericContextLoader#loadBeanDefinitions
179         */
180        @Override
181        protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
182                Class<?>[] annotatedClasses = mergedConfig.getClasses();
183                if (logger.isDebugEnabled()) {
184                        logger.debug("Registering annotated classes: " + ObjectUtils.nullSafeToString(annotatedClasses));
185                }
186                new AnnotatedBeanDefinitionReader(context).register(annotatedClasses);
187        }
188
189        /**
190         * {@code AnnotationConfigContextLoader} should be used as a
191         * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
192         * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
193         * Consequently, this method is not supported.
194         * @throws UnsupportedOperationException in this implementation
195         * @see #loadBeanDefinitions
196         * @see AbstractGenericContextLoader#createBeanDefinitionReader
197         */
198        @Override
199        protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
200                throw new UnsupportedOperationException(
201                                "AnnotationConfigContextLoader does not support the createBeanDefinitionReader(GenericApplicationContext) method");
202        }
203
204}