diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java
index 1b494b5f799..8d1cac3a43f 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java
@@ -17,6 +17,8 @@
package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset;
+
+import javax.script.Bindings;
import javax.script.ScriptEngine;
import org.springframework.lang.Nullable;
@@ -63,7 +65,8 @@ public interface ScriptTemplateConfig {
String getRenderObject();
/**
- * Return the render function name (mandatory).
+ * Return the render function name (optional). If not specified, the script templates
+ * will be evaluated with {@link ScriptEngine#eval(String, Bindings)}.
*/
@Nullable
String getRenderFunction();
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java
index eee09342f13..868b9532cc4 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java
@@ -17,6 +17,8 @@
package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset;
+
+import javax.script.Bindings;
import javax.script.ScriptEngine;
/**
@@ -63,9 +65,23 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
private String resourceLoaderPath;
+ /**
+ * Default constructor.
+ */
+ public ScriptTemplateConfigurer() {
+ }
+
+ /**
+ * Create a new ScriptTemplateConfigurer using the given engine name.
+ */
+ public ScriptTemplateConfigurer(String engineName) {
+ this.engineName = engineName;
+ }
+
+
/**
* Set the {@link ScriptEngine} to use by the view.
- * The script engine must implement {@code Invocable}.
+ * If {@code renderFunction} is specified, 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)}
@@ -83,7 +99,7 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
/**
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
- * The script engine must implement {@code Invocable}.
+ * If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
* You must define {@code engine} or {@code engineName}, not both.
* @see #setEngine(ScriptEngine)
*/
@@ -152,14 +168,15 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
}
/**
- * Set the render function name (mandatory).
- *
+ * Set the render function name (optional). If not specified, the script templates
+ * will be evaluated with {@link ScriptEngine#eval(String, Bindings)}.
*
This function will be called with the following parameters:
*
* - {@code String template}: the template content
* - {@code Map model}: the view model
- * - {@code String url}: the template url
+ * - {@code RenderingContext context}: the rendering context (since 5.0)
*
+ * @see RenderingContext
*/
public void setRenderFunction(String renderFunction) {
this.renderFunction = renderFunction;
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 d695eebce2f..c6f0787a7e3 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
@@ -26,6 +26,7 @@ import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
+import javax.script.SimpleBindings;
import reactor.core.publisher.Mono;
@@ -112,7 +113,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
* See {@link ScriptTemplateConfigurer#setEngine(ScriptEngine)} documentation.
*/
public void setEngine(ScriptEngine engine) {
- Assert.isInstanceOf(Invocable.class, engine, "ScriptEngine must implement Invocable");
this.engine = engine;
}
@@ -225,7 +225,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
setEngine(createEngineFromName());
}
- Assert.isTrue(this.renderFunction != null, "The 'renderFunction' property must be defined.");
+ if (this.renderFunction != null && this.engine != null) {
+ Assert.isInstanceOf(Invocable.class, this.engine, "ScriptEngine must implement Invocable when 'renderFunction' is specified.");
+ }
}
protected ScriptEngine getEngine() {
@@ -297,8 +299,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
ServerHttpResponse response = exchange.getResponse();
try {
ScriptEngine engine = getEngine();
- Invocable invocable = (Invocable) engine;
-
String url = getUrl();
Assert.state(url != null, "'url' not set");
String template = getTemplate(url);
@@ -316,12 +316,18 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
obtainApplicationContext(), this.locale, templateLoader, url);
Object html;
- if (this.renderObject != null) {
+ if (this.renderFunction == null) {
+ SimpleBindings bindings = new SimpleBindings();
+ bindings.putAll(model);
+ model.put("renderingContext", context);
+ html = engine.eval(template, bindings);
+ }
+ else if (this.renderObject != null) {
Object thiz = engine.eval(this.renderObject);
- html = invocable.invokeMethod(thiz, this.renderFunction, template, model, context);
+ html = ((Invocable)engine).invokeMethod(thiz, this.renderFunction, template, model, context);
}
else {
- html = invocable.invokeFunction(this.renderFunction, template, model, context);
+ html = ((Invocable)engine).invokeFunction(this.renderFunction, template, model, context);
}
byte[] bytes = String.valueOf(html).getBytes(StandardCharsets.UTF_8);
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java
index 7844dcfec21..b38e6cca460 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/KotlinScriptTemplateTests.java
@@ -30,6 +30,7 @@ import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
+import org.springframework.mock.web.test.MockHttpServletResponse;
import static org.junit.Assert.assertEquals;
@@ -60,6 +61,19 @@ public class KotlinScriptTemplateTests {
response.getBodyAsString().block());
}
+ @Test
+ public void renderTemplateWithoutRenderFunction() throws Exception {
+ Map model = new HashMap<>();
+ model.put("header", "");
+ model.put("hello", "Hello");
+ model.put("foo", "Foo");
+ model.put("footer", "");
+ MockServerHttpResponse response = renderViewWithModel("org/springframework/web/reactive/result/view/script/kotlin/eval.kts",
+ model, Locale.ENGLISH, ScriptTemplatingConfigurationWithoutRenderFunction.class);
+ assertEquals("\nHello Foo
\n",
+ response.getBodyAsString().block());
+ }
+
private MockServerHttpResponse renderViewWithModel(String viewUrl, Map model, Locale locale, Class> configuration) throws Exception {
ScriptTemplateView view = createViewWithUrl(viewUrl, configuration);
view.setLocale(locale);
@@ -101,4 +115,13 @@ public class KotlinScriptTemplateTests {
}
}
+ @Configuration
+ static class ScriptTemplatingConfigurationWithoutRenderFunction {
+
+ @Bean
+ public ScriptTemplateConfigurer kotlinScriptConfigurer() {
+ return new ScriptTemplateConfigurer("kotlin");
+ }
+ }
+
}
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
index 529d79be01b..1e3ca39cae4 100644
--- 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
@@ -151,17 +151,16 @@ public class ScriptTemplateViewTests {
@Test
public void nonInvocableScriptEngine() throws Exception {
- this.expectedException.expect(IllegalArgumentException.class);
this.view.setEngine(mock(ScriptEngine.class));
- this.expectedException.expectMessage(contains("instance"));
+ this.view.setApplicationContext(this.context);
}
@Test
- public void noRenderFunctionDefined() {
- this.view.setEngine(mock(InvocableScriptEngine.class));
+ public void nonInvocableScriptEngineWithRenderFunction() throws Exception {
+ this.view.setEngine(mock(ScriptEngine.class));
+ this.view.setRenderFunction("render");
this.expectedException.expect(IllegalArgumentException.class);
this.view.setApplicationContext(this.context);
- this.expectedException.expectMessage(contains("renderFunction"));
}
@Test
diff --git a/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script/kotlin/eval.kts b/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script/kotlin/eval.kts
new file mode 100644
index 00000000000..6a8121acf02
--- /dev/null
+++ b/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script/kotlin/eval.kts
@@ -0,0 +1,4 @@
+// TODO Improve syntax when KT-15125 will be fixed
+"""${bindings["header"]}
+${bindings["hello"]} ${bindings["foo"]}
+${bindings["footer"]}"""
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 861f9998806..ae9935b9bbc 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
@@ -17,6 +17,8 @@
package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset;
+
+import javax.script.Bindings;
import javax.script.ScriptEngine;
import org.springframework.lang.Nullable;
@@ -63,7 +65,8 @@ public interface ScriptTemplateConfig {
String getRenderObject();
/**
- * Return the render function name (mandatory).
+ * Return the render function name (optional). If not specified, the script templates
+ * will be evaluated with {@link ScriptEngine#eval(String, Bindings)}.
*/
@Nullable
String getRenderFunction();
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 4542c418850..d439ec788ae 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
@@ -17,6 +17,8 @@
package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset;
+
+import javax.script.Bindings;
import javax.script.ScriptEngine;
/**
@@ -65,9 +67,23 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
private String resourceLoaderPath;
+ /**
+ * Default constructor.
+ */
+ public ScriptTemplateConfigurer() {
+ }
+
+ /**
+ * Create a new ScriptTemplateConfigurer using the given engine name.
+ */
+ public ScriptTemplateConfigurer(String engineName) {
+ this.engineName = engineName;
+ }
+
+
/**
* Set the {@link ScriptEngine} to use by the view.
- * The script engine must implement {@code Invocable}.
+ * If {@code renderFunction} is specified, 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)}
@@ -85,7 +101,7 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
/**
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
- * The script engine must implement {@code Invocable}.
+ * If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
* You must define {@code engine} or {@code engineName}, not both.
* @see #setEngine(ScriptEngine)
*/
@@ -155,13 +171,15 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
}
/**
- * Set the render function name (mandatory).
+ * Set the render function name (optional). If not specified, the script templates
+ * will be evaluated with {@link ScriptEngine#eval(String, Bindings)}.
*
This function will be called with the following parameters:
*
* - {@code String template}: the template content
* - {@code Map model}: the view model
- * - {@code String url}: the template url (since 4.2.2)
+ * - {@code RenderingContext context}: the rendering context (since 5.0)
*
+ * @see RenderingContext
*/
public void setRenderFunction(String renderFunction) {
this.renderFunction = renderFunction;
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 0306e3fc97b..c44ca67c1cf 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
@@ -29,6 +29,7 @@ import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
+import javax.script.SimpleBindings;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -126,7 +127,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
* See {@link ScriptTemplateConfigurer#setEngine(ScriptEngine)} documentation.
*/
public void setEngine(ScriptEngine engine) {
- Assert.isInstanceOf(Invocable.class, engine, "ScriptEngine must implement Invocable");
this.engine = engine;
}
@@ -260,7 +260,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
setEngine(createEngineFromName());
}
- Assert.isTrue(this.renderFunction != null, "The 'renderFunction' property must be defined.");
+ if (this.renderFunction != null && this.engine != null) {
+ Assert.isInstanceOf(Invocable.class, this.engine, "ScriptEngine must implement Invocable when 'renderFunction' is specified.");
+ }
}
protected ScriptEngine getEngine() {
@@ -357,7 +359,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
try {
ScriptEngine engine = getEngine();
- Invocable invocable = (Invocable) engine;
String url = getUrl();
Assert.state(url != null, "'url' not set");
String template = getTemplate(url);
@@ -372,12 +373,18 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
RenderingContext context = new RenderingContext(obtainApplicationContext(), this.locale, templateLoader, url);
Object html;
- if (this.renderObject != null) {
+ if (this.renderFunction == null) {
+ SimpleBindings bindings = new SimpleBindings();
+ bindings.putAll(model);
+ model.put("renderingContext", context);
+ html = engine.eval(template, bindings);
+ }
+ else if (this.renderObject != null) {
Object thiz = engine.eval(this.renderObject);
- html = invocable.invokeMethod(thiz, this.renderFunction, template, model, context);
+ html = ((Invocable)engine).invokeMethod(thiz, this.renderFunction, template, model, context);
}
else {
- html = invocable.invokeFunction(this.renderFunction, template, model, context);
+ html = ((Invocable)engine).invokeFunction(this.renderFunction, template, model, context);
}
response.getWriter().write(String.valueOf(html));
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java
index ba0311aeeb6..6ebaa384057 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/KotlinScriptTemplateTests.java
@@ -74,6 +74,19 @@ public class KotlinScriptTemplateTests {
response.getContentAsString());
}
+ @Test
+ public void renderTemplateWithoutRenderFunction() throws Exception {
+ Map model = new HashMap<>();
+ model.put("header", "");
+ model.put("hello", "Hello");
+ model.put("foo", "Foo");
+ model.put("footer", "");
+ MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/kotlin/eval.kts",
+ model, Locale.ENGLISH, ScriptTemplatingConfigurationWithoutRenderFunction.class);
+ assertEquals("\nHello Foo
\n",
+ response.getContentAsString());
+ }
+
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map model, Locale locale, Class> configuration) throws Exception {
ScriptTemplateView view = createViewWithUrl(viewUrl, configuration);
view.setLocale(locale);
@@ -116,4 +129,13 @@ public class KotlinScriptTemplateTests {
}
}
+ @Configuration
+ static class ScriptTemplatingConfigurationWithoutRenderFunction {
+
+ @Bean
+ public ScriptTemplateConfigurer kotlinScriptConfigurer() {
+ return new ScriptTemplateConfigurer("kotlin");
+ }
+ }
+
}
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 034308970f3..c6f3daa961e 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
@@ -166,17 +166,16 @@ public class ScriptTemplateViewTests {
@Test
public void nonInvocableScriptEngine() throws Exception {
- this.expectedException.expect(IllegalArgumentException.class);
this.view.setEngine(mock(ScriptEngine.class));
- this.expectedException.expectMessage(contains("instance"));
+ this.view.setApplicationContext(this.wac);
}
@Test
- public void noRenderFunctionDefined() {
- this.view.setEngine(mock(InvocableScriptEngine.class));
+ public void nonInvocableScriptEngineWithRenderFunction() throws Exception {
+ this.view.setEngine(mock(ScriptEngine.class));
+ this.view.setRenderFunction("render");
this.expectedException.expect(IllegalArgumentException.class);
this.view.setApplicationContext(this.wac);
- this.expectedException.expectMessage(contains("renderFunction"));
}
@Test
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/kotlin/eval.kts b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/kotlin/eval.kts
new file mode 100644
index 00000000000..6a8121acf02
--- /dev/null
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script/kotlin/eval.kts
@@ -0,0 +1,4 @@
+// TODO Improve syntax when KT-15125 will be fixed
+"""${bindings["header"]}
+${bindings["hello"]} ${bindings["foo"]}
+${bindings["footer"]}"""