diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java index fb223a06c0b..55a12ac406a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java @@ -171,14 +171,13 @@ public class ScriptTemplateView extends AbstractUrlBasedView { */ public void setResourceLoaderPath(String resourceLoaderPath) { String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath); - this.resourceLoaderPaths = new String[paths.length + 1]; - this.resourceLoaderPaths[0] = ""; + this.resourceLoaderPaths = new String[paths.length]; for (int i = 0; i < paths.length; i++) { String path = paths[i]; if (!path.endsWith("/") && !path.endsWith(":")) { path = path + "/"; } - this.resourceLoaderPaths[i + 1] = path; + this.resourceLoaderPaths[i] = path; } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java new file mode 100644 index 00000000000..3e5356435fa --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java @@ -0,0 +1,265 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.reactive.result.view.script; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import javax.script.Invocable; +import javax.script.ScriptEngine; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.support.StaticApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link ScriptTemplateView}. + * + * @author Sebastien Deleuze + */ +public class ScriptTemplateViewTests { + + private ScriptTemplateView view; + + private ScriptTemplateConfigurer configurer; + + private StaticApplicationContext context; + + + @BeforeEach + public void setup() { + this.configurer = new ScriptTemplateConfigurer(); + this.context = new StaticApplicationContext(); + this.context.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer); + this.view = new ScriptTemplateView(); + } + + + @Test + public void missingTemplate() throws Exception { + this.context.refresh(); + this.view.setResourceLoaderPath("classpath:org/springframework/web/reactive/result/view/script/"); + this.view.setUrl("missing.txt"); + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.configurer.setRenderFunction("render"); + this.view.setApplicationContext(this.context); + assertThat(this.view.checkResourceExists(Locale.ENGLISH)).isFalse(); + } + + @Test + public void missingScriptTemplateConfig() throws Exception { + assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> + this.view.setApplicationContext(new StaticApplicationContext())) + .withMessageContaining("ScriptTemplateConfig"); + } + + @Test + public void detectScriptTemplateConfigWithEngine() { + InvocableScriptEngine engine = mock(InvocableScriptEngine.class); + this.configurer.setEngine(engine); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + this.configurer.setCharset(StandardCharsets.ISO_8859_1); + this.configurer.setSharedEngine(true); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.context); + assertThat(accessor.getPropertyValue("engine")).isEqualTo(engine); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("defaultCharset")).isEqualTo(StandardCharsets.ISO_8859_1); + assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isTrue(); + } + + @Test + public void detectScriptTemplateConfigWithEngineName() { + this.configurer.setEngineName("jython"); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.context); + assertThat(accessor.getPropertyValue("engineName")).isEqualTo("jython"); + assertThat(accessor.getPropertyValue("engine")).isNotNull(); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("defaultCharset")).isEqualTo(StandardCharsets.UTF_8); + } + + @Test + public void customEngineAndRenderFunction() throws Exception { + ScriptEngine engine = mock(InvocableScriptEngine.class); + given(engine.get("key")).willReturn("value"); + this.view.setEngine(engine); + this.view.setRenderFunction("render"); + this.view.setApplicationContext(this.context); + engine = this.view.getEngine(); + assertThat(engine).isNotNull(); + assertThat(engine.get("key")).isEqualTo("value"); + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + assertThat(accessor.getPropertyValue("renderObject")).isNull(); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("defaultCharset")).isEqualTo(StandardCharsets.UTF_8); + } + + @Test + public void nonSharedEngine() throws Exception { + int iterations = 20; + this.view.setEngineName("jython"); + this.view.setRenderFunction("render"); + this.view.setSharedEngine(false); + this.view.setApplicationContext(this.context); + ExecutorService executor = Executors.newFixedThreadPool(4); + List> results = new ArrayList<>(); + for (int i = 0; i < iterations; i++) { + results.add(executor.submit(() -> view.getEngine() != null)); + } + assertThat(results.size()).isEqualTo(iterations); + for (int i = 0; i < iterations; i++) { + assertThat((boolean) results.get(i).get()).isTrue(); + } + executor.shutdown(); + } + + @Test + public void nonInvocableScriptEngine() throws Exception { + this.view.setEngine(mock(ScriptEngine.class)); + this.view.setApplicationContext(this.context); + } + + @Test + public void nonInvocableScriptEngineWithRenderFunction() throws Exception { + this.view.setEngine(mock(ScriptEngine.class)); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.context)); + } + + @Test + public void engineAndEngineNameBothDefined() { + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.view.setEngineName("test"); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.context)) + .withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'."); + } + + @Test // gh-23258 + public void engineAndEngineSupplierBothDefined() { + ScriptEngine engine = mock(InvocableScriptEngine.class); + this.view.setEngineSupplier(() -> engine); + this.view.setEngine(engine); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.context)) + .withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'."); + } + + @Test // gh-23258 + public void engineNameAndEngineSupplierBothDefined() { + this.view.setEngineSupplier(() -> mock(InvocableScriptEngine.class)); + this.view.setEngineName("test"); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.context)) + .withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'."); + } + + @Test + public void engineSetterAndNonSharedEngine() { + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.view.setRenderFunction("render"); + this.view.setSharedEngine(false); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.context)) + .withMessageContaining("sharedEngine"); + } + + @Test + public void resourceLoaderPath() { + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.view.setApplicationContext(this.context); + DirectFieldAccessor viewAccessor = new DirectFieldAccessor(this.view); + String[] resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths"); + assertThat(resourceLoaderPaths).containsExactly("classpath:"); + + this.view.setResourceLoaderPath("classpath:org/springframework/web/reactive/result/view/script/"); + resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths"); + assertThat(resourceLoaderPaths).containsExactly("classpath:org/springframework/web/reactive/result/view/script/"); + + this.view.setResourceLoaderPath("classpath:org/springframework/web/reactive/result/view/script"); + resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths"); + assertThat(resourceLoaderPaths).containsExactly("classpath:org/springframework/web/reactive/result/view/script/"); + } + + @Test // gh-23258 + public void engineSupplierWithSharedEngine() { + this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class)); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + this.configurer.setSharedEngine(true); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.context); + ScriptEngine engine1 = this.view.getEngine(); + ScriptEngine engine2 = this.view.getEngine(); + assertThat(engine1).isNotNull(); + assertThat(engine2).isNotNull(); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isTrue(); + } + + @SuppressWarnings("unchecked") + @Test // gh-23258 + public void engineSupplierWithNonSharedEngine() { + this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class)); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + this.configurer.setSharedEngine(false); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.context); + ScriptEngine engine1 = this.view.getEngine(); + ScriptEngine engine2 = this.view.getEngine(); + assertThat(engine1).isNotNull(); + assertThat(engine2).isNotNull(); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isFalse(); + } + + private interface InvocableScriptEngine extends ScriptEngine, Invocable { + } + +} 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 88ce20a1ddc..6cf7479e6e5 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 @@ -199,14 +199,13 @@ public class ScriptTemplateView extends AbstractUrlBasedView { */ public void setResourceLoaderPath(String resourceLoaderPath) { String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath); - this.resourceLoaderPaths = new String[paths.length + 1]; - this.resourceLoaderPaths[0] = ""; + this.resourceLoaderPaths = new String[paths.length]; for (int i = 0; i < paths.length; i++) { String path = paths[i]; if (!path.endsWith("/") && !path.endsWith(":")) { path = path + "/"; } - this.resourceLoaderPaths[i + 1] = path; + this.resourceLoaderPaths[i] = path; } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java new file mode 100644 index 00000000000..28e020151cd --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java @@ -0,0 +1,333 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.view.script; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import javax.script.Invocable; +import javax.script.ScriptEngine; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; +import org.springframework.web.testfixture.servlet.MockHttpServletResponse; +import org.springframework.web.testfixture.servlet.MockServletContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link ScriptTemplateView}. + * + * @author Sebastien Deleuze + */ +public class ScriptTemplateViewTests { + + private ScriptTemplateView view; + + private ScriptTemplateConfigurer configurer; + + private StaticWebApplicationContext wac; + + + @BeforeEach + public void setup() { + this.configurer = new ScriptTemplateConfigurer(); + this.wac = new StaticWebApplicationContext(); + this.wac.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer); + this.view = new ScriptTemplateView(); + } + + + @Test + public void missingTemplate() throws Exception { + MockServletContext servletContext = new MockServletContext(); + this.wac.setServletContext(servletContext); + this.wac.refresh(); + this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script/"); + this.view.setUrl("missing.txt"); + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.configurer.setRenderFunction("render"); + this.view.setApplicationContext(this.wac); + assertThat(this.view.checkResource(Locale.ENGLISH)).isFalse(); + } + + @Test + public void missingScriptTemplateConfig() { + assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> + this.view.setApplicationContext(new StaticApplicationContext())) + .withMessageContaining("ScriptTemplateConfig"); + } + + @Test + public void detectScriptTemplateConfigWithEngine() { + InvocableScriptEngine engine = mock(InvocableScriptEngine.class); + this.configurer.setEngine(engine); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + this.configurer.setContentType(MediaType.TEXT_PLAIN_VALUE); + this.configurer.setCharset(StandardCharsets.ISO_8859_1); + this.configurer.setSharedEngine(true); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.wac); + assertThat(accessor.getPropertyValue("engine")).isEqualTo(engine); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("contentType")).isEqualTo(MediaType.TEXT_PLAIN_VALUE); + assertThat(accessor.getPropertyValue("charset")).isEqualTo(StandardCharsets.ISO_8859_1); + assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isTrue(); + } + + @Test + public void detectScriptTemplateConfigWithEngineName() { + this.configurer.setEngineName("jython"); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.wac); + assertThat(accessor.getPropertyValue("engineName")).isEqualTo("jython"); + assertThat(accessor.getPropertyValue("engine")).isNotNull(); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("contentType")).isEqualTo(MediaType.TEXT_HTML_VALUE); + assertThat(accessor.getPropertyValue("charset")).isEqualTo(StandardCharsets.UTF_8); + } + + @Test + public void customEngineAndRenderFunction() { + ScriptEngine engine = mock(InvocableScriptEngine.class); + given(engine.get("key")).willReturn("value"); + this.view.setEngine(engine); + this.view.setRenderFunction("render"); + this.view.setApplicationContext(this.wac); + engine = this.view.getEngine(); + assertThat(engine).isNotNull(); + assertThat(engine.get("key")).isEqualTo("value"); + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + assertThat(accessor.getPropertyValue("renderObject")).isNull(); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("charset")).isEqualTo(StandardCharsets.UTF_8); + } + + @Test + public void nonSharedEngine() throws Exception { + int iterations = 20; + this.view.setEngineName("jython"); + this.view.setRenderFunction("render"); + this.view.setSharedEngine(false); + this.view.setApplicationContext(this.wac); + ExecutorService executor = Executors.newFixedThreadPool(4); + List> results = new ArrayList<>(); + for (int i = 0; i < iterations; i++) { + results.add(executor.submit(() -> view.getEngine() != null)); + } + assertThat(results.size()).isEqualTo(iterations); + for (int i = 0; i < iterations; i++) { + assertThat((boolean) results.get(i).get()).isTrue(); + } + executor.shutdown(); + } + + @Test + public void nonInvocableScriptEngine() { + this.view.setEngine(mock(ScriptEngine.class)); + this.view.setApplicationContext(this.wac); + } + + @Test + public void nonInvocableScriptEngineWithRenderFunction() { + this.view.setEngine(mock(ScriptEngine.class)); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.wac)); + } + + @Test + public void engineAndEngineNameBothDefined() { + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.view.setEngineName("test"); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.wac)) + .withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'."); + } + + @Test // gh-23258 + public void engineAndEngineSupplierBothDefined() { + ScriptEngine engine = mock(InvocableScriptEngine.class); + this.view.setEngineSupplier(() -> engine); + this.view.setEngine(engine); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.wac)) + .withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'."); + } + + @Test // gh-23258 + public void engineNameAndEngineSupplierBothDefined() { + this.view.setEngineSupplier(() -> mock(InvocableScriptEngine.class)); + this.view.setEngineName("test"); + this.view.setRenderFunction("render"); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.wac)) + .withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'."); + } + + @Test + public void engineSetterAndNonSharedEngine() { + this.view.setEngine(mock(InvocableScriptEngine.class)); + this.view.setRenderFunction("render"); + this.view.setSharedEngine(false); + assertThatIllegalArgumentException().isThrownBy(() -> + this.view.setApplicationContext(this.wac)) + .withMessageContaining("sharedEngine"); + } + + @Test // SPR-14210 + public void resourceLoaderPath() 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<>(); + InvocableScriptEngine engine = mock(InvocableScriptEngine.class); + given(engine.invokeFunction(any(), any(), any(), any())).willReturn("foo"); + this.view.setEngine(engine); + this.view.setRenderFunction("render"); + this.view.setApplicationContext(this.wac); + this.view.setUrl("org/springframework/web/servlet/view/script/empty.txt"); + this.view.render(model, request, response); + assertThat(response.getContentAsString()).isEqualTo("foo"); + DirectFieldAccessor viewAccessor = new DirectFieldAccessor(this.view); + String[] resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths"); + assertThat(resourceLoaderPaths).containsExactly("classpath:"); + + response = new MockHttpServletResponse(); + this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script/"); + this.view.setUrl("empty.txt"); + this.view.render(model, request, response); + assertThat(response.getContentAsString()).isEqualTo("foo"); + resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths"); + assertThat(resourceLoaderPaths).containsExactly("classpath:org/springframework/web/servlet/view/script/"); + + response = new MockHttpServletResponse(); + this.view.setResourceLoaderPath("classpath:org/springframework/web/servlet/view/script"); + this.view.setUrl("empty.txt"); + this.view.render(model, request, response); + assertThat(response.getContentAsString()).isEqualTo("foo"); + resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths"); + assertThat(resourceLoaderPaths).containsExactly("classpath:org/springframework/web/servlet/view/script/"); + } + + @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("classpath:org/springframework/web/servlet/view/script/"); + this.view.setUrl("empty.txt"); + this.view.setApplicationContext(this.wac); + + this.view.render(model, request, response); + assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo((MediaType.TEXT_HTML_VALUE + ";charset=" + + StandardCharsets.UTF_8)); + + response = new MockHttpServletResponse(); + this.view.setContentType(MediaType.TEXT_PLAIN_VALUE); + this.view.render(model, request, response); + assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo((MediaType.TEXT_PLAIN_VALUE + ";charset=" + + StandardCharsets.UTF_8)); + + response = new MockHttpServletResponse(); + this.view.setCharset(StandardCharsets.ISO_8859_1); + this.view.render(model, request, response); + assertThat(response.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo((MediaType.TEXT_PLAIN_VALUE + ";charset=" + + StandardCharsets.ISO_8859_1)); + + } + + @Test // gh-23258 + public void engineSupplierWithSharedEngine() { + this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class)); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + this.configurer.setSharedEngine(true); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.wac); + ScriptEngine engine1 = this.view.getEngine(); + ScriptEngine engine2 = this.view.getEngine(); + assertThat(engine1).isNotNull(); + assertThat(engine2).isNotNull(); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isTrue(); + } + + @Test // gh-23258 + public void engineSupplierWithNonSharedEngine() { + this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class)); + this.configurer.setRenderObject("Template"); + this.configurer.setRenderFunction("render"); + this.configurer.setSharedEngine(false); + + DirectFieldAccessor accessor = new DirectFieldAccessor(this.view); + this.view.setApplicationContext(this.wac); + ScriptEngine engine1 = this.view.getEngine(); + ScriptEngine engine2 = this.view.getEngine(); + assertThat(engine1).isNotNull(); + assertThat(engine2).isNotNull(); + assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template"); + assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render"); + assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isFalse(); + } + + private interface InvocableScriptEngine extends ScriptEngine, Invocable { + } + +}