Skip to content

Commit 04cff89

Browse files
committed
Update ScriptTemplateView to manage content type
This commit introduces the following changes: - Content type can now be properly configured - Default content type is "text/html" - Content type and charset are now properly set in the response Issue: SPR-13379
1 parent 88405be commit 04cff89

File tree

9 files changed

+143
-20
lines changed

9 files changed

+143
-20
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/ScriptTemplateConfigurerBeanDefinitionParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
6767
if (element.hasAttribute("render-function")) {
6868
builder.addPropertyValue("renderFunction", element.getAttribute("render-function"));
6969
}
70+
if (element.hasAttribute("content-type")) {
71+
builder.addPropertyValue("contentType", element.getAttribute("content-type"));
72+
}
7073
if (element.hasAttribute("charset")) {
7174
builder.addPropertyValue("charset", Charset.forName(element.getAttribute("charset")));
7275
}
@@ -81,7 +84,8 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
8184
@Override
8285
protected boolean isEligibleAttribute(String name) {
8386
return (name.equals("engine-name") || name.equals("scripts") || name.equals("render-object") ||
84-
name.equals("render-function") || name.equals("charset") || name.equals("resource-loader-path"));
87+
name.equals("render-function") || name.equals("content-type") ||
88+
name.equals("charset") || name.equals("resource-loader-path"));
8589
}
8690

8791
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public interface ScriptTemplateConfig {
6060
*/
6161
String getRenderFunction();
6262

63+
/**
64+
* Return the content type to use for the response.
65+
* @since 4.2.1
66+
*/
67+
String getContentType();
68+
6369
/**
6470
* Return the charset used to read script and template files.
6571
*/

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
5959

6060
private String renderFunction;
6161

62+
private String contentType;
63+
6264
private Charset charset;
6365

6466
private String resourceLoaderPath;
@@ -170,6 +172,24 @@ public String getRenderFunction() {
170172
return this.renderFunction;
171173
}
172174

175+
/**
176+
* Set the content type to use for the response.
177+
* ({@code text/html} by default).
178+
* @since 4.2.1
179+
*/
180+
public void setContentType(String contentType) {
181+
this.contentType = contentType;
182+
}
183+
184+
/**
185+
* Return the content type to use for the response.
186+
* @since 4.2.1
187+
*/
188+
@Override
189+
public String getContentType() {
190+
return this.contentType;
191+
}
192+
173193
/**
174194
* Set the charset used to read script and template files.
175195
* ({@code UTF-8} by default).

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
*/
6464
public class ScriptTemplateView extends AbstractUrlBasedView {
6565

66+
public static final String DEFAULT_CONTENT_TYPE = "text/html";
67+
6668
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
6769

6870
private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:";
@@ -89,6 +91,24 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
8991
private String resourceLoaderPath;
9092

9193

94+
/**
95+
* Constructor for use as a bean.
96+
* @see #setUrl
97+
*/
98+
public ScriptTemplateView() {
99+
setContentType(null);
100+
}
101+
102+
/**
103+
* Create a new ScriptTemplateView with the given URL.
104+
* @since 4.2.1
105+
*/
106+
public ScriptTemplateView(String url) {
107+
super(url);
108+
setContentType(null);
109+
}
110+
111+
92112
/**
93113
* See {@link ScriptTemplateConfigurer#setEngine(ScriptEngine)} documentation.
94114
*/
@@ -132,6 +152,15 @@ public void setRenderFunction(String functionName) {
132152
this.renderFunction = functionName;
133153
}
134154

155+
/**
156+
* See {@link ScriptTemplateConfigurer#setContentType(String)}} documentation.
157+
* @since 4.2.1
158+
*/
159+
@Override
160+
public void setContentType(String contentType) {
161+
super.setContentType(contentType);
162+
}
163+
135164
/**
136165
* See {@link ScriptTemplateConfigurer#setCharset(Charset)} documentation.
137166
*/
@@ -167,6 +196,9 @@ protected void initApplicationContext(ApplicationContext context) {
167196
if (this.renderFunction == null && viewConfig.getRenderFunction() != null) {
168197
this.renderFunction = viewConfig.getRenderFunction();
169198
}
199+
if (this.getContentType() == null) {
200+
setContentType(viewConfig.getContentType() != null ? viewConfig.getContentType() : DEFAULT_CONTENT_TYPE);
201+
}
170202
if (this.charset == null) {
171203
this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET);
172204
}
@@ -276,10 +308,17 @@ protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
276308
}
277309
}
278310

