Skip to content

Commit 34de167

Browse files
committed
Support non thread-safe ScriptEngine in ScriptTemplateView
This commit adds a new sharedEngine property to ScriptTemplateConfigurer and ScriptTemplateView in order to support non thread-safe ScriptEngine implementations like Nashorn. When this flag is set to false, the engine is retrieved from a ThreadLocal<ScriptEngine> field instead of a ScriptEngine one. Also as part of this commit, all the initialization logic has been moved from ScriptTemplateConfigurer to ScriptTemplateView since the script engine can now be lazily initialized multiple time in the view when sharedEngine is set to false. Issue: SPR-13034
1 parent 0783a1c commit 34de167

File tree

9 files changed

+404
-281
lines changed

9 files changed

+404
-281
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/ScriptTemplateConfigurerBeanDefinitionParser.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
7373
if (element.hasAttribute("resource-loader-path")) {
7474
builder.addPropertyValue("resourceLoaderPath", element.getAttribute("resource-loader-path"));
7575
}
76+
if (element.hasAttribute("shared-engine")) {
77+
builder.addPropertyValue("sharedEngine", element.getAttribute("shared-engine"));
78+
}
7679
}
7780

7881
@Override

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import java.nio.charset.Charset;
2020
import javax.script.ScriptEngine;
2121

