View Javadoc

1   /*
2    * Copyright 2009, Josh Devins, Jitr.org.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.jitr;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.Modifier;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.LinkedList;
25  
26  import javax.servlet.ServletContext;
27  
28  import org.apache.log4j.Logger;
29  import org.jitr.annotation.BaseUri;
30  import org.jitr.annotation.JitrConfiguration;
31  import org.jitr.annotation.Port;
32  import org.jitr.core.ConfigurationModel;
33  import org.jitr.core.Container;
34  import org.jitr.core.JitrException;
35  import org.jitr.core.OperationalMode;
36  import org.junit.internal.runners.ClassRoadie;
37  import org.junit.internal.runners.InitializationError;
38  import org.junit.internal.runners.JUnit4ClassRunner;
39  import org.junit.runner.notification.RunNotifier;
40  import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
41  import org.springframework.context.ApplicationContext;
42  import org.springframework.context.support.ClassPathXmlApplicationContext;
43  import org.springframework.core.annotation.AnnotationUtils;
44  import org.springframework.test.context.ContextConfiguration;
45  import org.springframework.test.context.ContextLoader;
46  import org.springframework.web.context.WebApplicationContext;
47  import org.springframework.web.context.support.WebApplicationContextUtils;
48  
49  /**
50   * The heart of Jitr. This provides all of the configuration, Spring application context loading,
51   * bootstrapping and wiring of test classes.
52   * 
53   * @author Josh Devins (info@joshdevins.net)
54   */
55  @JitrConfiguration
56  public final class Jitr extends JUnit4ClassRunner {
57  
58      private static final Logger LOG = Logger.getLogger(Jitr.class);
59  
60      private ConfigurationModel config;
61  
62      private Container<?> container;
63  
64      private AutowireCapableBeanFactory autowireCapableBeanFactory;
65  
66      public Jitr(final Class<?> clazz) throws InitializationError {
67          super(clazz);
68      }
69  
70      @Override
71      public void run(final RunNotifier notifier) {
72  
73          if (LOG.isInfoEnabled()) {
74              LOG.info("Jitr loaded");
75          }
76  
77          // get the Jitr configuration (if any) from the test class
78          JitrConfiguration annot =
79                  AnnotationUtils.findAnnotation(getTestClass().getJavaClass(),
80                          JitrConfiguration.class);
81  
82          if (LOG.isInfoEnabled() && annot == null) {
83              LOG.info("No @JitrConfiguration found on test class");
84          }
85  
86          config = JitrConfigurationFactory.createConfigurationFromAnnotation(annot);
87  
88          if (config == null) {
89              throw new JitrException("Configuration could not be created.");
90          }
91  
92          final ServletContext servletContext = initAndStartContainerIfNeeded();
93  
94          // setup Spring if we can
95          setSpringAutowireCapableBeanFactory(servletContext);
96  
97          // create JUnit class roadie for running tests
98          final ClassRoadie roadie =
99                  new ClassRoadie(notifier, getTestClass(), getDescription(), new Runnable() {
100                     public void run() {
101                         runMethods(notifier);
102                     }
103                 });
104 
105         if (LOG.isInfoEnabled()) {
106             LOG.info("Running tests");
107         }
108 
109         // run tests regularly with JUnit class roadie
110         roadie.runProtected();
111 
112         if (config.getMode().isContainerEnabled()) {
113 
114             if (LOG.isInfoEnabled()) {
115                 LOG.info("Stopping container: " + container.getName());
116             }
117 
118             container.stop();
119         }
120     }
121 
122     @Override
123     protected Object createTest() throws Exception {
124         Object testInstance = super.createTest();
125 
126         // autowire the test if we can
127         if (autowireCapableBeanFactory != null) {
128             autowireCapableBeanFactory.autowireBean(testInstance);
129         }
130 
131         // set @Port field
132         final Field portField =
133                 JitrUtils.getMostSpecificAnnotatedDeclaredField(testInstance.getClass(), Port.class);
134 
135         if (portField != null) {
136 
137             validateModifiersAndSetAccessible(portField, "Port");
138             portField.setInt(testInstance, config.getPort());
139         }
140 
141         // set @BaseUri field
142         final Field baseUriField =
143                 JitrUtils.getMostSpecificAnnotatedDeclaredField(testInstance.getClass(),
144                         BaseUri.class);
145 
146         if (baseUriField != null) {
147 
148             validateModifiersAndSetAccessible(baseUriField, "BaseUri");
149             baseUriField.set(testInstance, config.getBaseUri());
150         }
151 
152         return testInstance;
153     }
154 
155     private ServletContext initAndStartContainerIfNeeded() {
156 
157         // if the container is disabled, then we don't need to do any of this
158         if (!config.getMode().isContainerEnabled()) {
159             return null;
160         }
161 
162         final Class<?> containerClass = config.getContainerClass();
163 
164         // try to instantiate the container implementation class
165         try {
166             Constructor<?> containerConstructor = containerClass.getConstructor();
167             container = (Container<?>) containerConstructor.newInstance();
168 
169         } catch (final Exception e) {
170             throw new JitrException("Could not instantiate Container implementation class: "
171                     + containerClass.toString(), e);
172         }
173 
174         if (LOG.isInfoEnabled()) {
175             LOG.info("Initializing container: " + container.getName());
176         }
177 
178         ServletContext servletContext =
179                 container.initialize(config, getTestClass().getJavaClass().getAnnotations());
180 
181         if (LOG.isInfoEnabled()) {
182             LOG.info("Starting container: " + container.getName());
183         }
184 
185         container.start();
186 
187         return servletContext;
188     }
189 
190     private void setSpringAutowireCapableBeanFactory(final ServletContext servletContext) {
191 
192         // using same Spring application context as webapp
193         if (config.getMode() == OperationalMode.INTERNAL) {
194 
195             // load web app context from current Thread
196             WebApplicationContext webApplicationContext =
197                     org.springframework.web.context.ContextLoader.getCurrentWebApplicationContext();
198 
199             // sometimes Spring will be loaded in another thread, make sure we have a ServletContext
200             if (webApplicationContext == null && servletContext == null) {
201                 throw new JitrException(
202                         "Servlet context was null and no Spring web application context could"
203                                 + " be found in the current Thread.");
204             }
205 
206             if (webApplicationContext == null) {
207                 webApplicationContext =
208                         WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
209             }
210 
211             // make sure there is a Spring web application context
212             if (webApplicationContext == null) {
213                 LOG.warn("No Spring web application context was found and Jitr is not configured "
214                         + "to use an external Spring application context configuration.");
215                 return;
216             }
217 
218             autowireCapableBeanFactory = webApplicationContext.getAutowireCapableBeanFactory();
219 
220             return;
221         }
222 
223         // need to load an external Spring application context configuration
224         ContextConfiguration contextConfiguration =
225                 AnnotationUtils.findAnnotation(getTestClass().getJavaClass(),
226                         ContextConfiguration.class);
227 
228         // make sure we can find the configuration
229         if (contextConfiguration == null) {
230             throw new JitrException(
231                     "Jitr is configured to use an external Spring application context but none"
232                             + " has been configured. Use @ContextConfiguration on your test class"
233                             + " to define what Spring application context configurations should"
234                             + " be loaded.");
235         }
236 
237         // TODO: Implement location inheritance.
238         // boolean inheritLocations =
239         // contextConfiguration.inheritLocations();
240 
241         Class<? extends ContextLoader> contextLoaderClass = contextConfiguration.loader();
242         String[] locations = contextConfiguration.locations();
243         ApplicationContext applicationContext;
244 
245         // if a ContextLoader class was specified on the annotation, use it
246         if (contextLoaderClass != null && contextLoaderClass != ContextLoader.class) {
247             try {
248                 Constructor<? extends ContextLoader> constructor =
249                         contextLoaderClass.getConstructor();
250                 ContextLoader contextLoader = constructor.newInstance();
251                 String[] processedLocations =
252                         contextLoader.processLocations(getTestClass().getJavaClass(), locations);
253                 applicationContext = contextLoader.loadContext(processedLocations);
254 
255             } catch (final Exception e) {
256                 throw new JitrException("Error loading Spring application contexts.", e);
257             }
258         } else {
259             // use a default XML context loader
260             applicationContext = new ClassPathXmlApplicationContext(locations);
261         }
262 
263         autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
264     }
265 
266     private void validateModifiersAndSetAccessible(final Field field, final String annotationName) {
267 
268         final int modifiers = field.getModifiers();
269         final Collection<String> problemModifiers = new LinkedList<String>();
270 
271         if (Modifier.isStatic(modifiers)) {
272             problemModifiers.add("static");
273         }
274 
275         if (Modifier.isFinal(modifiers)) {
276             problemModifiers.add("final");
277         }
278 
279         if (!problemModifiers.isEmpty()) {
280 
281             final Iterator<String> iterator = problemModifiers.iterator();
282             final StringBuilder stringBuilder = new StringBuilder();
283 
284             while (iterator.hasNext()) {
285                 stringBuilder.append(iterator.next());
286 
287                 if (iterator.hasNext()) {
288                     stringBuilder.append(", ");
289                 }
290             }
291 
292             throw new JitrException("Field annotated with @" + annotationName + " cannot be: "
293                     + stringBuilder.toString());
294         }
295 
296         field.setAccessible(true);
297     }
298 }