311+
@Override
312+
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
313+
super.prepareResponse(request, response);
314+
315+
setResponseContentType(request, response);
316+
response.setCharacterEncoding(this.charset.name());
317+
}
279318

280319
@Override
281-
protected void renderMergedOutputModel(
282-
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
320+
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
321+
HttpServletResponse response) throws Exception {
283322

284323
try {
285324
ScriptEngine engine = getEngine();

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,13 @@
12521252
]]></xsd:documentation>
12531253
</xsd:annotation>
12541254
</xsd:attribute>
1255+
<xsd:attribute name="content-type" type="xsd:string">
1256+
<xsd:annotation>
1257+
<xsd:documentation><![CDATA[
1258+
Set the content type to use for the response (text/html by default).
1259+
]]></xsd:documentation>
1260+
</xsd:annotation>
1261+
</xsd:attribute>
12551262
<xsd:attribute name="charset" type="xsd:string">
12561263
<xsd:annotation>
12571264
<xsd:documentation><![CDATA[

spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ public void testViewResolution() throws Exception {
799799
ScriptTemplateConfigurer scriptTemplateConfigurer = appContext.getBean(ScriptTemplateConfigurer.class);
800800
assertNotNull(scriptTemplateConfigurer);
801801
assertEquals("render", scriptTemplateConfigurer.getRenderFunction());
802+
assertEquals(MediaType.TEXT_PLAIN_VALUE, scriptTemplateConfigurer.getContentType());
802803
assertEquals(StandardCharsets.ISO_8859_1, scriptTemplateConfigurer.getCharset());
803804
assertEquals("classpath:", scriptTemplateConfigurer.getResourceLoaderPath());
804805
assertFalse(scriptTemplateConfigurer.isSharedEngine());

spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.util.ArrayList;
2222
import java.util.Arrays;
23+
import java.util.HashMap;
2324
import java.util.List;
25+
import java.util.Map;
2426
import java.util.concurrent.ExecutorService;
2527
import java.util.concurrent.Executors;
2628
import java.util.concurrent.Future;
@@ -34,6 +36,13 @@
3436
import org.springframework.beans.DirectFieldAccessor;
3537
import org.springframework.context.ApplicationContextException;
3638
import org.springframework.context.support.StaticApplicationContext;
39+
import org.springframework.http.HttpHeaders;
40+
import org.springframework.http.MediaType;
41+
import org.springframework.mock.web.test.MockHttpServletRequest;
42+
import org.springframework.mock.web.test.MockHttpServletResponse;
43+
import org.springframework.mock.web.test.MockServletContext;
44+
import org.springframework.web.context.support.StaticWebApplicationContext;
45+
import org.springframework.web.servlet.DispatcherServlet;
3746

3847
import static org.hamcrest.Matchers.*;
3948
import static org.junit.Assert.*;
@@ -51,18 +60,18 @@ public class ScriptTemplateViewTests {
5160

5261
private ScriptTemplateConfigurer configurer;
5362

54-
private StaticApplicationContext applicationContext;
63+
private StaticWebApplicationContext wac;
5564

5665
private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
5766

5867

5968
@Before
6069
public void setup() {
6170
this.configurer = new ScriptTemplateConfigurer();
62-
this.applicationContext = new StaticApplicationContext();
63-
this.applicationContext.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
71+
this.wac = new StaticWebApplicationContext();
72+
this.wac.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
6473
this.view = new ScriptTemplateView();
65-
this.view.setUrl("sampleView");
74+
this.view.setUrl(RESOURCE_LOADER_PATH + "empty.txt");
6675
}
6776

6877
@Test
@@ -83,30 +92,34 @@ public void detectScriptTemplateConfigWithEngine() {
8392
this.configurer.setEngine(engine);
8493
this.configurer.setRenderObject("Template");
8594
this.configurer.setRenderFunction("render");
95+
this.configurer.setContentType(MediaType.TEXT_PLAIN_VALUE);
8696
this.configurer.setCharset(StandardCharsets.ISO_8859_1);
97+
this.configurer.setSharedEngine(true);
98+
8799
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
88-
this.view.setApplicationContext(this.applicationContext);
100+
this.view.setApplicationContext(this.wac);
89101
assertEquals(engine, accessor.getPropertyValue("engine"));
90102
assertEquals("Template", accessor.getPropertyValue("renderObject"));
91103
assertEquals("render", accessor.getPropertyValue("renderFunction"));
104+
assertEquals(MediaType.TEXT_PLAIN_VALUE, accessor.getPropertyValue("contentType"));
92105
assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
106+
assertEquals(true, accessor.getPropertyValue("sharedEngine"));
93107
}
94108

95109
@Test
96110
public void detectScriptTemplateConfigWithEngineName() {
97111
this.configurer.setEngineName("nashorn");
98112
this.configurer.setRenderObject("Template");
99113
this.configurer.setRenderFunction("render");
100-
this.configurer.setCharset(StandardCharsets.ISO_8859_1);
101-
this.configurer.setSharedEngine(true);
114+
102115
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
103-
this.view.setApplicationContext(this.applicationContext);
116+
this.view.setApplicationContext(this.wac);
104117
assertEquals("nashorn", accessor.getPropertyValue("engineName"));
105118
assertNotNull(accessor.getPropertyValue("engine"));
106119
assertEquals("Template", accessor.getPropertyValue("renderObject"));
107120
assertEquals("render", accessor.getPropertyValue("renderFunction"));
108-
assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
109-
assertEquals(true, accessor.getPropertyValue("sharedEngine"));
121+
assertEquals(MediaType.TEXT_HTML_VALUE, accessor.getPropertyValue("contentType"));
122+
assertEquals(StandardCharsets.UTF_8, accessor.getPropertyValue("charset"));
110123
}
111124

112125
@Test
@@ -115,7 +128,7 @@ public void customEngineAndRenderFunction() throws Exception {
115128
given(engine.get("key")).willReturn("value");
116129
this.view.setEngine(engine);
117130
this.view.setRenderFunction("render");
118-
this.view.setApplicationContext(this.applicationContext);
131+
this.view.setApplicationContext(this.wac);
119132
engine = this.view.getEngine();
120133
assertNotNull(engine);
121134
assertEquals("value", engine.get("key"));
@@ -131,7 +144,7 @@ public void nonSharedEngine() throws Exception {
131144
this.view.setEngineName("nashorn");
132145
this.view.setRenderFunction("render");
133146
this.view.setSharedEngine(false);
134-
this.view.setApplicationContext(this.applicationContext);
147+
this.view.setApplicationContext(this.wac);
135148
ExecutorService executor = Executors.newFixedThreadPool(4);
136149
List<Future<Boolean>> results = new ArrayList<>();
137150
for(int i = 0; i < iterations; i++) {
@@ -160,7 +173,7 @@ public void nonInvocableScriptEngine() throws Exception {
160173
public void noRenderFunctionDefined() {
161174
this.view.setEngine(mock(InvocableScriptEngine.class));
162175
try {
163-
this.view.setApplicationContext(this.applicationContext);
176+
this.view.setApplicationContext(this.wac);
164177
fail("Should have thrown IllegalArgumentException");
165178
}
166179
catch (IllegalArgumentException ex) {
@@ -174,7 +187,7 @@ public void engineAndEngineNameBothDefined() {
174187
this.view.setEngineName("test");
175188
this.view.setRenderFunction("render");
176189
try {
177-
this.view.setApplicationContext(this.applicationContext);
190+
this.view.setApplicationContext(this.wac);
178191
fail("Should have thrown IllegalArgumentException");
179192
}
180193
catch (IllegalArgumentException ex) {
@@ -188,7 +201,7 @@ public void engineSetterAndNonSharedEngine() {
188201
this.view.setRenderFunction("render");
189202
this.view.setSharedEngine(false);
190203
try {
191-
this.view.setApplicationContext(this.applicationContext);
204+
this.view.setApplicationContext(this.wac);
192205
fail("Should have thrown IllegalArgumentException");
193206
}
194207
catch (IllegalArgumentException ex) {
@@ -203,7 +216,7 @@ public void parentLoader() {
203216
this.view.setEngine(mock(InvocableScriptEngine.class));
204217
this.view.setRenderFunction("render");
205218
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
206-
this.view.setApplicationContext(this.applicationContext);
219+
this.view.setApplicationContext(this.wac);
207220
ClassLoader classLoader = this.view.createClassLoader();
208221
assertNotNull(classLoader);
209222
URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
@@ -219,6 +232,38 @@ public void parentLoader() {
219232
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), Matchers.endsWith("org/springframework/web/servlet/view/"));
220233
}
221234

235+
@Test // SPR-13379
236+
public void contentType() throws Exception {
237+
MockServletContext servletContext = new MockServletContext();
238+
this.wac.setServletContext(servletContext);
239+
this.wac.refresh();
240+
MockHttpServletRequest request = new MockHttpServletRequest();
241+
request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.wac);
242+
MockHttpServletResponse response = new MockHttpServletResponse();
243+
Map<String, Object> model = new HashMap<String, Object>();
244+
this.view.setEngine(mock(InvocableScriptEngine.class));
245+
this.view.setRenderFunction("render");
246+
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
247+
this.view.setApplicationContext(this.wac);
248+
249+
this.view.render(model, request, response);
250+
assertEquals(MediaType.TEXT_HTML_VALUE + ";charset=" +
251+
StandardCharsets.UTF_8, response.getHeader(HttpHeaders.CONTENT_TYPE));
252+
253+
response = new MockHttpServletResponse();
254+
this.view.setContentType(MediaType.TEXT_PLAIN_VALUE);
255+
this.view.render(model, request, response);
256+
assertEquals(MediaType.TEXT_PLAIN_VALUE + ";charset=" +
257+
StandardCharsets.UTF_8, response.getHeader(HttpHeaders.CONTENT_TYPE));
258+
259+
response = new MockHttpServletResponse();
260+
this.view.setCharset(StandardCharsets.ISO_8859_1);
261+
this.view.render(model, request, response);
262+
assertEquals(MediaType.TEXT_PLAIN_VALUE + ";charset=" +
263+
StandardCharsets.ISO_8859_1, response.getHeader(HttpHeaders.CONTENT_TYPE));
264+
265+
}
266+
222267

223268
private interface InvocableScriptEngine extends ScriptEngine, Invocable {
224269
}

spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434

3535
<mvc:groovy-configurer resource-loader-path="/test" cache-templates="false" auto-indent="true" />
3636

37-
<mvc:script-template-configurer engine-name="nashorn" render-function="render" charset="ISO-8859-1"
37+
<mvc:script-template-configurer engine-name="nashorn" render-function="render"
38+
content-type="text/plain" charset="ISO-8859-1"
3839
resource-loader-path="classpath:" shared-engine="false">
3940
<mvc:script location="org/springframework/web/servlet/view/script/nashorn/render.js" />
4041
</mvc:script-template-configurer>

spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/empty.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)