22-
import org.springframework.core.io.ResourceLoader;
23-
2422
/**
2523
* Interface to be implemented by objects that configure and manage a
2624
* {@link ScriptEngine} for automatic lookup in a web environment.
@@ -33,12 +31,18 @@ public interface ScriptTemplateConfig {
3331

3432
ScriptEngine getEngine();
3533

34+
String getEngineName();
35+
36+
String[] getScripts();
37+
3638
String getRenderObject();
3739

3840
String getRenderFunction();
3941

4042
Charset getCharset();
4143

42-
ResourceLoader getResourceLoader();
44+
String getResourceLoaderPath();
45+
46+
Boolean isShareEngine();
4347

4448
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java

Lines changed: 44 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,9 @@
1616

1717
package org.springframework.web.servlet.view.script;
1818

19-
import java.io.IOException;
20-
import java.io.InputStreamReader;
21-
import java.io.Reader;
22-
import java.net.URL;
23-
import java.net.URLClassLoader;
2419
import java.nio.charset.Charset;
25-
import java.util.ArrayList;
26-
import java.util.List;
2720

28-
import javax.script.Invocable;
2921
import javax.script.ScriptEngine;
30-
import javax.script.ScriptEngineManager;
31-
import javax.script.ScriptException;
32-
33-
import org.springframework.beans.factory.InitializingBean;
34-
import org.springframework.context.ApplicationContext;
35-
import org.springframework.context.ApplicationContextAware;
36-
import org.springframework.core.io.DefaultResourceLoader;
37-
import org.springframework.core.io.Resource;
38-
import org.springframework.core.io.ResourceLoader;
39-
import org.springframework.util.Assert;
40-
import org.springframework.util.StringUtils;
4122

4223
/**
4324
* An implementation of Spring MVC's {@link ScriptTemplateConfig} for creating
@@ -59,37 +40,44 @@
5940
* }
6041
* </pre>
6142
*
43+
* <p>It is possible to use non thread-safe script engines and templating libraries, like
44+
* Handlebars or React running on Nashorn, by setting the
45+
* {@link #setSharedEngine(Boolean) sharedEngine} property to {@code false}.
46+
*
6247
* @author Sebastien Deleuze
6348
* @since 4.2
6449
* @see ScriptTemplateView
6550
*/
66-
public class ScriptTemplateConfigurer implements ScriptTemplateConfig, ApplicationContextAware, InitializingBean {
51+
public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
6752

6853
private ScriptEngine engine;
6954

7055
private String engineName;
7156

72-
private ApplicationContext applicationContext;
73-
7457
private String[] scripts;
7558

7659
private String renderObject;
7760

7861
private String renderFunction;
7962

80-
private Charset charset = Charset.forName("UTF-8");
63+
private Charset charset;
8164

82-
private ResourceLoader resourceLoader;
65+
private String resourceLoaderPath;
8366

84-
private String resourceLoaderPath = "classpath:";
67+
private Boolean sharedEngine;
8568

8669
/**
8770
* Set the {@link ScriptEngine} to use by the view.
8871
* The script engine must implement {@code Invocable}.
8972
* You must define {@code engine} or {@code engineName}, not both.
73+
*
74+
* <p>When the {@code sharedEngine} flag is set to {@code false}, you should not specify
75+
* the script engine with this setter, but with the {@link #setEngineName(String)}
76+
* one (since it implies multiple lazy instanciations of the script engine).
77+
*
78+
* @see #setEngineName(String)
9079
*/
9180
public void setEngine(ScriptEngine engine) {
92-
Assert.isInstanceOf(Invocable.class, engine);
9381
this.engine = engine;
9482
}
9583

@@ -102,18 +90,15 @@ public ScriptEngine getEngine() {
10290
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
10391
* The script engine must implement {@code Invocable}.
10492
* You must define {@code engine} or {@code engineName}, not both.
93+
* @see #setEngine(ScriptEngine)
10594
*/
10695
public void setEngineName(String engineName) {
10796
this.engineName = engineName;
10897
}
10998

11099
@Override
111-
public void setApplicationContext(ApplicationContext applicationContext) {
112-
this.applicationContext = applicationContext;
113-
}
114-
115-
protected ApplicationContext getApplicationContext() {
116-
return this.applicationContext;
100+
public String getEngineName() {
101+
return this.engineName;
117102
}
118103

119104
/**
@@ -134,8 +119,8 @@ public void setScripts(String... scriptNames) {
134119
}
135120

136121
@Override
137-
public String getRenderObject() {
138-
return renderObject;
122+
public String[] getScripts() {
123+
return this.scripts;
139124
}
140125

141126
/**
@@ -148,8 +133,8 @@ public void setRenderObject(String renderObject) {
148133
}
149134

150135
@Override
151-
public String getRenderFunction() {
152-
return renderFunction;
136+
public String getRenderObject() {
137+
return this.renderObject;
153138
}
154139

155140
/**
@@ -164,6 +149,11 @@ public void setRenderFunction(String renderFunction) {
164149
this.renderFunction = renderFunction;
165150
}
166151

152+
@Override
153+
public String getRenderFunction() {
154+
return this.renderFunction;
155+
}
156+
167157
/**
168158
* Set the charset used to read script and template files.
169159
* ({@code UTF-8} by default).
@@ -189,69 +179,30 @@ public void setResourceLoaderPath(String resourceLoaderPath) {
189179
this.resourceLoaderPath = resourceLoaderPath;
190180
}
191181

182+
@Override
192183
public String getResourceLoaderPath() {
193-
return resourceLoaderPath;
184+
return this.resourceLoaderPath;
194185
}
195186

196-
@Override
197-
public ResourceLoader getResourceLoader() {
198-
return resourceLoader;
187+
/**
188+
* When set to {@code false}, use thread-local {@link ScriptEngine} instances instead
189+
* of one single shared instance. This flag should be set to {@code false} for those
190+
* using non thread-safe script engines and templating libraries, like Handlebars or
191+
* React running on Nashorn for example.
192+
*
193+
* <p>When this flag is set to {@code false}, the script engine must be specified using
194+
* {@link #setEngineName(String)}. Using {@link #setEngine(ScriptEngine)} is not
195+
* possible because multiple instances of the script engine need to be created lazily
196+
* (one per thread).
197+
* @see <a href="http://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html#getParameter-java.lang.String-">THREADING ScriptEngine parameter<a/>
198+
*/
199+
public void setSharedEngine(Boolean sharedEngine) {
200+
this.sharedEngine = sharedEngine;
199201
}
200202

201203
@Override
202-
public void afterPropertiesSet() throws Exception {
203-
if (this.engine == null) {
204-
this.engine = createScriptEngine();
205-
}
206-
Assert.state(this.renderFunction != null, "renderFunction property must be defined.");
207-
this.resourceLoader = new DefaultResourceLoader(createClassLoader());
208-
if (this.scripts != null) {
209-
try {
210-
for (String script : this.scripts) {
211-
this.engine.eval(read(script));
212-
}
213-
}
214-
catch (ScriptException e) {
215-
throw new IllegalStateException("could not load script", e);
216-
}
217-
}
218-
}
219-
220-
protected ClassLoader createClassLoader() throws IOException {
221-
String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
222-
List<URL> urls = new ArrayList<URL>();
223-
for (String path : paths) {
224-
Resource[] resources = getApplicationContext().getResources(path);
225-
if (resources.length > 0) {
226-
for (Resource resource : resources) {
227-
if (resource.exists()) {
228-
urls.add(resource.getURL());
229-
}
230-
}
231-
}
232-
}
233-
ClassLoader classLoader = getApplicationContext().getClassLoader();
234-
return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
235-
}
236-
237-
private Reader read(String path) throws IOException {
238-
Resource resource = this.resourceLoader.getResource(path);
239-
Assert.state(resource.exists(), "Resource " + path + " not found.");
240-
return new InputStreamReader(resource.getInputStream());
241-
}
242-
243-
protected ScriptEngine createScriptEngine() throws IOException {
244-
if (this.engine != null && this.engineName != null) {
245-
throw new IllegalStateException("You should define engine or engineName properties, not both.");
246-
}
247-
if (this.engineName != null) {
248-
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(this.engineName);
249-
Assert.state(scriptEngine != null, "No engine \"" + this.engineName + "\" found.");
250-
Assert.state(scriptEngine instanceof Invocable, "Script engine should be instance of Invocable");
251-
this.engine = scriptEngine;
252-
}
253-
Assert.state(this.engine != null, "No script engine found, please specify valid engine or engineName properties.");
254-
return this.engine;
204+
public Boolean isShareEngine() {
205+
return this.sharedEngine;
255206
}
256207

257208
}

0 commit comments

Comments
 (0)