1616
1717package 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 ;
2419import java .nio .charset .Charset ;
25- import java .util .ArrayList ;
26- import java .util .List ;
2720
28- import javax .script .Invocable ;
2921import 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
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