From 04cff89eb700e82765615ff47b78aa47a9daf3fa Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Mon, 24 Aug 2015 14:27:17 +0200 Subject: [PATCH] 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 --- ...emplateConfigurerBeanDefinitionParser.java | 6 +- .../view/script/ScriptTemplateConfig.java | 6 ++ .../view/script/ScriptTemplateConfigurer.java | 20 +++++ .../view/script/ScriptTemplateView.java | 43 ++++++++++- .../web/servlet/config/spring-mvc-4.2.xsd | 7 ++ .../web/servlet/config/MvcNamespaceTests.java | 1 + .../view/script/ScriptTemplateViewTests.java | 77 +++++++++++++++---- .../config/mvc-config-view-resolution.xml | 3 +- .../web/servlet/view/script/empty.txt | 0 9 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/empty.txt diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ScriptTemplateConfigurerBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ScriptTemplateConfigurerBeanDefinitionParser.java index 3a726af77f4..13a71ac1e5d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ScriptTemplateConfigurerBeanDefinitionParser.java +++ b/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")) { builder.addPropertyValue("renderFunction", element.getAttribute("render-function")); } + if (element.hasAttribute("content-type")) { + builder.addPropertyValue("contentType", element.getAttribute("content-type")); + } if (element.hasAttribute("charset")) { builder.addPropertyValue("charset", Charset.forName(element.getAttribute("charset"))); } @@ -81,7 +84,8 @@ public class ScriptTemplateConfigurerBeanDefinitionParser extends AbstractSimple @Override protected boolean isEligibleAttribute(String name) { 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")); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java index b5d05abf93e..a11968bd1c2 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java @@ -60,6 +60,12 @@ public interface ScriptTemplateConfig { */ 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. */ diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java index e9714b48364..2a6a7fe349c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java +++ b/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 contentType; + private Charset charset; private String resourceLoaderPath; @@ -170,6 +172,24 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig { 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. * ({@code UTF-8} by default). diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java index efcf539343e..5712b3807cd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java +++ b/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 static final String DEFAULT_CONTENT_TYPE = "text/html"; + private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:"; @@ -89,6 +91,24 @@ public class ScriptTemplateView extends AbstractUrlBasedView { 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. */ @@ -132,6 +152,15 @@ public class ScriptTemplateView extends AbstractUrlBasedView { 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. */ @@ -167,6 +196,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView { if (this.renderFunction == null && viewConfig.getRenderFunction() != null) { this.renderFunction = viewConfig.getRenderFunction(); } + if (this.getContentType() == null) { + setContentType(viewConfig.getContentType() != null ? viewConfig.getContentType() : DEFAULT_CONTENT_TYPE); + } if (this.charset == null) { 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 - protected void renderMergedOutputModel( - Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) throws Exception { try { ScriptEngine engine = getEngine(); diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd index 4f85089263b..80226cdbd83 100644 --- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd +++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd @@ -1252,6 +1252,13 @@ ]]> + + + + + > results = new ArrayList<>(); for(int i = 0; i < iterations; i++) { @@ -160,7 +173,7 @@ public class ScriptTemplateViewTests { public void noRenderFunctionDefined() { this.view.setEngine(mock(InvocableScriptEngine.class)); try { - this.view.setApplicationContext(this.applicationContext); + this.view.setApplicationContext(this.wac); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException ex) { @@ -174,7 +187,7 @@ public class ScriptTemplateViewTests { this.view.setEngineName("test"); this.view.setRenderFunction("render"); try { - this.view.setApplicationContext(this.applicationContext); + this.view.setApplicationContext(this.wac); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException ex) { @@ -188,7 +201,7 @@ public class ScriptTemplateViewTests { this.view.setRenderFunction("render"); this.view.setSharedEngine(false); try { - this.view.setApplicationContext(this.applicationContext); + this.view.setApplicationContext(this.wac); fail("Should have thrown IllegalArgumentException"); } catch (IllegalArgumentException ex) { @@ -203,7 +216,7 @@ public class ScriptTemplateViewTests { this.view.setEngine(mock(InvocableScriptEngine.class)); this.view.setRenderFunction("render"); this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH); - this.view.setApplicationContext(this.applicationContext); + this.view.setApplicationContext(this.wac); ClassLoader classLoader = this.view.createClassLoader(); assertNotNull(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/")); } + @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 model = new HashMap(); + 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 { } diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml index 3488a15b486..2c384c8bb82 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml @@ -34,7 +34,8 @@ - diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/empty.txt b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/empty.txt new file mode 100644 index 00000000000..e69de29bb2d