View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2010 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.tests.core;
18  
19  import java.util.HashSet;
20  import java.util.Set;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
25  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
26  import org.springframework.context.ConfigurableApplicationContext;
27  import org.springframework.context.annotation.AnnotationConfigUtils;
28  import org.springframework.test.context.ContextConfiguration;
29  import org.springframework.test.context.support.AbstractContextLoader;
30  import org.springframework.util.StringUtils;
31  
32  import ch.elca.el4j.core.context.ModuleApplicationContext;
33  import ch.elca.el4j.core.context.ModuleApplicationContextConfiguration;
34  import ch.elca.el4j.core.context.ModuleApplicationContextCreationListener;
35  import ch.elca.el4j.tests.core.context.ExtendedContextConfiguration;
36  import ch.elca.el4j.tests.core.context.junit4.EL4JJunit4ClassRunner;
37  
38  /**
39   * Application context loader for tests with the module application context.
40   *
41   * @svnLink $Revision: 4253 $;$Date: 2010-12-21 11:08:04 +0100 (Di, 21. Dez 2010) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/core/src/test/java/ch/elca/el4j/tests/core/ModuleTestContextLoader.java $
42   *
43   * @author Simon Stelling (SST)
44   * @author Martin Zeltner (MZE)
45   */
46  public class ModuleTestContextLoader extends AbstractContextLoader
47  	implements ModuleApplicationContextCreationListener {
48  	
49  	/**
50  	 * Private logger.
51  	 */
52  	private static final Logger s_logger = LoggerFactory.getLogger(ModuleTestContextLoader.class);
53  
54  	/**
55  	 * Set by {@link EL4JJunit4ClassRunner} upon instanciation of the test class.
56  	 * Needed to discover <code>@ExtendedContextConfiguration</code> annotations which
57  	 * influence the configuration of the ModuleApplicationContext to be loaded.
58  	 */
59  	private static ThreadLocal<Class<?>> s_testedClass = new ThreadLocal<Class<?>>();
60  	
61  	/**
62  	 * Loads a ModuleApplicationContext from the supplied <code>locations</code>
63  	 * using the ModuleApplicationContextConfiguration created in 
64  	 * <code>createModuleApplicationContextConfiguration</code>.
65  	 * 
66  	 * {@inheritDoc}
67  	 */
68  	@Override
69  	public ConfigurableApplicationContext loadContext(String... locations) throws Exception {
70  		if (s_logger.isDebugEnabled()) {
71  			s_logger.debug("Loading ModuleApplicationContext for locations ["
72  				+ StringUtils.arrayToCommaDelimitedString(locations) + "].");
73  		}
74  		ModuleApplicationContextConfiguration config = createModuleApplicationContextConfiguration();
75  		config.setInclusiveConfigLocations(locations);
76  		config.setModuleApplicationContextCreationListener(this);
77  		customizeModuleApplicationContextConfiguration(config);
78  		ModuleApplicationContext context = new ModuleApplicationContext(config);
79  		return context;
80  	}
81  	
82  	/**
83  	 * Finds <code>@ExtendedContextConfiguration</code> annotations along the inheritance
84  	 * graph of the test class and aggregates the information into an instance of
85  	 * ModuleApplicationsContextConfiguration.
86  	 * @throws IllegalStateException if the tested class or a superclass of it is annotated
87  	 *         with a <code>@ExtendedContextConfiguration</code> but none of its transitive
88  	 *         superclasses are annotated with a <code>@ContextConfiguration</code>.
89  	 * @return the aggregated context configuration
90  	 */
91  	private ModuleApplicationContextConfiguration createModuleApplicationContextConfiguration() 
92  		throws IllegalStateException {
93  		ModuleApplicationContextConfiguration resultingConfiguration = new ModuleApplicationContextConfiguration();
94  		recursivelyConfigure(s_testedClass.get(), resultingConfiguration);
95  		return resultingConfiguration;
96  	}
97  	
98  	/**
99  	 * recusively walks through the inheritance tree and applies attributes from
100 	 * the <code>@ExtendedContextConfiguration</code> annotations most-specific-last.
101 	 * @param annotatedClass the class whose annotations should get inspected
102 	 * @param config the ModuleApplicationContextConfiguration to modify
103 	 */
104 	private void recursivelyConfigure(Class<?> annotatedClass, ModuleApplicationContextConfiguration config) {
105 		// end recursion just after Object
106 		if (annotatedClass == null) {
107 			return;
108 		}
109 		
110 		// parent goes first such that the child overrides the parent configuration
111 		recursivelyConfigure(annotatedClass.getSuperclass(), config);
112 		
113 		ContextConfiguration configuration 
114 			= annotatedClass.getAnnotation(ContextConfiguration.class);
115 		ExtendedContextConfiguration extendedConfiguration 
116 			= annotatedClass.getAnnotation(ExtendedContextConfiguration.class);
117 		
118 		if (extendedConfiguration != null && configuration == null) {
119 			throw new IllegalStateException(
120 				"@ExtendedContextConfiguration without @ContextConfiguration in class "
121 				+ annotatedClass.getName());
122 		}
123 		
124 		if (extendedConfiguration == null) {
125 			// no point in going further up the class hierarchy
126 			return;
127 		}
128 		
129 		// iterate over all attributes of the annotation
130 		TernaryBoolean b;
131 		
132 		// if 'inheritLocations' attribute from '@ContextConfiguration' is true, merge paths
133 		// otherwise override them
134 		if (configuration.inheritLocations()) {
135 			Set<String> exclusiveLocations = new HashSet<String>();
136 			for (String path : config.getExclusiveConfigLocations()) {
137 				exclusiveLocations.add(path);
138 			}
139 			for (String path : extendedConfiguration.exclusiveConfigLocations()) {
140 				exclusiveLocations.add(path);
141 			}
142 			config.setExclusiveConfigLocations(exclusiveLocations.toArray(new String[0]));
143 		} else {
144 			config.setExclusiveConfigLocations(extendedConfiguration.exclusiveConfigLocations());
145 		}
146 		
147 		// set boolean properties if specified
148 		b = new TernaryBoolean(extendedConfiguration.allowBeanDefinitionOverriding());
149 		if (b.isSet) {
150 			config.setAllowBeanDefinitionOverriding(b.value);
151 		}
152 		
153 		b = new TernaryBoolean(extendedConfiguration.mergeWithOuterResources());
154 		if (b.isSet) {
155 			config.setMergeWithOuterResources(b.value);
156 		}
157 		
158 		b = new TernaryBoolean(extendedConfiguration.mostSpecificBeanDefinitionCounts());
159 		if (b.isSet) {
160 			config.setMostSpecificBeanDefinitionCounts(b.value);
161 		}
162 		
163 		b = new TernaryBoolean(extendedConfiguration.mostSpecificResourceLast());
164 		if (b.isSet) {
165 			config.setMostSpecificResourceLast(b.value);
166 		}
167 		
168 	}
169 	
170 	/**
171 	 * Helper class.
172 	 */
173 	private class TernaryBoolean {
174 
175 		public TernaryBoolean(String sValue) {
176 			if ("true".equals(sValue) || "false".equals(sValue)) {
177 				isSet = true;
178 				value = "true".equals(sValue);
179 			} else {
180 				isSet = false;
181 			}
182 		}
183 		
184 		public boolean isSet;
185 		public boolean value;
186 	}
187 
188 	/**
189 	 * Interception method to customize the configuration of the module application context, 
190 	 * before the module application context will be created.
191 	 * 
192 	 * @param config Is the module application context configuration.
193 	 */
194 	protected void customizeModuleApplicationContextConfiguration(ModuleApplicationContextConfiguration config) { }
195 
196 	/**
197 	 * Returns &quot;<code>-context.xml</code>&quot;.
198 	 *
199 	 * {@inheritDoc}
200 	 */
201 	@Override
202 	public String getResourceSuffix() {
203 		return "-context.xml";
204 	}
205 
206 	/**
207 	 * {@inheritDoc}
208 	 */
209 	@Override
210 	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
211 		AnnotationConfigUtils.registerAnnotationConfigProcessors((BeanDefinitionRegistry) beanFactory);
212 	}
213 
214 	/**
215 	 * {@inheritDoc}
216 	 */
217 	@Override
218 	public void finishRefresh(ModuleApplicationContext context) {
219 		context.registerShutdownHook();
220 	}
221 
222 	/**
223 	 * @param testClass the test class which is annotated with <code>@ConfigurationContext</code>
224 	 * and possibly <code>@ExtendedConfigurationContext</code>
225 	 */
226 	public static void setTestedClass(Class<?> testClass) {
227 		s_testedClass.set(testClass);
228 	}
229 
230 }