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 eb640e2aa0..3a726af77f 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
@@ -73,6 +73,9 @@ public class ScriptTemplateConfigurerBeanDefinitionParser extends AbstractSimple
if (element.hasAttribute("resource-loader-path")) {
builder.addPropertyValue("resourceLoaderPath", element.getAttribute("resource-loader-path"));
}
+ if (element.hasAttribute("shared-engine")) {
+ builder.addPropertyValue("sharedEngine", element.getAttribute("shared-engine"));
+ }
}
@Override
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 4919492b15..da3d7c8e63 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
@@ -19,8 +19,6 @@ package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset;
import javax.script.ScriptEngine;
-import org.springframework.core.io.ResourceLoader;
-
/**
* Interface to be implemented by objects that configure and manage a
* {@link ScriptEngine} for automatic lookup in a web environment.
@@ -33,12 +31,18 @@ public interface ScriptTemplateConfig {
ScriptEngine getEngine();
+ String getEngineName();
+
+ String[] getScripts();
+
String getRenderObject();
String getRenderFunction();
Charset getCharset();
- ResourceLoader getResourceLoader();
+ String getResourceLoaderPath();
+
+ Boolean isShareEngine();
}
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 5e3c1b755a..374d95f76d 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
@@ -16,28 +16,9 @@
package org.springframework.web.servlet.view.script;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.List;
-import javax.script.Invocable;
import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.core.io.DefaultResourceLoader;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.ResourceLoader;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
/**
* An implementation of Spring MVC's {@link ScriptTemplateConfig} for creating
@@ -59,37 +40,44 @@ import org.springframework.util.StringUtils;
* }
*
*
+ *
It is possible to use non thread-safe script engines and templating libraries, like
+ * Handlebars or React running on Nashorn, by setting the
+ * {@link #setSharedEngine(Boolean) sharedEngine} property to {@code false}.
+ *
* @author Sebastien Deleuze
* @since 4.2
* @see ScriptTemplateView
*/
-public class ScriptTemplateConfigurer implements ScriptTemplateConfig, ApplicationContextAware, InitializingBean {
+public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
private ScriptEngine engine;
private String engineName;
- private ApplicationContext applicationContext;
-
private String[] scripts;
private String renderObject;
private String renderFunction;
- private Charset charset = Charset.forName("UTF-8");
+ private Charset charset;
- private ResourceLoader resourceLoader;
+ private String resourceLoaderPath;
- private String resourceLoaderPath = "classpath:";
+ private Boolean sharedEngine;
/**
* Set the {@link ScriptEngine} to use by the view.
* The script engine must implement {@code Invocable}.
* You must define {@code engine} or {@code engineName}, not both.
+ *
+ *
When the {@code sharedEngine} flag is set to {@code false}, you should not specify
+ * the script engine with this setter, but with the {@link #setEngineName(String)}
+ * one (since it implies multiple lazy instanciations of the script engine).
+ *
+ * @see #setEngineName(String)
*/
public void setEngine(ScriptEngine engine) {
- Assert.isInstanceOf(Invocable.class, engine);
this.engine = engine;
}
@@ -102,18 +90,15 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig, Applicati
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
* The script engine must implement {@code Invocable}.
* You must define {@code engine} or {@code engineName}, not both.
+ * @see #setEngine(ScriptEngine)
*/
public void setEngineName(String engineName) {
this.engineName = engineName;
}
@Override
- public void setApplicationContext(ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
- }
-
- protected ApplicationContext getApplicationContext() {
- return this.applicationContext;
+ public String getEngineName() {
+ return this.engineName;
}
/**
@@ -134,8 +119,8 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig, Applicati
}
@Override
- public String getRenderObject() {
- return renderObject;
+ public String[] getScripts() {
+ return this.scripts;
}
/**
@@ -148,8 +133,8 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig, Applicati
}
@Override
- public String getRenderFunction() {
- return renderFunction;
+ public String getRenderObject() {
+ return this.renderObject;
}
/**
@@ -164,6 +149,11 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig, Applicati
this.renderFunction = renderFunction;
}
+ @Override
+ public String getRenderFunction() {
+ return this.renderFunction;
+ }
+
/**
* Set the charset used to read script and template files.
* ({@code UTF-8} by default).
@@ -189,69 +179,30 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig, Applicati
this.resourceLoaderPath = resourceLoaderPath;
}
+ @Override
public String getResourceLoaderPath() {
- return resourceLoaderPath;
+ return this.resourceLoaderPath;
+ }
+
+ /**
+ * When set to {@code false}, use thread-local {@link ScriptEngine} instances instead
+ * of one single shared instance. This flag should be set to {@code false} for those
+ * using non thread-safe script engines and templating libraries, like Handlebars or
+ * React running on Nashorn for example.
+ *
+ *
When this flag is set to {@code false}, the script engine must be specified using
+ * {@link #setEngineName(String)}. Using {@link #setEngine(ScriptEngine)} is not
+ * possible because multiple instances of the script engine need to be created lazily
+ * (one per thread).
+ * @see THREADING ScriptEngine parameter
+ */
+ public void setSharedEngine(Boolean sharedEngine) {
+ this.sharedEngine = sharedEngine;
}
@Override
- public ResourceLoader getResourceLoader() {
- return resourceLoader;
- }
-
- @Override
- public void afterPropertiesSet() throws Exception {
- if (this.engine == null) {
- this.engine = createScriptEngine();
- }
- Assert.state(this.renderFunction != null, "renderFunction property must be defined.");
- this.resourceLoader = new DefaultResourceLoader(createClassLoader());
- if (this.scripts != null) {
- try {
- for (String script : this.scripts) {
- this.engine.eval(read(script));
- }
- }
- catch (ScriptException e) {
- throw new IllegalStateException("could not load script", e);
- }
- }
- }
-
- protected ClassLoader createClassLoader() throws IOException {
- String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
- List urls = new ArrayList();
- for (String path : paths) {
- Resource[] resources = getApplicationContext().getResources(path);
- if (resources.length > 0) {
- for (Resource resource : resources) {
- if (resource.exists()) {
- urls.add(resource.getURL());
- }
- }
- }
- }
- ClassLoader classLoader = getApplicationContext().getClassLoader();
- return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
- }
-
- private Reader read(String path) throws IOException {
- Resource resource = this.resourceLoader.getResource(path);
- Assert.state(resource.exists(), "Resource " + path + " not found.");
- return new InputStreamReader(resource.getInputStream());
- }
-
- protected ScriptEngine createScriptEngine() throws IOException {
- if (this.engine != null && this.engineName != null) {
- throw new IllegalStateException("You should define engine or engineName properties, not both.");
- }
- if (this.engineName != null) {
- ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(this.engineName);
- Assert.state(scriptEngine != null, "No engine \"" + this.engineName + "\" found.");
- Assert.state(scriptEngine instanceof Invocable, "Script engine should be instance of Invocable");
- this.engine = scriptEngine;
- }
- Assert.state(this.engine != null, "No script engine found, please specify valid engine or engineName properties.");
- return this.engine;
+ public Boolean isShareEngine() {
+ return this.sharedEngine;
}
}
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 1d306e9f2e..0f9e7b9b1f 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
@@ -17,10 +17,17 @@
package org.springframework.web.servlet.view.script;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLClassLoader;
import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import javax.script.Invocable;
import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -29,17 +36,26 @@ import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
+import org.springframework.core.NamedThreadLocal;
+import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
+import org.springframework.util.StringUtils;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
/**
* An {@link org.springframework.web.servlet.view.AbstractUrlBasedView AbstractUrlBasedView}
* designed to run any template library based on a JSR-223 script engine.
*
- * Nashorn Javascript engine requires Java 8+.
+ *
If not set, each property is auto-detected by looking up up a single
+ * {@link ScriptTemplateConfig} bean in the web application context and using
+ * it to obtain the configured properties.
+ *
+ *
Nashorn Javascript engine requires Java 8+, and may require setting the
+ * {@code sharedEngine} property to {@code false} in order to run properly. See
+ * {@link ScriptTemplateConfigurer#setSharedEngine(Boolean)} for more details.
*
* @author Sebastien Deleuze
* @since 4.2
@@ -48,8 +64,20 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
*/
public class ScriptTemplateView extends AbstractUrlBasedView {
+ private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+ private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:";
+
+
private ScriptEngine engine;
+ private final ThreadLocal engineHolder =
+ new NamedThreadLocal("ScriptTemplateView engine");
+
+ private String engineName;
+
+ private String[] scripts;
+
private String renderObject;
private String renderFunction;
@@ -58,58 +86,182 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
private ResourceLoader resourceLoader;
+ private String resourceLoaderPath;
+
+ private Boolean sharedEngine;
+
+
/**
- * Set the {@link ScriptEngine} to use in this view.
- * If not set, the engine is auto-detected by looking up up a single
- * {@link ScriptTemplateConfig} bean in the web application context and using
- * it to obtain the configured {@code ScriptEngine} instance.
- * @see ScriptTemplateConfig
+ * See {@link ScriptTemplateConfigurer#setEngine(ScriptEngine)} documentation.
*/
public void setEngine(ScriptEngine engine) {
+ Assert.isInstanceOf(Invocable.class, engine);
this.engine = engine;
}
+ protected ScriptEngine getEngine() {
+ if (Boolean.FALSE.equals(this.sharedEngine)) {
+ ScriptEngine engine = this.engineHolder.get();
+ if (engine == null) {
+ engine = createEngineFromName();
+ this.engineHolder.set(engine);
+ }
+ return engine;
+ }
+ else if (this.engine == null) {
+ setEngine(createEngineFromName());
+ }
+ return this.engine;
+ }
+
+ protected ScriptEngine createEngineFromName() {
+ Assert.notNull(this.engineName);
+ ScriptEngine engine = new ScriptEngineManager().getEngineByName(this.engineName);
+ Assert.state(engine != null, "No engine \"" + this.engineName + "\" found.");
+ loadScripts(engine);
+ return engine;
+ }
+
+ protected void loadScripts(ScriptEngine engine) {
+ Assert.notNull(engine);
+ if (this.scripts != null) {
+ try {
+ for (String script : this.scripts) {
+ Resource resource = this.resourceLoader.getResource(script);
+ Assert.state(resource.exists(), "Resource " + script + " not found.");
+ engine.eval(new InputStreamReader(resource.getInputStream()));
+ }
+ }
+ catch (ScriptException e) {
+ throw new IllegalStateException("could not load script", e);
+ }
+ catch (IOException e) {
+ throw new IllegalStateException("could not load script", e);
+ }
+ }
+ }
+
/**
- * Set the render function name. This function will be called with the
- * following parameters:
- *
- * - {@code template}: the view template content (String)
- * - {@code model}: the view model (Map)
- *
- * If not set, the function name is auto-detected by looking up up a single
- * {@link ScriptTemplateConfig} bean in the web application context and using
- * it to obtain the configured {@code functionName} property.
- * @see ScriptTemplateConfig
+ * See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation.
+ */
+ public void setEngineName(String engineName) {
+ this.engineName = engineName;
+ }
+
+ /**
+ * See {@link ScriptTemplateConfigurer#setScripts(String...)} documentation.
+ */
+ public void setScripts(String... scripts) {
+ this.scripts = scripts;
+ }
+
+ /**
+ * See {@link ScriptTemplateConfigurer#setRenderObject(String)} documentation.
+ */
+ public void setRenderObject(String renderObject) {
+ this.renderObject = renderObject;
+ }
+
+ /**
+ * See {@link ScriptTemplateConfigurer#setRenderFunction(String)} documentation.
*/
public void setRenderFunction(String functionName) {
this.renderFunction = functionName;
}
- public void setResourceLoader(ResourceLoader resourceLoader) {
- this.resourceLoader = resourceLoader;
+ /**
+ * See {@link ScriptTemplateConfigurer#setCharset(Charset)} documentation.
+ */
+ public void setCharset(Charset charset) {
+ this.charset = charset;
+ }
+
+ /**
+ * See {@link ScriptTemplateConfigurer#setResourceLoaderPath(String)} documentation.
+ */
+ public void setResourceLoaderPath(String resourceLoaderPath) {
+ this.resourceLoaderPath = resourceLoaderPath;
+ }
+
+ /**
+ * See {@link ScriptTemplateConfigurer#setSharedEngine(Boolean)} documentation.
+ */
+ public void setSharedEngine(Boolean sharedEngine) {
+ this.sharedEngine = sharedEngine;
}
@Override
protected void initApplicationContext(ApplicationContext context) {
super.initApplicationContext(context);
+
ScriptTemplateConfig viewConfig = autodetectViewConfig();
- if (this.engine == null) {
- this.engine = viewConfig.getEngine();
- Assert.state(this.engine != null, "Script engine should not be null.");
- Assert.state(this.engine instanceof Invocable, "Script engine should be instance of Invocable");
+ if (this.engine == null && viewConfig.getEngine() != null) {
+ setEngine(viewConfig.getEngine());
}
- if (this.resourceLoader == null) {
- this.resourceLoader = viewConfig.getResourceLoader();
+ if (this.engineName == null && viewConfig.getEngineName() != null) {
+ this.engineName = viewConfig.getEngineName();
}
- if (this.renderObject == null) {
+ if (this.scripts == null && viewConfig.getScripts() != null) {
+ this.scripts = viewConfig.getScripts();
+ }
+ if (this.renderObject == null && viewConfig.getRenderObject() != null) {
this.renderObject = viewConfig.getRenderObject();
}
- if (this.renderFunction == null) {
+ if (this.renderFunction == null && viewConfig.getRenderFunction() != null) {
this.renderFunction = viewConfig.getRenderFunction();
}
if (this.charset == null) {
- this.charset = viewConfig.getCharset();
+ this.charset = viewConfig.getCharset() == null ? DEFAULT_CHARSET : viewConfig.getCharset();
}
+ if (this.resourceLoaderPath == null) {
+ this.resourceLoaderPath = viewConfig.getResourceLoaderPath() == null ?
+ DEFAULT_RESOURCE_LOADER_PATH : viewConfig.getResourceLoaderPath();
+ }
+ if (this.resourceLoader == null) {
+ this.resourceLoader = new DefaultResourceLoader(createClassLoader());
+ }
+ if (this.sharedEngine == null && viewConfig.isShareEngine() != null) {
+ this.sharedEngine = viewConfig.isShareEngine();
+ }
+
+ Assert.state(!(this.engine != null && this.engineName != null),
+ "You should define engine or engineName properties, not both.");
+ Assert.state(!(this.engine == null && this.engineName == null),
+ "No script engine found, please specify valid engine or engineName properties.");
+ if (Boolean.FALSE.equals(this.sharedEngine)) {
+ Assert.state(this.engineName != null,
+ "When sharedEngine property is set to false, you should specify the " +
+ "script engine using the engineName property, not the engine one.");
+ }
+ Assert.state(this.renderFunction != null, "renderFunction property must be defined.");
+
+ if (this.engine != null) {
+ loadScripts(this.engine);
+ }
+ else {
+ setEngine(createEngineFromName());
+ }
+ }
+
+ protected ClassLoader createClassLoader() {
+ String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
+ List urls = new ArrayList();
+ try {
+ for (String path : paths) {
+ Resource[] resources = getApplicationContext().getResources(path);
+ if (resources.length > 0) {
+ for (Resource resource : resources) {
+ if (resource.exists()) {
+ urls.add(resource.getURL());
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot create class loader: " + e.getMessage());
+ }
+ ClassLoader classLoader = getApplicationContext().getClassLoader();
+ return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
}
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
@@ -128,13 +280,13 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
Assert.notNull("Render function must not be null", this.renderFunction);
try {
String template = getTemplate(getUrl());
- Object html = null;
+ Object html;
if (this.renderObject != null) {
Object thiz = engine.eval(this.renderObject);
- html = ((Invocable)this.engine).invokeMethod(thiz, this.renderFunction, template, model);
+ html = ((Invocable)getEngine()).invokeMethod(thiz, this.renderFunction, template, model);
}
else {
- html = ((Invocable)this.engine).invokeFunction(this.renderFunction, template, model);
+ html = ((Invocable)getEngine()).invokeFunction(this.renderFunction, template, model);
}
response.getWriter().write(String.valueOf(html));
}
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 ccb04b50b3..b2ecd06d04 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
@@ -1260,6 +1260,15 @@
]]>
+
+
+
+
+
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index fe7ba2f77f..fc40bd4b13 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -807,6 +807,7 @@ public class MvcNamespaceTests {
assertEquals("render", scriptTemplateConfigurer.getRenderFunction());
assertEquals(StandardCharsets.ISO_8859_1, scriptTemplateConfigurer.getCharset());
assertEquals("classpath:", scriptTemplateConfigurer.getResourceLoaderPath());
+ assertFalse(scriptTemplateConfigurer.isShareEngine());
String[] scripts = { "org/springframework/web/servlet/view/script/nashorn/render.js" };
accessor = new DirectFieldAccessor(scriptTemplateConfigurer);
assertArrayEquals(scripts, (String[]) accessor.getPropertyValue("scripts"));
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurerTests.java
deleted file mode 100644
index e5c46c45df..0000000000
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurerTests.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2002-2015 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
- *
- * http://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.net.URLClassLoader;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import javax.script.Invocable;
-import javax.script.ScriptEngine;
-
-import org.hamcrest.Matchers;
-import static org.junit.Assert.*;
-
-import org.junit.Before;
-import org.junit.Test;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-
-import org.springframework.context.support.StaticApplicationContext;
-
-/**
- * Unit tests for {@link ScriptTemplateConfigurer}.
- *
- * @author Sebastien Deleuze
- */
-public class ScriptTemplateConfigurerTests {
-
- private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
-
- private StaticApplicationContext applicationContext;
-
- private ScriptTemplateConfigurer configurer;
-
-
- @Before
- public void setup() throws Exception {
- this.applicationContext = new StaticApplicationContext();
- this.configurer = new ScriptTemplateConfigurer();
- this.configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH);
- }
-
- @Test
- public void customEngineAndRenderFunction() throws Exception {
- this.configurer.setApplicationContext(this.applicationContext);
- ScriptEngine engine = mock(InvocableScriptEngine.class);
- given(engine.get("key")).willReturn("value");
- this.configurer.setEngine(engine);
- this.configurer.setRenderFunction("render");
- this.configurer.afterPropertiesSet();
-
- engine = this.configurer.getEngine();
- assertNotNull(engine);
- assertEquals("value", engine.get("key"));
- assertNull(this.configurer.getRenderObject());
- assertEquals("render", this.configurer.getRenderFunction());
- assertEquals(StandardCharsets.UTF_8, this.configurer.getCharset());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void nonInvocableScriptEngine() throws Exception {
- this.configurer.setApplicationContext(this.applicationContext);
- ScriptEngine engine = mock(ScriptEngine.class);
- this.configurer.setEngine(engine);
- }
-
- @Test(expected = IllegalStateException.class)
- public void noRenderFunctionDefined() throws Exception {
- this.configurer.setApplicationContext(this.applicationContext);
- ScriptEngine engine = mock(InvocableScriptEngine.class);
- this.configurer.setEngine(engine);
- this.configurer.afterPropertiesSet();
- }
-
- @Test
- public void parentLoader() throws Exception {
-
- this.configurer.setApplicationContext(this.applicationContext);
-
- ClassLoader classLoader = this.configurer.createClassLoader();
- assertNotNull(classLoader);
- URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
- assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(1));
- assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(),
- Matchers.endsWith("org/springframework/web/servlet/view/script/"));
-
- this.configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH + ",classpath:org/springframework/web/servlet/view/");
- classLoader = this.configurer.createClassLoader();
- assertNotNull(classLoader);
- urlClassLoader = (URLClassLoader) classLoader;
- assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(2));
- assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(),
- Matchers.endsWith("org/springframework/web/servlet/view/script/"));
- assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(),
- Matchers.endsWith("org/springframework/web/servlet/view/"));
- }
-
-
- private interface InvocableScriptEngine extends ScriptEngine, Invocable {
- }
-
-}
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
index 1ae173d5c9..708eace985 100644
--- 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
@@ -16,16 +16,22 @@
package org.springframework.web.servlet.view.script;
+import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import javax.script.Invocable;
import javax.script.ScriptEngine;
-import javax.servlet.ServletContext;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.BDDMockito.given;
@@ -33,9 +39,7 @@ import static org.mockito.Mockito.mock;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContextException;
-import org.springframework.mock.web.test.MockServletContext;
-import org.springframework.web.context.WebApplicationContext;
-
+import org.springframework.context.support.StaticApplicationContext;
/**
* Unit tests for {@link ScriptTemplateView}.
@@ -44,63 +48,176 @@ import org.springframework.web.context.WebApplicationContext;
*/
public class ScriptTemplateViewTests {
- private WebApplicationContext webAppContext;
+ private ScriptTemplateView view;
+
+ private ScriptTemplateConfigurer configurer;
+
+ private StaticApplicationContext applicationContext;
+
+ private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
- private ServletContext servletContext;
@Before
public void setup() {
- this.webAppContext = mock(WebApplicationContext.class);
- this.servletContext = new MockServletContext();
- this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
+ this.configurer = new ScriptTemplateConfigurer();
+ this.applicationContext = new StaticApplicationContext();
+ this.applicationContext.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
+ this.view = new ScriptTemplateView();
+ this.view.setUrl("sampleView");
}
@Test
public void missingScriptTemplateConfig() throws Exception {
- ScriptTemplateView view = new ScriptTemplateView();
- given(this.webAppContext.getBeansOfType(ScriptTemplateConfig.class, true, false))
- .willReturn(new HashMap());
-
- view.setUrl("sampleView");
try {
- view.setApplicationContext(this.webAppContext);
- fail();
+ this.view.setApplicationContext(new StaticApplicationContext());
}
catch (ApplicationContextException ex) {
assertTrue(ex.getMessage().contains("ScriptTemplateConfig"));
+ return;
}
+ fail();
}
@Test
- public void dectectScriptTemplateConfig() throws Exception {
+ public void detectScriptTemplateConfigWithEngine() {
InvocableScriptEngine engine = mock(InvocableScriptEngine.class);
- ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
- configurer.setEngine(engine);
- configurer.setRenderObject("Template");
- configurer.setRenderFunction("render");
- configurer.setCharset(StandardCharsets.ISO_8859_1);
- Map configMap = new HashMap();
- configMap.put("scriptTemplateConfigurer", configurer);
- ScriptTemplateView view = new ScriptTemplateView();
- given(this.webAppContext.getBeansOfType(ScriptTemplateConfig.class, true, false)).willReturn(configMap);
-
- DirectFieldAccessor accessor = new DirectFieldAccessor(view);
- view.setUrl("sampleView");
- view.setApplicationContext(this.webAppContext);
+ this.configurer.setEngine(engine);
+ this.configurer.setRenderObject("Template");
+ this.configurer.setRenderFunction("render");
+ this.configurer.setCharset(StandardCharsets.ISO_8859_1);
+ DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
+ this.view.setApplicationContext(this.applicationContext);
assertEquals(engine, accessor.getPropertyValue("engine"));
- assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
assertEquals("Template", accessor.getPropertyValue("renderObject"));
assertEquals("render", accessor.getPropertyValue("renderFunction"));
+ assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
+ public void detectScriptTemplateConfigWithEngineName() {
+ this.configurer.setEngineName("nashorn");
+ 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.applicationContext);
+ assertEquals("nashorn", accessor.getPropertyValue("engineName"));
+ assertNotNull(accessor.getPropertyValue("engine"));
+ assertEquals("Template", accessor.getPropertyValue("renderObject"));
+ assertEquals("render", accessor.getPropertyValue("renderFunction"));
+ assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
+ assertEquals(true, accessor.getPropertyValue("sharedEngine"));
+ }
+
+ @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.applicationContext);
+ engine = this.view.getEngine();
+ assertNotNull(engine);
+ assertEquals("value", engine.get("key"));
+ DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
+ assertNull(accessor.getPropertyValue("renderObject"));
+ assertEquals("render", accessor.getPropertyValue("renderFunction"));
+ assertEquals(StandardCharsets.UTF_8, accessor.getPropertyValue("charset"));
+ }
+
+ @Test
+ public void nonSharedEngine() throws Exception {
+ int iterations = 20;
+ this.view.setEngineName("nashorn");
+ this.view.setRenderFunction("render");
+ this.view.setSharedEngine(false);
+ this.view.setApplicationContext(this.applicationContext);
+ ExecutorService executor = Executors.newFixedThreadPool(4);
+ List> results = new ArrayList<>();
+ for(int i = 0; i < iterations; i++) {
+ results.add(executor.submit(() -> view.getEngine() != null));
+ }
+ assertEquals(iterations, results.size());
+ for(int i = 0; i < iterations; i++) {
+ assertTrue(results.get(i).get());
+ }
+ executor.shutdown();
+ }
+
+ @Test
public void nonInvocableScriptEngine() throws Exception {
- ScriptEngine engine = mock(ScriptEngine.class);
- ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
- configurer.setEngine(engine);
+ try {
+ this.view.setEngine(mock(ScriptEngine.class));
+ } catch(IllegalArgumentException ex) {
+ assertThat(ex.getMessage(), containsString("instance"));
+ return;
+ }
fail();
}
+ @Test
+ public void noRenderFunctionDefined() {
+ this.view.setEngine(mock(InvocableScriptEngine.class));
+ try {
+ this.view.setApplicationContext(this.applicationContext);
+ } catch(IllegalStateException ex) {
+ assertThat(ex.getMessage(), containsString("renderFunction"));
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void engineAndEngineNameBothDefined() {
+ this.view.setEngine(mock(InvocableScriptEngine.class));
+ this.view.setEngineName("test");
+ this.view.setRenderFunction("render");
+ try {
+ this.view.setApplicationContext(this.applicationContext);
+ } catch(IllegalStateException ex) {
+ assertThat(ex.getMessage(), containsString("engine or engineName"));
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void engineSetterAndNonSharedEngine() {
+ this.view.setEngine(mock(InvocableScriptEngine.class));
+ this.view.setRenderFunction("render");
+ this.view.setSharedEngine(false);
+ try {
+ this.view.setApplicationContext(this.applicationContext);
+ } catch(IllegalStateException ex) {
+ assertThat(ex.getMessage(), containsString("sharedEngine"));
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void parentLoader() {
+ this.view.setEngine(mock(InvocableScriptEngine.class));
+ this.view.setRenderFunction("render");
+ this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH);
+ this.view.setApplicationContext(this.applicationContext);
+ ClassLoader classLoader = this.view.createClassLoader();
+ assertNotNull(classLoader);
+ URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
+ assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(1));
+ assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(),
+ Matchers.endsWith("org/springframework/web/servlet/view/script/"));
+ this.view.setResourceLoaderPath(RESOURCE_LOADER_PATH + ",classpath:org/springframework/web/servlet/view/");
+ classLoader = this.view.createClassLoader();
+ assertNotNull(classLoader);
+ urlClassLoader = (URLClassLoader) classLoader;
+ assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(2));
+ assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(), Matchers.endsWith("org/springframework/web/servlet/view/script/"));
+ assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), Matchers.endsWith("org/springframework/web/servlet/view/"));
+ }
+
+
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 222650f8ee..3488a15b48 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 @@
-
+