001: /*
002: * Copyright 2006-2007, Unitils.org
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: * http://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: package org.unitils.spring.util;
017:
018: import org.apache.commons.lang.StringUtils;
019: import org.springframework.beans.BeansException;
020: import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
021: import org.springframework.beans.factory.config.BeanPostProcessor;
022: import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
023: import org.springframework.context.ApplicationContext;
024: import org.springframework.context.ConfigurableApplicationContext;
025: import org.unitils.core.UnitilsException;
026: import org.unitils.core.util.AnnotatedInstanceManager;
027: import org.unitils.spring.annotation.SpringApplicationContext;
028: import static org.unitils.util.ReflectionUtils.createInstanceOfType;
029:
030: import java.util.ArrayList;
031: import static java.util.Arrays.asList;
032: import java.util.HashMap;
033: import java.util.List;
034: import java.util.Map;
035:
036: /**
037: * A class for managing and creating Spring application contexts.
038: * <p/>
039: * todo javadoc
040: *
041: * @author Tim Ducheyne
042: * @author Filip Neven
043: */
044: public class ApplicationContextManager
045: extends
046: AnnotatedInstanceManager<ApplicationContext, SpringApplicationContext> {
047:
048: /**
049: * Factory for creating ApplicationContexts
050: */
051: protected ApplicationContextFactory applicationContextFactory;
052:
053: /**
054: * List of registered BeanPostProcessor types. For each ApplicationContext that is created, a BeanPostProcessor of
055: * each of these types is associated with the ApplicationContext
056: */
057: protected List<Class<? extends BeanPostProcessor>> beanPostProcessorTypes;
058:
059: // todo javadoc
060: protected Map<ApplicationContext, Map<Class<? extends BeanPostProcessor>, BeanPostProcessor>> beanPostProcessors;
061:
062: /**
063: * Creates a new instance, using the given {@link ApplicationContextFactory}. The given list of
064: * <code>BeanPostProcessor</code>s will be registered on all <code>ApplicationContext</code>s that are
065: * created.
066: *
067: * @param applicationContextFactory The factory for creating <code>ApplicationContext</code>s, not null.
068: */
069: public ApplicationContextManager(
070: ApplicationContextFactory applicationContextFactory) {
071: super (ApplicationContext.class, SpringApplicationContext.class);
072: this .applicationContextFactory = applicationContextFactory;
073: this .beanPostProcessorTypes = new ArrayList<Class<? extends BeanPostProcessor>>();
074: this .beanPostProcessors = new HashMap<ApplicationContext, Map<Class<? extends BeanPostProcessor>, BeanPostProcessor>>();
075: }
076:
077: /**
078: * Gets the application context for the given test as described in the class javadoc. A UnitilsException will be
079: * thrown if no context could be retrieved or created.
080: *
081: * @param testObject The test instance, not null
082: * @return The application context, not null
083: */
084: public ApplicationContext getApplicationContext(Object testObject) {
085: ApplicationContext applicationContext = getInstance(testObject);
086: if (applicationContext == null) {
087: throw new UnitilsException(
088: "No configuration found for creating an instance for test "
089: + testObject.getClass()
090: + ". Make sure that you either specify a value "
091: + "for an @"
092: + annotationClass.getSimpleName()
093: + " annotation somewhere in the testclass or a superclass or that you specify a custom create method in the test class itself.");
094: }
095: return applicationContext;
096: }
097:
098: /**
099: * Checks whether the given test object has an application context linked to it. If true is returned,
100: * {@link #getApplicationContext} will return an application context, If false is returned, it will raise
101: * an exception.
102: *
103: * @param testObject The test instance, not null
104: * @return True if an application context is linked
105: */
106: public boolean hasApplicationContext(Object testObject) {
107: return hasInstance(testObject);
108: }
109:
110: /**
111: * Forces the reloading of the application context the next time that it is requested. If classes are given
112: * only contexts that are linked to those classes will be reset. If no classes are given, all cached
113: * contexts will be reset.
114: *
115: * @param classes The classes for which to reset the contexts
116: */
117: public void invalidateApplicationContext(Class<?>... classes) {
118: invalidateInstance(classes);
119: }
120:
121: // todo javadoc
122: public void addBeanPostProcessorType(
123: Class<? extends BeanPostProcessor> beanPostProcessorType) {
124: beanPostProcessorTypes.add(beanPostProcessorType);
125: }
126:
127: /**
128: * Creates a new application context for the given locations. The application context factory is used to create
129: * the instance. After creating the context, this will also register all <code>BeanPostProcessor</code>s and
130: * refresh the context.
131: * <p/>
132: * Note: for this to work, the application context may not have been refreshed in the factory.
133: * By registering the bean post processors before the refresh, we can intercept bean creation and bean wiring.
134: * This is no longer possible if the context is already refreshed.
135: *
136: * @param locations The locations where to find configuration files, not null
137: * @return the context, not null
138: */
139: @Override
140: protected ApplicationContext createInstanceForValues(
141: List<String> locations) {
142: try {
143: // create application context
144: final ConfigurableApplicationContext applicationContext = applicationContextFactory
145: .createApplicationContext(locations);
146:
147: // register post processors
148: if (!beanPostProcessorTypes.isEmpty()) {
149: applicationContext
150: .addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
151: public void postProcessBeanFactory(
152: ConfigurableListableBeanFactory beanFactory)
153: throws BeansException {
154: for (Class<? extends BeanPostProcessor> beanPostProcessorType : beanPostProcessorTypes) {
155: BeanPostProcessor beanPostProcessor = createInstanceOfType(
156: beanPostProcessorType,
157: false);
158: registerBeanPostProcessor(
159: applicationContext,
160: beanPostProcessorType,
161: beanPostProcessor);
162: beanFactory
163: .addBeanPostProcessor(beanPostProcessor);
164: }
165: }
166: });
167: }
168: // load application context
169: applicationContext.refresh();
170: return applicationContext;
171:
172: } catch (Throwable t) {
173: throw new UnitilsException(
174: "Unable to create application context for locations "
175: + locations, t);
176: }
177: }
178:
179: // todo javadoc
180: protected void registerBeanPostProcessor(
181: ConfigurableApplicationContext applicationContext,
182: Class<? extends BeanPostProcessor> beanPostProcessorType,
183: BeanPostProcessor beanPostProcessor) {
184: Map<Class<? extends BeanPostProcessor>, BeanPostProcessor> beanPostProcessorMap = beanPostProcessors
185: .get(applicationContext);
186: if (beanPostProcessorMap == null) {
187: beanPostProcessorMap = new HashMap<Class<? extends BeanPostProcessor>, BeanPostProcessor>();
188: beanPostProcessors.put(applicationContext,
189: beanPostProcessorMap);
190: }
191: beanPostProcessorMap.put(beanPostProcessorType,
192: beanPostProcessor);
193: }
194:
195: // todo javadoc
196: @SuppressWarnings("unchecked")
197: public <T extends BeanPostProcessor> T getBeanPostProcessor(
198: ApplicationContext applicationContext,
199: Class<T> beanPostProcessorType) {
200: Map<Class<? extends BeanPostProcessor>, ? extends BeanPostProcessor> beanPostProcessorMap = beanPostProcessors
201: .get(applicationContext);
202: if (beanPostProcessorMap == null) {
203: return null;
204: } else {
205: return (T) beanPostProcessorMap.get(beanPostProcessorType);
206: }
207: }
208:
209: /**
210: * Gets the locations that are specified for the given {@link SpringApplicationContext} annotation. An array with
211: * 1 empty string should be considered to be empty and null should be returned.
212: *
213: * @param annotation The annotation, not null
214: * @return The locations, null if no values were specified
215: */
216: @Override
217: protected List<String> getAnnotationValues(
218: SpringApplicationContext annotation) {
219: String[] locations = annotation.value();
220: if (locations.length == 0
221: || (locations.length == 1 && StringUtils
222: .isEmpty(locations[0]))) {
223: return null;
224: }
225: return asList(locations);
226: }
227: }
|