|
1 | 1 | package cucumber.runtime.java.spring;
|
2 | 2 |
|
3 |
| -import java.io.PrintWriter; |
4 |
| -import java.io.StringWriter; |
5 |
| -import java.util.Collection; |
6 |
| -import java.util.HashMap; |
7 |
| -import java.util.HashSet; |
8 |
| -import java.util.Map; |
9 |
| - |
| 3 | +import cucumber.runtime.CucumberException; |
| 4 | +import cucumber.runtime.java.ObjectFactory; |
| 5 | +import org.springframework.beans.BeansException; |
10 | 6 | import org.springframework.beans.factory.config.BeanDefinition;
|
11 | 7 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
12 | 8 | import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
13 | 9 | import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
14 | 10 | import org.springframework.context.ConfigurableApplicationContext;
|
15 |
| -import org.springframework.context.support.GenericXmlApplicationContext; |
| 11 | +import org.springframework.context.support.ClassPathXmlApplicationContext; |
| 12 | +import org.springframework.context.support.GenericApplicationContext; |
16 | 13 | import org.springframework.test.context.ContextConfiguration;
|
17 | 14 | import org.springframework.test.context.ContextHierarchy;
|
18 | 15 | import org.springframework.test.context.TestContextManager;
|
19 | 16 |
|
20 |
| -import cucumber.runtime.CucumberException; |
21 |
| -import cucumber.runtime.java.ObjectFactory; |
| 17 | +import java.lang.annotation.Annotation; |
| 18 | +import java.util.Collection; |
| 19 | +import java.util.HashSet; |
22 | 20 |
|
23 | 21 | /**
|
24 | 22 | * Spring based implementation of ObjectFactory.
|
25 | 23 | * <p/>
|
26 | 24 | * <p>
|
27 | 25 | * <ul>
|
28 |
| - * <li>It uses TestContextManager to create and prepare test instances. Configuration via: @ContextConfiguration |
| 26 | + * <li>It uses TestContextManager to manage the spring context. |
| 27 | + * Configuration via: @ContextConfiguration or @ContextHierarcy |
| 28 | + * At least on step definition class needs to have a @ContextConfiguration or |
| 29 | + * @ContextHierarchy annotation. If more that one step definition class has such |
| 30 | + * an annotation, the annotations must be equal on the different step definition |
| 31 | + * classes. If no step definition class with @ContextConfiguration or |
| 32 | + * @ContextHierarcy is found, it will try to load cucumber.xml from the classpath. |
29 | 33 | * </li>
|
30 |
| - * <li>It also uses a context which contains the step definitions and is reloaded for each |
31 |
| - * scenario.</li> |
| 34 | + * <li>The step definitions class with @ContextConfiguration or @ContextHierarchy |
| 35 | + * annotation, may also have a @WebAppConfiguration or @DirtiesContext annotation. |
| 36 | + * </li> |
| 37 | + * <li>The step definitions added to the TestContextManagers context and |
| 38 | + * is reloaded for each scenario.</li> |
32 | 39 | * </ul>
|
33 | 40 | * </p>
|
34 | 41 | * <p/>
|
|
39 | 46 | */
|
40 | 47 | public class SpringFactory implements ObjectFactory {
|
41 | 48 |
|
42 |
| - private static ConfigurableApplicationContext applicationContext; |
43 |
| - private static ConfigurableListableBeanFactory beanFactory; |
| 49 | + private ConfigurableListableBeanFactory beanFactory; |
| 50 | + private CucumberTestContextManager testContextManager; |
44 | 51 |
|
45 | 52 | private final Collection<Class<?>> stepClasses = new HashSet<Class<?>>();
|
46 |
| - private final Map<Class<?>, TestContextManager> contextManagersByClass = new HashMap<Class<?>, TestContextManager>(); |
| 53 | + private Class<?> stepClassWithSpringContext = null; |
47 | 54 |
|
48 | 55 | public SpringFactory() {
|
49 | 56 | }
|
50 | 57 |
|
51 |
| - static { |
52 |
| - applicationContext = new GenericXmlApplicationContext("cucumber/runtime/java/spring/cucumber-glue.xml"); |
53 |
| - applicationContext.registerShutdownHook(); |
54 |
| - beanFactory = applicationContext.getBeanFactory(); |
55 |
| - } |
56 |
| - |
57 | 58 | @Override
|
58 | 59 | public void addClass(final Class<?> stepClass) {
|
59 | 60 | if (!stepClasses.contains(stepClass)) {
|
| 61 | + if (dependsOnSpringContext(stepClass)) { |
| 62 | + if (stepClassWithSpringContext == null) { |
| 63 | + stepClassWithSpringContext = stepClass; |
| 64 | + } else { |
| 65 | + checkAnnotationsEqual(stepClassWithSpringContext, stepClass); |
| 66 | + } |
| 67 | + } |
60 | 68 | stepClasses.add(stepClass);
|
61 | 69 |
|
62 |
| - BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory(); |
63 |
| - BeanDefinition beanDefinition = BeanDefinitionBuilder |
64 |
| - .genericBeanDefinition(stepClass) |
65 |
| - .setScope(GlueCodeScope.NAME) |
66 |
| - .getBeanDefinition(); |
67 |
| - registry.registerBeanDefinition(stepClass.getName(), beanDefinition); |
68 | 70 | }
|
69 | 71 | }
|
70 | 72 |
|
| 73 | + private void checkAnnotationsEqual(Class<?> stepClassWithSpringContext, Class<?> stepClass) { |
| 74 | + Annotation[] annotations1 = stepClassWithSpringContext.getAnnotations(); |
| 75 | + Annotation[] annotations2 = stepClass.getAnnotations(); |
| 76 | + if (annotations1.length != annotations2.length) { |
| 77 | + throw new CucumberException("Annotations differs on glue classes found: " + |
| 78 | + stepClassWithSpringContext.getName() + ", " + |
| 79 | + stepClass.getName()); |
| 80 | + } |
| 81 | + for (Annotation annotation : annotations1) { |
| 82 | + if (!isAnnotationInArray(annotation, annotations2)) { |
| 83 | + throw new CucumberException("Annotations differs on glue classes found: " + |
| 84 | + stepClassWithSpringContext.getName() + ", " + |
| 85 | + stepClass.getName()); |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + private boolean isAnnotationInArray(Annotation annotation, Annotation[] annotations) { |
| 91 | + for (Annotation annotationFromArray: annotations) { |
| 92 | + if (annotation.equals(annotationFromArray)) { |
| 93 | + return true; |
| 94 | + } |
| 95 | + } |
| 96 | + return false; |
| 97 | + } |
| 98 | + |
71 | 99 | @Override
|
72 | 100 | public void start() {
|
| 101 | + if (stepClassWithSpringContext != null) { |
| 102 | + testContextManager = new CucumberTestContextManager(stepClassWithSpringContext); |
| 103 | + } else { |
| 104 | + if (beanFactory == null) { |
| 105 | + beanFactory = createFallbackContext(); |
| 106 | + } |
| 107 | + } |
| 108 | + notifyContextManagerAboutTestClassStarted(); |
| 109 | + if (beanFactory == null || isNewContextCreated()) { |
| 110 | + beanFactory = testContextManager.getBeanFactory(); |
| 111 | + for (Class<?> stepClass : stepClasses) { |
| 112 | + registerStepClassBeanDefinition(beanFactory, stepClass); |
| 113 | + } |
| 114 | + } |
73 | 115 | GlueCodeContext.INSTANCE.start();
|
74 | 116 | }
|
75 | 117 |
|
76 |
| - @Override |
77 |
| - public void stop() { |
78 |
| - notifyContextManagersAboutTestClassFinished(); |
79 |
| - |
80 |
| - GlueCodeContext.INSTANCE.stop(); |
81 |
| - beanFactory.destroySingletons(); |
| 118 | + @SuppressWarnings("resource") |
| 119 | + private ConfigurableListableBeanFactory createFallbackContext() { |
| 120 | + ConfigurableApplicationContext applicationContext; |
| 121 | + if (getClass().getClassLoader().getResource("cucumber.xml") != null) { |
| 122 | + applicationContext = new ClassPathXmlApplicationContext("cucumber.xml"); |
| 123 | + } else { |
| 124 | + applicationContext = new GenericApplicationContext(); |
| 125 | + } |
| 126 | + applicationContext.registerShutdownHook(); |
| 127 | + ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); |
| 128 | + beanFactory.registerScope(GlueCodeScope.NAME, new GlueCodeScope()); |
| 129 | + for (Class<?> stepClass : stepClasses) { |
| 130 | + registerStepClassBeanDefinition(beanFactory, stepClass); |
| 131 | + } |
| 132 | + return beanFactory; |
82 | 133 | }
|
83 | 134 |
|
84 |
| - private void notifyContextManagersAboutTestClassFinished() { |
85 |
| - Map<Class<?>, Exception> exceptionsThrown = new HashMap<Class<?>, Exception>(); |
86 |
| - |
87 |
| - for (Map.Entry<Class<?>, TestContextManager> classTestContextManagerEntry : contextManagersByClass |
88 |
| - .entrySet()) { |
| 135 | + private void notifyContextManagerAboutTestClassStarted() { |
| 136 | + if (testContextManager != null) { |
89 | 137 | try {
|
90 |
| - classTestContextManagerEntry.getValue().afterTestClass(); |
| 138 | + testContextManager.beforeTestClass(); |
91 | 139 | } catch (Exception e) {
|
92 |
| - exceptionsThrown.put(classTestContextManagerEntry.getKey(), e); |
| 140 | + throw new CucumberException(e.getMessage(), e); |
93 | 141 | }
|
94 | 142 | }
|
95 |
| - |
96 |
| - contextManagersByClass.clear(); |
97 |
| - |
98 |
| - rethrowExceptionsIfAny(exceptionsThrown); |
99 | 143 | }
|
100 | 144 |
|
101 |
| - private void rethrowExceptionsIfAny(Map<Class<?>, Exception> exceptionsThrown) { |
102 |
| - if (exceptionsThrown.isEmpty()) { |
103 |
| - return; |
| 145 | + private boolean isNewContextCreated() { |
| 146 | + if (testContextManager == null) { |
| 147 | + return false; |
104 | 148 | }
|
105 |
| - |
106 |
| - if (exceptionsThrown.size() == 1) { |
107 |
| - //ony one exception, throw an exception with the correct cause |
108 |
| - Exception e = exceptionsThrown.values().iterator().next(); |
109 |
| - throw new CucumberException(e.getMessage(), e); |
110 |
| - } |
111 |
| - |
112 |
| - //multiple exceptions but we can only have one cause, put relevant info in the exception message |
113 |
| - //to not lose the interesting data |
114 |
| - throw new CucumberException(getMultipleExceptionMessage(exceptionsThrown)); |
| 149 | + return !beanFactory.equals(testContextManager.getBeanFactory()); |
115 | 150 | }
|
116 | 151 |
|
117 |
| - private String getMultipleExceptionMessage(Map<Class<?>, Exception> exceptionsThrow) { |
118 |
| - StringBuilder exceptionsThrown = new StringBuilder(1000); |
119 |
| - exceptionsThrown.append("Multiple exceptions occurred during processing of the TestExecutionListeners\n\n"); |
120 |
| - |
121 |
| - for (Map.Entry<Class<?>, Exception> classExceptionEntry : exceptionsThrow.entrySet()) { |
122 |
| - exceptionsThrown.append("Exception during processing of TestExecutionListeners of "); |
123 |
| - exceptionsThrown.append(classExceptionEntry.getKey()); |
124 |
| - exceptionsThrown.append('\n'); |
125 |
| - exceptionsThrown.append(classExceptionEntry.getValue().toString()); |
126 |
| - exceptionsThrown.append('\n'); |
| 152 | + private void registerStepClassBeanDefinition(ConfigurableListableBeanFactory beanFactory, Class<?> stepClass) { |
| 153 | + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; |
| 154 | + BeanDefinition beanDefinition = BeanDefinitionBuilder |
| 155 | + .genericBeanDefinition(stepClass) |
| 156 | + .setScope(GlueCodeScope.NAME) |
| 157 | + .getBeanDefinition(); |
| 158 | + registry.registerBeanDefinition(stepClass.getName(), beanDefinition); |
| 159 | + } |
127 | 160 |
|
128 |
| - StringWriter stackTraceStringWriter = new StringWriter(); |
129 |
| - PrintWriter stackTracePrintWriter = new PrintWriter(stackTraceStringWriter); |
130 |
| - classExceptionEntry.getValue().printStackTrace(stackTracePrintWriter); |
131 |
| - exceptionsThrown.append(stackTraceStringWriter.toString()); |
132 |
| - exceptionsThrown.append('\n'); |
| 161 | + @Override |
| 162 | + public void stop() { |
| 163 | + notifyContextManagerAboutTestClassFinished(); |
| 164 | + GlueCodeContext.INSTANCE.stop(); |
| 165 | + } |
133 | 166 |
|
| 167 | + private void notifyContextManagerAboutTestClassFinished() { |
| 168 | + if (testContextManager != null) { |
| 169 | + try { |
| 170 | + testContextManager.afterTestClass(); |
| 171 | + } catch (Exception e) { |
| 172 | + throw new CucumberException(e.getMessage(), e); |
| 173 | + } |
134 | 174 | }
|
135 |
| - |
136 |
| - return exceptionsThrown.toString(); |
137 | 175 | }
|
138 | 176 |
|
139 | 177 | @Override
|
140 | 178 | public <T> T getInstance(final Class<T> type) {
|
141 |
| - if (!beanFactory.containsSingleton(type.getName())) { |
142 |
| - beanFactory.registerSingleton(type.getName(), getTestInstance(type)); |
| 179 | + try { |
| 180 | + return beanFactory.getBean(type); |
| 181 | + } catch (BeansException e) { |
| 182 | + throw new CucumberException(e.getMessage(), e); |
143 | 183 | }
|
144 |
| - |
145 |
| - return applicationContext.getBean(type); |
146 | 184 | }
|
147 | 185 |
|
148 |
| - private <T> T getTestInstance(final Class<T> type) { |
149 |
| - try { |
150 |
| - T instance = createTest(type); |
| 186 | + private boolean dependsOnSpringContext(Class<?> type) { |
| 187 | + return type.isAnnotationPresent(ContextConfiguration.class) |
| 188 | + || type.isAnnotationPresent(ContextHierarchy.class); |
| 189 | + } |
| 190 | +} |
151 | 191 |
|
152 |
| - if (dependsOnSpringContext(type)) { |
153 |
| - TestContextManager contextManager = new TestContextManager(type); |
154 |
| - contextManager.prepareTestInstance(instance); |
155 |
| - contextManager.beforeTestClass(); |
| 192 | +class CucumberTestContextManager extends TestContextManager { |
156 | 193 |
|
157 |
| - contextManagersByClass.put(type, contextManager); |
158 |
| - } |
| 194 | + public CucumberTestContextManager(Class<?> testClass) { |
| 195 | + super(testClass); |
| 196 | + registerGlueCodeScope(getContext()); |
| 197 | + } |
159 | 198 |
|
160 |
| - return instance; |
161 |
| - } catch (Exception e) { |
162 |
| - throw new CucumberException(e.getMessage(), e); |
163 |
| - } |
| 199 | + public ConfigurableListableBeanFactory getBeanFactory() { |
| 200 | + return getContext().getBeanFactory(); |
164 | 201 | }
|
165 | 202 |
|
166 |
| - @SuppressWarnings("unchecked") |
167 |
| - protected <T> T createTest(Class<T> type) throws Exception { |
168 |
| - return (T) type.getConstructors()[0].newInstance(); |
| 203 | + private ConfigurableApplicationContext getContext() { |
| 204 | + return (ConfigurableApplicationContext)getTestContext().getApplicationContext(); |
169 | 205 | }
|
170 | 206 |
|
171 |
| - private boolean dependsOnSpringContext(Class<?> type) { |
172 |
| - return type.isAnnotationPresent(ContextConfiguration.class) |
173 |
| - || type.isAnnotationPresent(ContextHierarchy.class); |
| 207 | + private void registerGlueCodeScope(ConfigurableApplicationContext context) { |
| 208 | + do { |
| 209 | + context.getBeanFactory().registerScope(GlueCodeScope.NAME, new GlueCodeScope()); |
| 210 | + context = (ConfigurableApplicationContext)context.getParent(); |
| 211 | + } while (context != null); |
174 | 212 | }
|
175 | 213 | }
|
0 commit comments