001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.spring.javaconfig.test;
018    
019    
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    
023    import org.apache.commons.logging.Log;
024    import org.apache.commons.logging.LogFactory;
025    import org.springframework.beans.factory.config.BeanPostProcessor;
026    import org.springframework.context.ApplicationContext;
027    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
028    import org.springframework.context.annotation.AnnotationConfigUtils;
029    import org.springframework.context.support.GenericApplicationContext;
030    import org.springframework.test.context.ContextLoader;
031    
032    /**
033     * Implementation of the {@link ContextLoader} strategy for creating a
034     * {@link JavaConfigApplicationContext} for a test's
035     * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}
036     * <p/>
037     *
038     * Example usage: <p/>
039     * <pre class="code">
040     * &#064;RunWith(SpringJUnit4ClassRunner.class)
041     * &#064;ContextConfiguration(locations = {"com.myco.TestDatabaseConfiguration", "com.myco.config"},
042     *                       loader = JavaConfigContextLoader.class)
043     * public MyTests { ... }
044     * </pre>
045     * <p/>
046     *
047     * Implementation note: At this time, due to restrictions in Java annotations and Spring's
048     * TestContext framework, locations of classes / packages must be specified as strings to
049     * the ContextConfiguration annotation.  It is understood that this has a detrimental effect
050     * on type safety, discoverability and refactoring, and for these reasons may change in
051     * future revisions, possibly with a customized version of the ContextConfiguration annotation
052     * that accepts an array of class literals to load.
053     *
054     * @author Jim Moore
055     * @author Chris Beams
056     * @see org.springframework.test.context.ContextConfiguration
057     */
058    public class JavaConfigContextLoader implements ContextLoader {
059    
060        protected final Log logger = LogFactory.getLog(getClass());
061    
062        /**
063         * Simply returns the supplied <var>locations</var> unchanged.
064         * <p/>
065         *
066         * @param clazz the class with which the locations are associated: used to determine how to
067         *            process the supplied locations.
068         * @param locations the unmodified locations to use for loading the application context; can be
069         *            {@code null} or empty.
070         * @return an array of application context resource locations
071         * @see org.springframework.test.context.ContextLoader#processLocations(Class, String[])
072         */
073        public String[] processLocations(Class<?> clazz, String... locations) {
074            return locations;
075        }
076    
077        /**
078         * Loads a new {@link ApplicationContext context} based on the supplied {@code locations},
079         * configures the context, and finally returns the context in fully <em>refreshed</em> state.
080         * <p/>
081         *
082         * Configuration locations are either fully-qualified class names or base package names. These
083         * locations will be given to a {@link JavaConfigApplicationContext} for configuration via the
084         * {@link JavaConfigApplicationContext#addConfigClass(Class)} and
085         * {@link JavaConfigApplicationContext#addBasePackage(String)} methods.
086         *
087         * @param locations the locations to use to load the application context
088         * @return a new application context
089         * @throws IllegalArgumentException if any of <var>locations</var> are not valid fully-qualified
090         * Class or Package names
091         */
092        public ApplicationContext loadContext(String... locations) {
093            if (logger.isDebugEnabled()) {
094                logger.debug("Creating a JavaConfigApplicationContext for " + Arrays.asList(locations));
095            }
096    
097            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
098    
099            ArrayList<Class<?>> configClasses = new ArrayList<Class<?>>();
100            ArrayList<String> basePackages = new ArrayList<String>();
101            for (String location : locations) {
102                // if the location refers to a class, use it. Otherwise assume it's a base package name
103                try {
104                    final Class<?> aClass = this.getClass().getClassLoader().loadClass(location);
105                    configClasses.add(aClass);
106                } catch (ClassNotFoundException e) {
107                    if (Package.getPackage(location) == null) {
108                        throw new IllegalArgumentException(
109                                String.format("A non-existent class or package name was specified: [%s]", location));
110                    }
111                    basePackages.add(location);
112                }
113            }
114    
115            if (logger.isDebugEnabled()) {
116                logger.debug("Setting config classes to " + configClasses);
117                logger.debug("Setting base packages to " + basePackages);
118            }
119    
120            for (Class<?> configClass : configClasses) {
121                context.register(configClass);
122            }
123            
124            for (String basePackage : basePackages) {
125                context.scan(basePackage);
126            }
127            
128            context.refresh();
129    
130            // Have to create a child context that implements BeanDefinitionRegistry
131            // to pass to registerAnnotationConfigProcessors, since
132            // JavaConfigApplicationContext does not
133            final GenericApplicationContext gac = new GenericApplicationContext(context);
134            AnnotationConfigUtils.registerAnnotationConfigProcessors(gac);
135            // copy BeanPostProcessors to the child context
136            for (String bppName : context.getBeanFactory().getBeanNamesForType(BeanPostProcessor.class)) {
137                gac.registerBeanDefinition(bppName, context.getBeanFactory().getBeanDefinition(bppName));
138            }
139            gac.refresh();
140            gac.registerShutdownHook();
141    
142            return gac;
143        }
144    
145    }