Skip to content

Commit 4689424

Browse files
committed
BroadleafCommerce#1127 Added a Broadleaf-merge-behavior SmartContextLoader for use with Spring 4 Integration tests
1 parent 9b1011a commit 4689424

File tree

1 file changed

+270
-0
lines changed

1 file changed

+270
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
/*
2+
* #%L
3+
* BroadleafCommerce Custom Field
4+
* %%
5+
* Copyright (C) 2009 - 2014 Broadleaf Commerce
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
21+
package org.broadleafcommerce.test;
22+
23+
import org.apache.commons.lang3.StringUtils;
24+
import org.broadleafcommerce.common.extensibility.context.StandardConfigLocations;
25+
import org.broadleafcommerce.common.web.extensibility.MergeXmlWebApplicationContext;
26+
import org.springframework.context.ApplicationContext;
27+
import org.springframework.context.ConfigurableApplicationContext;
28+
import org.springframework.core.io.DefaultResourceLoader;
29+
import org.springframework.core.io.FileSystemResourceLoader;
30+
import org.springframework.core.io.ResourceLoader;
31+
import org.springframework.mock.web.MockServletContext;
32+
import org.springframework.test.context.MergedContextConfiguration;
33+
import org.springframework.test.context.support.AbstractContextLoader;
34+
import org.springframework.test.context.web.AbstractGenericWebContextLoader;
35+
import org.springframework.test.context.web.WebMergedContextConfiguration;
36+
import org.springframework.util.Assert;
37+
import org.springframework.util.ObjectUtils;
38+
import org.springframework.web.context.WebApplicationContext;
39+
40+
import javax.servlet.ServletContext;
41+
42+
/**
43+
* This class was created due to AbstractGenericWebContextLoader utilizing the qualifier "final" for its
44+
* loadContext(MergedContextConfiguration) method which we needed to override and provide our Broadleaf created
45+
* MergeXmlWebApplicationContext object in place of the GenericWebApplicationContext it used. Since we are using
46+
* Groovy/Spock, the other methods included are to support Groovy test classes. As such, are included some methods
47+
* from the GenericGroovyXmlWebContextLoader and GenericXmlWebContextLoader classes, refactored to compensate for
48+
* the levels of inheritance between them.
49+
*
50+
* This class loader should be used with the @ContextConfiguration annotation to be placed in the 'loader'
51+
* parameter for all Broadleaf integration tests which use the Spock testing framework.
52+
*
53+
* @author austinrooke
54+
*
55+
*/
56+
public class BroadleafGenericGroovyXmlWebContextLoader extends AbstractContextLoader {
57+
58+
/*
59+
* Some notes on usage of this context loader.
60+
*
61+
* The set up for the spock/groovy test is as follows. You will need to include the dependencies:
62+
* spock-spring
63+
* spring-beans
64+
* spring-test
65+
* javax-servlet
66+
* integration
67+
*
68+
* Then you will need to also set the following notations on the test class itself:
69+
* @TransactionConfiguration(transactionManager = "blTransactionManager")
70+
* @ContextConfiguration(locations = [Include application context's that are required for your
71+
* integration test but you MUST include the following application context as the last
72+
* entry into this set, "classpath:/bl-applicationContext-test.xml"]
73+
* @WebAppConfiguration
74+
*
75+
* With these annotations, you are now set up to test any features and classes.
76+
*
77+
* In addition, with the usage of spring's testframework and spock/groovy, we can also test
78+
* RESTful services directly utilizing spring's mockMVC api. Please see its documentation
79+
* if you would like to test RESTful services.
80+
*/
81+
82+
/**
83+
* {@code BroadleafGenericGroovyXmlWebContextLoader} supports the XML merging that
84+
* Broadleaf's framework features, but this is handled during the .refresh() method
85+
* in the loadContext method so this method was only pulled from the original class
86+
* {@code GenericGroovyXmlWebContextLoader} in case of the Spring-Test framework requiring
87+
* this method to have a particular behavior.
88+
*/
89+
protected String[] getResourceSuffixes() {
90+
return new String[] { "-context.xml", "Context.groovy" };
91+
}
92+
93+
/**
94+
* {@code BroadleafGenericGroovyXmlWebContextLoader} supports the XML merging that
95+
* Broadleaf's framework features, but this is handled during the .refresh() method
96+
* in the loadContext method so this method was only pulled from the original class
97+
* {@code GenericGroovyXmlWebContextLoader} in case of the Spring-Test framework requiring
98+
* this method to have a particular behavior.
99+
*/
100+
@Override
101+
protected String getResourceSuffix() {
102+
throw new UnsupportedOperationException(
103+
"BroadleafGenericGroovyXmlWebContextLoader does not support the getResourceSuffix() method");
104+
}
105+
106+
/**
107+
* {@code BroadleafGenericGroovyXmlWebContextLoader} supports the XML merging that
108+
* Broadleaf's framework features, but this is handled during the .refresh() method
109+
* in the loadContext method so this method was only pulled from the original class
110+
* {@code GenericXmlWebContextLoader} in case of the Spring-Test framework requiring
111+
* this method to have a particular behavior.
112+
*
113+
* @see AbstractGenericWebContextLoader#validateMergedContextConfiguration
114+
*/
115+
protected void validateMergedContextConfiguration(WebMergedContextConfiguration webMergedConfig) {
116+
if (webMergedConfig.hasClasses()) {
117+
String msg = String.format(
118+
"Test class [%s] has been configured with @ContextConfiguration's 'classes' attribute %s, "
119+
+ "but %s does not support annotated classes.", webMergedConfig.getTestClass().getName(),
120+
ObjectUtils.nullSafeToString(webMergedConfig.getClasses()), getClass().getSimpleName());
121+
throw new IllegalStateException(msg);
122+
}
123+
}
124+
125+
/**
126+
* {@code BroadleafGenericGroovyXmlWebContextLoader} should be used as a
127+
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
128+
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
129+
* Consequently, this method is not supported.
130+
*
131+
* This method was pulled from {@code AbstractGenericWebContextLoader}.
132+
*
133+
* @see org.springframework.test.context.ContextLoader#loadContext(java.lang.String[])
134+
* @throws UnsupportedOperationException
135+
*/
136+
@Override
137+
public final ApplicationContext loadContext(String... locations) throws Exception {
138+
throw new UnsupportedOperationException(
139+
"BroadleafGenericGroovyXmlWebContextLoader does not support the loadContext(String... locations) method");
140+
}
141+
142+
/**
143+
* Load a {@link MergeXmlWebApplicationContext} from the supplied {@link MergedContextConfiguration}
144+
*
145+
* <p>Implementation details:
146+
*
147+
* <ul>
148+
* <li>Calls {@link #validateMergedContextConfiguration(WebMergedContextConfiguration)}
149+
* to allow subclasses to validate the supplied configuration before proceeding.</li>
150+
* <li>Creates a {@link MergeXmlWebApplicationContext} instance.</li>
151+
* <li>If the supplied {@link MergeXmlWebApplicationContext} references a
152+
* {@linkplain MergeXmlWebApplicationContext#getParent() parent configuration},
153+
* the corresponding {@link MergeXmlWebApplicationContext#getParentApplicationContext()
154+
* ApplicationContext} will be retrieved and
155+
* {@linkplain MergeXmlWebApplicationContext#setParent(ApplicationContext) set as the parent}
156+
* for the context created by this method.</li>
157+
* <li>Converts the patch locations into a single string to be set via
158+
* {@link MergeXmlWebApplicationContext#setPatchLocation(String)}</li>
159+
* <li>Sets the patch locations via {@link MergeXmlWebApplicationContext#setStandardLocationTypes(String)}
160+
* to the {@link StandardConfigLocations.TESTCONTEXTTYPE} for integration tests.</li>
161+
* <li>Delegates to {@link #configureWebResources} to create the {@link MockServletContext} and
162+
* set it in the {@code MergeXmlWebApplicationContext}.</li>
163+
* <li>Calls {@link #prepareContext} to allow for customizing the context before bean
164+
* definitions are loaded.</li>
165+
* <li>{@link ConfigurableApplicationContext#refresh Refreshes} the context and registers
166+
* a JVM shutdown hook for it.</li>
167+
* </ul></p>
168+
*
169+
* Refactored from {@code org.springframework.test.context.web.AbstractGenericWebContextLoader}
170+
*
171+
* @return a new merge xml web application context
172+
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
173+
* @see MergeXmlWebApplicationContext
174+
*/
175+
@Override
176+
public ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
177+
178+
if (!(mergedConfig instanceof WebMergedContextConfiguration)) {
179+
throw new IllegalArgumentException(String.format(
180+
"Cannot load WebApplicationContext from non-web merged context configuration %s. "
181+
+ "Consider annotating your test class with @WebAppConfiguration.", mergedConfig));
182+
}
183+
WebMergedContextConfiguration webMergedConfig = (WebMergedContextConfiguration) mergedConfig;
184+
185+
validateMergedContextConfiguration(webMergedConfig);
186+
187+
MergeXmlWebApplicationContext context = new MergeXmlWebApplicationContext();
188+
189+
ApplicationContext parent = mergedConfig.getParentApplicationContext();
190+
if (parent != null) {
191+
context.setParent(parent);
192+
}
193+
context.setPatchLocation(StringUtils.join(mergedConfig.getLocations(), ";"));
194+
context.setStandardLocationTypes(StandardConfigLocations.TESTCONTEXTTYPE);
195+
configureWebResources(context, webMergedConfig);
196+
prepareContext(context, webMergedConfig);
197+
context.refresh();
198+
context.registerShutdownHook();
199+
return context;
200+
}
201+
202+
/**
203+
* Configures web resources for the supplied web application context (WAC).
204+
*
205+
* <h4>Implementation Details</h4>
206+
*
207+
* <p>If the supplied WAC has no parent or its parent is not a WAC, the
208+
* supplied WAC will be configured as the Root WAC (see "<em>Root WAC
209+
* Configuration</em>" below).
210+
*
211+
* <p>Otherwise the context hierarchy of the supplied WAC will be traversed
212+
* to find the top-most WAC (i.e., the root); and the {@link ServletContext}
213+
* of the Root WAC will be set as the {@code ServletContext} for the supplied
214+
* WAC.
215+
*
216+
* <h4>Root WAC Configuration</h4>
217+
*
218+
* <ul>
219+
* <li>The resource base path is retrieved from the supplied
220+
* {@code WebMergedContextConfiguration}.</li>
221+
* <li>A {@link ResourceLoader} is instantiated for the {@link MockServletContext}:
222+
* if the resource base path is prefixed with "{@code classpath:/}", a
223+
* {@link DefaultResourceLoader} will be used; otherwise, a
224+
* {@link FileSystemResourceLoader} will be used.</li>
225+
* <li>A {@code MockServletContext} will be created using the resource base
226+
* path and resource loader.</li>
227+
* <li>The supplied {@link MergeXmlWebApplicationContext} is then stored in
228+
* the {@code MockServletContext} under the
229+
* {@link MergeXmlWebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} key.</li>
230+
* <li>Finally, the {@code MockServletContext} is set in the
231+
* {@code MergeXmlWebApplicationContext}.</li>
232+
*
233+
* @param context the merge xml web application context for which to configure the web
234+
* resources
235+
* @param webMergedConfig the merged context configuration to use to load the
236+
* merge xml web application context
237+
*/
238+
protected void configureWebResources(MergeXmlWebApplicationContext context,
239+
WebMergedContextConfiguration webMergedConfig) {
240+
241+
ApplicationContext parent = context.getParent();
242+
243+
// if the WAC has no parent or the parent is not a WAC, set the WAC as
244+
// the Root WAC:
245+
if (parent == null || (!(parent instanceof WebApplicationContext))) {
246+
String resourceBasePath = webMergedConfig.getResourceBasePath();
247+
ResourceLoader resourceLoader = resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
248+
: new FileSystemResourceLoader();
249+
250+
ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader);
251+
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
252+
context.setServletContext(servletContext);
253+
}
254+
else {
255+
ServletContext servletContext = null;
256+
257+
// find the Root WAC
258+
while (parent != null) {
259+
if (parent instanceof WebApplicationContext && !(parent.getParent() instanceof WebApplicationContext)) {
260+
servletContext = ((WebApplicationContext) parent).getServletContext();
261+
break;
262+
}
263+
parent = parent.getParent();
264+
}
265+
Assert.state(servletContext != null, "Failed to find Root MergeXmlWebApplicationContext in the context hierarchy");
266+
context.setServletContext(servletContext);
267+
}
268+
}
269+
270+
}

0 commit comments

Comments
 (0)