Skip to content

Commit c8f8dfa

Browse files
committed
Add engineSupplier property to ScriptTemplateConfigurer
This commit adds an engineSupplier property to ScriptTemplateConfigurer and ScriptTemplateView in order to be able to customize the ScriptEngine when sharedEngine is set to false. This can be useful with Graal.js for example. Closes gh-23258
1 parent 01125a5 commit c8f8dfa

File tree

8 files changed

+303
-46
lines changed

8 files changed

+303
-46
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.web.reactive.result.view.script;
1818

1919
import java.nio.charset.Charset;
20+
import java.util.function.Supplier;
2021
import javax.script.Bindings;
2122
import javax.script.ScriptEngine;
2223

@@ -38,6 +39,13 @@ public interface ScriptTemplateConfig {
3839
@Nullable
3940
ScriptEngine getEngine();
4041

42+
/**
43+
* Return the engine supplier that will be used to instantiate the {@link ScriptEngine}.
44+
* @since 5.2
45+
*/
46+
@Nullable
47+
Supplier<ScriptEngine> getEngineSupplier();
48+
4149
/**
4250
* Return the engine name that will be used to instantiate the {@link ScriptEngine}.
4351
*/

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.web.reactive.result.view.script;
1818

1919
import java.nio.charset.Charset;
20+
import java.util.function.Supplier;
2021
import javax.script.Bindings;
2122
import javax.script.ScriptEngine;
2223

@@ -52,6 +53,9 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
5253
@Nullable
5354
private ScriptEngine engine;
5455

56+
@Nullable
57+
private Supplier<ScriptEngine> engineSupplier;
58+
5559
@Nullable
5660
private String engineName;
5761

@@ -94,8 +98,10 @@ public ScriptTemplateConfigurer(String engineName) {
9498
* You must define {@code engine} or {@code engineName}, not both.
9599
* <p>When the {@code sharedEngine} flag is set to {@code false}, you should not specify
96100
* the script engine with this setter, but with the {@link #setEngineName(String)}
97-
* one (since it implies multiple lazy instantiations of the script engine).
101+
* or {@link #setEngineSupplier(Supplier)} (since it implies multiple lazy
102+
* instantiations of the script engine).
98103
* @see #setEngineName(String)
104+
* @see #setEngineSupplier(Supplier)
99105
*/
100106
public void setEngine(@Nullable ScriptEngine engine) {
101107
this.engine = engine;
@@ -107,11 +113,31 @@ public ScriptEngine getEngine() {
107113
return this.engine;
108114
}
109115

116+
/**
117+
* Set the {@link ScriptEngine} supplier to use by the view, usually used with
118+
* {@link #setSharedEngine(Boolean)} set to {@code false}.
119+
* If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
120+
* You must either define {@code engineSupplier}, {@code engine} or {@code engineName}.
121+
* @since 5.2
122+
* @see #setEngine(ScriptEngine)
123+
* @see #setEngineName(String)
124+
*/
125+
public void setEngineSupplier(@Nullable Supplier<ScriptEngine> engineSupplier) {
126+
this.engineSupplier = engineSupplier;
127+
}
128+
129+
@Override
130+
@Nullable
131+
public Supplier<ScriptEngine> getEngineSupplier() {
132+
return this.engineSupplier;
133+
}
134+
110135
/**
111136
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
112137
* If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
113138
* You must define {@code engine} or {@code engineName}, not both.
114139
* @see #setEngine(ScriptEngine)
140+
* @see #setEngineSupplier(Supplier)
115141
*/
116142
public void setEngineName(@Nullable String engineName) {
117143
this.engineName = engineName;

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Locale;
2323
import java.util.Map;
2424
import java.util.function.Function;
25+
import java.util.function.Supplier;
2526
import javax.script.Invocable;
2627
import javax.script.ScriptEngine;
2728
import javax.script.ScriptEngineManager;
@@ -74,6 +75,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
7475
@Nullable
7576
private ScriptEngine engine;
7677

78+
@Nullable
79+
private Supplier<ScriptEngine> engineSupplier;
80+
7781
@Nullable
7882
private String engineName;
7983

@@ -118,6 +122,13 @@ public void setEngine(ScriptEngine engine) {
118122
this.engine = engine;
119123
}
120124

125+
/**
126+
* See {@link ScriptTemplateConfigurer#setEngineSupplier(Supplier)} documentation.
127+
*/
128+
public void setEngineSupplier(Supplier<ScriptEngine> engineSupplier) {
129+
this.engineSupplier = engineSupplier;
130+
}
131+
121132
/**
122133
* See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation.
123134
*/
@@ -175,7 +186,10 @@ public void setApplicationContext(@Nullable ApplicationContext context) {
175186

176187
ScriptTemplateConfig viewConfig = autodetectViewConfig();
177188
if (this.engine == null && viewConfig.getEngine() != null) {
178-
setEngine(viewConfig.getEngine());
189+
this.engine = viewConfig.getEngine();
190+
}
191+
if (this.engineSupplier == null && viewConfig.getEngineSupplier() != null) {
192+
this.engineSupplier = viewConfig.getEngineSupplier();
179193
}
180194
if (this.engineName == null && viewConfig.getEngineName() != null) {
181195
this.engineName = viewConfig.getEngineName();
@@ -200,22 +214,33 @@ public void setApplicationContext(@Nullable ApplicationContext context) {
200214
this.sharedEngine = viewConfig.isSharedEngine();
201215
}
202216

203-
Assert.isTrue(!(this.engine != null && this.engineName != null),
204-
"You should define either 'engine' or 'engineName', not both.");
205-
Assert.isTrue(!(this.engine == null && this.engineName == null),
206-
"No script engine found, please specify either 'engine' or 'engineName'.");
217+
int engineCount = 0;
218+
if (this.engine != null) {
219+
engineCount++;
220+
}
221+
if (this.engineSupplier != null) {
222+
engineCount++;
223+
}
224+
if (this.engineName != null) {
225+
engineCount++;
226+
}
227+
Assert.isTrue(engineCount == 1,
228+
"You should define either 'engine', 'engineSupplier' or 'engineName'.");
207229

208230
if (Boolean.FALSE.equals(this.sharedEngine)) {
209-
Assert.isTrue(this.engineName != null,
231+
Assert.isTrue(this.engine == null,
210232
"When 'sharedEngine' is set to false, you should specify the " +
211-
"script engine using the 'engineName' property, not the 'engine' one.");
233+
"script engine using 'engineName' or 'engineSupplier' , not 'engine'.");
212234
}
213235
else if (this.engine != null) {
214236
loadScripts(this.engine);
215237
}
216-
else {
238+
else if (this.engineName != null) {
217239
setEngine(createEngineFromName(this.engineName));
218240
}
241+
else {
242+
setEngine(createEngineFromSupplier());
243+
}
219244

220245
if (this.renderFunction != null && this.engine != null) {
221246
Assert.isInstanceOf(Invocable.class, this.engine,
@@ -225,8 +250,12 @@ else if (this.engine != null) {
225250

226251
protected ScriptEngine getEngine() {
227252
if (Boolean.FALSE.equals(this.sharedEngine)) {
228-
Assert.state(this.engineName != null, "No engine name specified");
229-
return createEngineFromName(this.engineName);
253+
if (this.engineName != null) {
254+
return createEngineFromName(this.engineName);
255+
}
256+
else {
257+
return createEngineFromSupplier();
258+
}
230259
}
231260
else {
232261
Assert.state(this.engine != null, "No shared engine available");
@@ -246,6 +275,16 @@ protected ScriptEngine createEngineFromName(String engineName) {
246275
return engine;
247276
}
248277

278+
private ScriptEngine createEngineFromSupplier() {
279+
ScriptEngine engine = this.engineSupplier.get();
280+
if (this.renderFunction != null && engine != null) {
281+
Assert.isInstanceOf(Invocable.class, engine,
282+
"ScriptEngine must implement Invocable when 'renderFunction' is specified");
283+
}
284+
loadScripts(engine);
285+
return engine;
286+
}
287+
249288
protected void loadScripts(ScriptEngine engine) {
250289
if (!ObjectUtils.isEmpty(this.scripts)) {
251290
for (String script : this.scripts) {

spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,28 @@ public void engineAndEngineNameBothDefined() {
169169
this.view.setRenderFunction("render");
170170
assertThatIllegalArgumentException().isThrownBy(() ->
171171
this.view.setApplicationContext(this.context))
172-
.withMessageContaining("'engine' or 'engineName'");
172+
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
173+
}
174+
175+
@Test // gh-23258
176+
public void engineAndEngineSupplierBothDefined() {
177+
ScriptEngine engine = mock(InvocableScriptEngine.class);
178+
this.view.setEngineSupplier(() -> engine);
179+
this.view.setEngine(engine);
180+
this.view.setRenderFunction("render");
181+
assertThatIllegalArgumentException().isThrownBy(() ->
182+
this.view.setApplicationContext(this.context))
183+
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
184+
}
185+
186+
@Test // gh-23258
187+
public void engineNameAndEngineSupplierBothDefined() {
188+
this.view.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
189+
this.view.setEngineName("test");
190+
this.view.setRenderFunction("render");
191+
assertThatIllegalArgumentException().isThrownBy(() ->
192+
this.view.setApplicationContext(this.context))
193+
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
173194
}
174195

175196
@Test
@@ -182,6 +203,43 @@ public void engineSetterAndNonSharedEngine() {
182203
.withMessageContaining("sharedEngine");
183204
}
184205

206+
@Test // gh-23258
207+
public void engineSupplierWithSharedEngine() {
208+
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
209+
this.configurer.setRenderObject("Template");
210+
this.configurer.setRenderFunction("render");
211+
this.configurer.setSharedEngine(true);
212+
213+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
214+
this.view.setApplicationContext(this.context);
215+
ScriptEngine engine1 = this.view.getEngine();
216+
ScriptEngine engine2 = this.view.getEngine();
217+
assertThat(engine1).isNotNull();
218+
assertThat(engine2).isNotNull();
219+
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
220+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
221+
assertThat(accessor.getPropertyValue("sharedEngine")).isEqualTo(true);
222+
}
223+
224+
@SuppressWarnings("unchecked")
225+
@Test // gh-23258
226+
public void engineSupplierWithNonSharedEngine() {
227+
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
228+
this.configurer.setRenderObject("Template");
229+
this.configurer.setRenderFunction("render");
230+
this.configurer.setSharedEngine(false);
231+
232+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
233+
this.view.setApplicationContext(this.context);
234+
ScriptEngine engine1 = this.view.getEngine();
235+
ScriptEngine engine2 = this.view.getEngine();
236+
assertThat(engine1).isNotNull();
237+
assertThat(engine2).isNotNull();
238+
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
239+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
240+
assertThat(accessor.getPropertyValue("sharedEngine")).isEqualTo(false);
241+
}
242+
185243
private interface InvocableScriptEngine extends ScriptEngine, Invocable {
186244
}
187245

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.web.servlet.view.script;
1818

1919
import java.nio.charset.Charset;
20+
import java.util.function.Supplier;
2021
import javax.script.Bindings;
2122
import javax.script.ScriptEngine;
2223

@@ -38,6 +39,13 @@ public interface ScriptTemplateConfig {
3839
@Nullable
3940
ScriptEngine getEngine();
4041

42+
/**
43+
* Return the engine supplier that will be used to instantiate the {@link ScriptEngine}.
44+
* @since 5.2
45+
*/
46+
@Nullable
47+
Supplier<ScriptEngine> getEngineSupplier();
48+
4149
/**
4250
* Return the engine name that will be used to instantiate the {@link ScriptEngine}.
4351
*/

0 commit comments

Comments
 (0)