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 * @RunWith(SpringJUnit4ClassRunner.class)
041 * @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 }