Support ScriptEngine#eval(String, Bindings) in ScriptTemplateView

Supporting ScriptEngine#eval(String, Bindings) when no render function
is specified allows to support use cases where script templates are
simply evaluating a script expression with an even more simplified
configuration.

This improvement also makes it possible to use script engines that
do not implement Invocable.

Issue: SPR-15115
This commit is contained in:
Sebastien Deleuze
2017-06-16 10:26:26 +02:00
parent 7e251274ee
commit d5f9ad03a7
12 changed files with 139 additions and 34 deletions

View File

@@ -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();

View File

@@ -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.
* <p>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)}.
* <p>This function will be called with the following parameters:
* <ol>
* <li>{@code String template}: the template content</li>
* <li>{@code Map model}: the view model</li>
* <li>{@code String url}: the template url (since 4.2.2)</li>
* <li>{@code RenderingContext context}: the rendering context (since 5.0)</li>
* </ol>
* @see RenderingContext
*/
public void setRenderFunction(String renderFunction) {
this.renderFunction = renderFunction;

View File

@@ -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));