Browse Source

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
pull/864/merge
Sebastien Deleuze 11 years ago
parent
commit
04cff89eb7
  1. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/ScriptTemplateConfigurerBeanDefinitionParser.java
  2. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java
  3. 20
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java
  4. 43
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
  5. 7
      spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd
  6. 1
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
  7. 77
      spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java
  8. 3
      spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml
  9. 0
      spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/empty.txt

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

@ -67,6 +67,9 @@ public class ScriptTemplateConfigurerBeanDefinitionParser extends AbstractSimple
if (element.hasAttribute("render-function")) { if (element.hasAttribute("render-function")) {
builder.addPropertyValue("renderFunction", element.getAttribute("render-function")); builder.addPropertyValue("renderFunction", element.getAttribute("render-function"));
} }
if (element.hasAttribute("content-type")) {
builder.addPropertyValue("contentType", element.getAttribute("content-type"));
}
if (element.hasAttribute("charset")) { if (element.hasAttribute("charset")) {
builder.addPropertyValue("charset", Charset.forName(element.getAttribute("charset"))); builder.addPropertyValue("charset", Charset.forName(element.getAttribute("charset")));
} }
@ -81,7 +84,8 @@ public class ScriptTemplateConfigurerBeanDefinitionParser extends AbstractSimple
@Override @Override
protected boolean isEligibleAttribute(String name) { protected boolean isEligibleAttribute(String name) {
return (name.equals("engine-name") || name.equals("scripts") || name.equals("render-object") || return (name.equals("engine-name") || name.equals("scripts") || name.equals("render-object") ||
name.equals("render-function") || name.equals("charset") || name.equals("resource-loader-path")); name.equals("render-function") || name.equals("content-type") ||
name.equals("charset") || name.equals("resource-loader-path"));
} }
} }

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

@ -60,6 +60,12 @@ public interface ScriptTemplateConfig {
*/ */
String getRenderFunction(); String getRenderFunction();
/**
* Return the content type to use for the response.
* @since 4.2.1
*/
String getContentType();
/** /**
* Return the charset used to read script and template files. * Return the charset used to read script and template files.
*/ */

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

@ -59,6 +59,8 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
private String renderFunction; private String renderFunction;
private String contentType;
private Charset charset; private Charset charset;
private String resourceLoaderPath; private String resourceLoaderPath;
@ -170,6 +172,24 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
return this.renderFunction; return this.renderFunction;
} }
/**
* Set the content type to use for the response.
* ({@code text/html} by default).
* @since 4.2.1
*/
public void setContentType(String contentType) {
this.contentType = contentType;
}
/**
* Return the content type to use for the response.
* @since 4.2.1
*/
@Override
public String getContentType() {
return this.contentType;
}
/** /**
* Set the charset used to read script and template files. * Set the charset used to read script and template files.
* ({@code UTF-8} by default). * ({@code UTF-8} by default).

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

@ -63,6 +63,8 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
*/ */
public class ScriptTemplateView extends AbstractUrlBasedView { public class ScriptTemplateView extends AbstractUrlBasedView {
public static final String DEFAULT_CONTENT_TYPE = "text/html";
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:"; private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:";
@ -89,6 +91,24 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
private String resourceLoaderPath; private String resourceLoaderPath;
/**
* Constructor for use as a bean.
* @see #setUrl
*/
public ScriptTemplateView() {
setContentType(null);
}
/**
* Create a new ScriptTemplateView with the given URL.
* @since 4.2.1
*/
public ScriptTemplateView(String url) {
super(url);
setContentType(null);
}
/** /**
* See {@link ScriptTemplateConfigurer#setEngine(ScriptEngine)} documentation. * See {@link ScriptTemplateConfigurer#setEngine(ScriptEngine)} documentation.
*/ */
@ -132,6 +152,15 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.renderFunction = functionName; this.renderFunction = functionName;
} }
/**
* See {@link ScriptTemplateConfigurer#setContentType(String)}} documentation.
* @since 4.2.1
*/
@Override
public void setContentType(String contentType) {
super.setContentType(contentType);
}
/** /**
* See {@link ScriptTemplateConfigurer#setCharset(Charset)} documentation. * See {@link ScriptTemplateConfigurer#setCharset(Charset)} documentation.
*/ */
@ -167,6 +196,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
if (this.renderFunction == null && viewConfig.getRenderFunction() != null) { if (this.renderFunction == null && viewConfig.getRenderFunction() != null) {
this.renderFunction = viewConfig.getRenderFunction(); this.renderFunction = viewConfig.getRenderFunction();
} }
if (this.getContentType() == null) {
setContentType(viewConfig.getContentType() != null ? viewConfig.getContentType() : DEFAULT_CONTENT_TYPE);
}
if (this.charset == null) { if (this.charset == null) {
this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET); this.charset = (viewConfig.getCharset() != null ? viewConfig.getCharset() : DEFAULT_CHARSET);
} }
@ -276,10 +308,17 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
} }
} }
@Override
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
super.prepareResponse(request, response);
setResponseContentType(request, response);
response.setCharacterEncoding(this.charset.name());
}
@Override @Override
protected void renderMergedOutputModel( protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletResponse response) throws Exception {
try { try {
ScriptEngine engine = getEngine(); ScriptEngine engine = getEngine();

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

@ -1252,6 +1252,13 @@
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="content-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Set the content type to use for the response (text/html by default).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string"> <xsd:attribute name="charset" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[

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

@ -799,6 +799,7 @@ public class MvcNamespaceTests {
ScriptTemplateConfigurer scriptTemplateConfigurer = appContext.getBean(ScriptTemplateConfigurer.class); ScriptTemplateConfigurer scriptTemplateConfigurer = appContext.getBean(ScriptTemplateConfigurer.class);
assertNotNull(scriptTemplateConfigurer); assertNotNull(scriptTemplateConfigurer);
assertEquals("render", scriptTemplateConfigurer.getRenderFunction()); assertEquals("render", scriptTemplateConfigurer.getRenderFunction());
assertEquals(MediaType.TEXT_PLAIN_VALUE, scriptTemplateConfigurer.getContentType());
assertEquals(StandardCharsets.ISO_8859_1, scriptTemplateConfigurer.getCharset()); assertEquals(StandardCharsets.ISO_8859_1, scriptTemplateConfigurer.getCharset());
assertEquals("classpath:", scriptTemplateConfigurer.getResourceLoaderPath()); assertEquals("classpath:", scriptTemplateConfigurer.getResourceLoaderPath());
assertFalse(scriptTemplateConfigurer.isSharedEngine()); assertFalse(scriptTemplateConfigurer.isSharedEngine());

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

@ -20,7 +20,9 @@ import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -34,6 +36,13 @@ import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextException;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -51,7 +60,7 @@ public class ScriptTemplateViewTests {
private ScriptTemplateConfigurer configurer; private ScriptTemplateConfigurer configurer;
private StaticApplicationContext applicationContext; private StaticWebApplicationContext wac;
private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/"; private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
@ -59,10 +68,10 @@ public class ScriptTemplateViewTests {
@Before @Before
public void setup() { public void setup() {
this.configurer = new ScriptTemplateConfigurer(); this.configurer = new ScriptTemplateConfigurer();
this.applicationContext = new StaticApplicationContext(); this.wac = new StaticWebApplicationContext();
this.applicationContext.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer); this.wac.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
this.view = new ScriptTemplateView(); this.view = new ScriptTemplateView();
this.view.setUrl("sampleView"); this.view.setUrl(RESOURCE_LOADER_PATH + "empty.txt");
} }
@Test @Test
@ -83,13 +92,18 @@ public class ScriptTemplateViewTests {
this.configurer.setEngine(engine); this.configurer.setEngine(engine);
this.configurer.setRenderObject("Template"); this.configurer.setRenderObject("Template");
this.configurer.setRenderFunction("render"); this.configurer.setRenderFunction("render");
this.configurer.setContentType(MediaType.TEXT_PLAIN_VALUE);
this.configurer.setCharset(StandardCharsets.ISO_8859_1); this.configurer.setCharset(StandardCharsets.ISO_8859_1);
this.configurer.setSharedEngine(true);
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
assertEquals(engine, accessor.getPropertyValue("engine")); assertEquals(engine, accessor.getPropertyValue("engine"));
assertEquals("Template", accessor.getPropertyValue("renderObject")); assertEquals("Template", accessor.getPropertyValue("renderObject"));
assertEquals("render", accessor.getPropertyValue("renderFunction")); assertEquals("render", accessor.getPropertyValue("renderFunction"));
assertEquals(MediaType.TEXT_PLAIN_VALUE, accessor.getPropertyValue("contentType"));
assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset")); assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
assertEquals(true, accessor.getPropertyValue("sharedEngine"));
} }
@Test @Test
@ -97,16 +111,15 @@ public class ScriptTemplateViewTests {
this.configurer.setEngineName("nashorn"); this.configurer.setEngineName("nashorn");
this.configurer.setRenderObject("Template"); this.configurer.setRenderObject("Template");
this.configurer.setRenderFunction("render"); this.configurer.setRenderFunction("render");
this.configurer.setCharset(StandardCharsets.ISO_8859_1);
this.configurer.setSharedEngine(true);
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
assertEquals("nashorn", accessor.getPropertyValue("engineName")); assertEquals("nashorn", accessor.getPropertyValue("engineName"));
assertNotNull(accessor.getPropertyValue("engine")); assertNotNull(accessor.getPropertyValue("engine"));
assertEquals("Template", accessor.getPropertyValue("renderObject")); assertEquals("Template", accessor.getPropertyValue("renderObject"));
assertEquals("render", accessor.getPropertyValue("renderFunction")); assertEquals("render", accessor.getPropertyValue("renderFunction"));
assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset")); assertEquals(MediaType.TEXT_HTML_VALUE, accessor.getPropertyValue("contentType"));
assertEquals(true, accessor.getPropertyValue("sharedEngine")); assertEquals(StandardCharsets.UTF_8, accessor.getPropertyValue("charset"));
} }
@Test @Test
@ -115,7 +128,7 @@ public class ScriptTemplateViewTests {
given(engine.get("key")).willReturn("value"); given(engine.get("key")).willReturn("value");
this.view.setEngine(engine); this.view.setEngine(engine);
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
engine = this.view.getEngine(); engine = this.view.getEngine();
assertNotNull(engine); assertNotNull(engine);
assertEquals("value", engine.get("key")); assertEquals("value", engine.get("key"));
@ -131,7 +144,7 @@ public class ScriptTemplateViewTests {
this.view.setEngineName("nashorn"); this.view.setEngineName("nashorn");
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
this.view.setSharedEngine(false); this.view.setSharedEngine(false);
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
ExecutorService executor = Executors.newFixedThreadPool(4); ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Boolean>> results = new ArrayList<>(); List<Future<Boolean>> results = new ArrayList<>();
for(int i = 0; i < iterations; i++) { for(int i = 0; i < iterations; i++) {
@ -160,7 +173,7 @@ public class ScriptTemplateViewTests {
public void noRenderFunctionDefined() { public void noRenderFunctionDefined() {
this.view.setEngine(mock(InvocableScriptEngine.class)); this.view.setEngine(mock(InvocableScriptEngine.class));
try { try {
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
fail("Should have thrown IllegalArgumentException"); fail("Should have thrown IllegalArgumentException");
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
@ -174,7 +187,7 @@ public class ScriptTemplateViewTests {
this.view.setEngineName("test"); this.view.setEngineName("test");
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
try { try {
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
fail("Should have thrown IllegalArgumentException"); fail("Should have thrown IllegalArgumentException");
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
@ -188,7 +201,7 @@ public class ScriptTemplateViewTests {
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
this.view.setSharedEngine(false); this.view.setSharedEngine(false);
try { try {
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
fail("Should have thrown IllegalArgumentException"); fail("Should have thrown IllegalArgumentException");
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
@ -203,7 +216,7 @@ public class ScriptTemplateViewTests {
this.view.setEngine(mock(InvocableScriptEngine.class)); this.view.setEngine(mock(InvocableScriptEngine.class));
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH); this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
this.view.setApplicationContext(this.applicationContext); this.view.setApplicationContext(this.wac);
ClassLoader classLoader = this.view.createClassLoader(); ClassLoader classLoader = this.view.createClassLoader();
assertNotNull(classLoader); assertNotNull(classLoader);
URLClassLoader urlClassLoader = (URLClassLoader) classLoader; URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
@ -219,6 +232,38 @@ public class ScriptTemplateViewTests {
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), Matchers.endsWith("org/springframework/web/servlet/view/")); assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), Matchers.endsWith("org/springframework/web/servlet/view/"));
} }
@Test // SPR-13379
public void contentType() throws Exception {
MockServletContext servletContext = new MockServletContext();
this.wac.setServletContext(servletContext);
this.wac.refresh();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.wac);
MockHttpServletResponse response = new MockHttpServletResponse();
Map<String, Object> model = new HashMap<String, Object>();
this.view.setEngine(mock(InvocableScriptEngine.class));
this.view.setRenderFunction("render");
this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
this.view.setApplicationContext(this.wac);
this.view.render(model, request, response);
assertEquals(MediaType.TEXT_HTML_VALUE + ";charset=" +
StandardCharsets.UTF_8, response.getHeader(HttpHeaders.CONTENT_TYPE));
response = new MockHttpServletResponse();
this.view.setContentType(MediaType.TEXT_PLAIN_VALUE);
this.view.render(model, request, response);
assertEquals(MediaType.TEXT_PLAIN_VALUE + ";charset=" +
StandardCharsets.UTF_8, response.getHeader(HttpHeaders.CONTENT_TYPE));
response = new MockHttpServletResponse();
this.view.setCharset(StandardCharsets.ISO_8859_1);
this.view.render(model, request, response);
assertEquals(MediaType.TEXT_PLAIN_VALUE + ";charset=" +
StandardCharsets.ISO_8859_1, response.getHeader(HttpHeaders.CONTENT_TYPE));
}
private interface InvocableScriptEngine extends ScriptEngine, Invocable { private interface InvocableScriptEngine extends ScriptEngine, Invocable {
} }

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

@ -34,7 +34,8 @@
<mvc:groovy-configurer resource-loader-path="/test" cache-templates="false" auto-indent="true" /> <mvc:groovy-configurer resource-loader-path="/test" cache-templates="false" auto-indent="true" />
<mvc:script-template-configurer engine-name="nashorn" render-function="render" charset="ISO-8859-1" <mvc:script-template-configurer engine-name="nashorn" render-function="render"
content-type="text/plain" charset="ISO-8859-1"
resource-loader-path="classpath:" shared-engine="false"> resource-loader-path="classpath:" shared-engine="false">
<mvc:script location="org/springframework/web/servlet/view/script/nashorn/render.js" /> <mvc:script location="org/springframework/web/servlet/view/script/nashorn/render.js" />
</mvc:script-template-configurer> </mvc:script-template-configurer>

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

Loading…
Cancel
Save