Browse Source

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
pull/23281/head
Sebastien Deleuze 7 years ago
parent
commit
c8f8dfa39e
  1. 10
      spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java
  2. 30
      spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java
  3. 59
      spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java
  4. 60
      spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java
  5. 10
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java
  6. 40
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java
  7. 73
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java
  8. 67
      spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.view.script; package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
@ -38,6 +39,13 @@ public interface ScriptTemplateConfig {
@Nullable @Nullable
ScriptEngine getEngine(); ScriptEngine getEngine();
/**
* Return the engine supplier that will be used to instantiate the {@link ScriptEngine}.
* @since 5.2
*/
@Nullable
Supplier<ScriptEngine> getEngineSupplier();
/** /**
* Return the engine name that will be used to instantiate the {@link ScriptEngine}. * Return the engine name that will be used to instantiate the {@link ScriptEngine}.
*/ */

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.view.script; package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
@ -52,6 +53,9 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
@Nullable @Nullable
private ScriptEngine engine; private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable @Nullable
private String engineName; private String engineName;
@ -94,8 +98,10 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
* You must define {@code engine} or {@code engineName}, not both. * 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 * <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)} * the script engine with this setter, but with the {@link #setEngineName(String)}
* one (since it implies multiple lazy instantiations of the script engine). * or {@link #setEngineSupplier(Supplier)} (since it implies multiple lazy
* instantiations of the script engine).
* @see #setEngineName(String) * @see #setEngineName(String)
* @see #setEngineSupplier(Supplier)
*/ */
public void setEngine(@Nullable ScriptEngine engine) { public void setEngine(@Nullable ScriptEngine engine) {
this.engine = engine; this.engine = engine;
@ -107,11 +113,31 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
return this.engine; return this.engine;
} }
/**
* Set the {@link ScriptEngine} supplier to use by the view, usually used with
* {@link #setSharedEngine(Boolean)} set to {@code false}.
* If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
* You must either define {@code engineSupplier}, {@code engine} or {@code engineName}.
* @since 5.2
* @see #setEngine(ScriptEngine)
* @see #setEngineName(String)
*/
public void setEngineSupplier(@Nullable Supplier<ScriptEngine> engineSupplier) {
this.engineSupplier = engineSupplier;
}
@Override
@Nullable
public Supplier<ScriptEngine> getEngineSupplier() {
return this.engineSupplier;
}
/** /**
* Set the engine name that will be used to instantiate the {@link ScriptEngine}. * Set the engine name that will be used to instantiate the {@link ScriptEngine}.
* If {@code renderFunction} is specified, 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. * You must define {@code engine} or {@code engineName}, not both.
* @see #setEngine(ScriptEngine) * @see #setEngine(ScriptEngine)
* @see #setEngineSupplier(Supplier)
*/ */
public void setEngineName(@Nullable String engineName) { public void setEngineName(@Nullable String engineName) {
this.engineName = engineName; this.engineName = engineName;

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

@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import javax.script.Invocable; import javax.script.Invocable;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager; import javax.script.ScriptEngineManager;
@ -74,6 +75,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
@Nullable @Nullable
private ScriptEngine engine; private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable @Nullable
private String engineName; private String engineName;
@ -118,6 +122,13 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.engine = engine; this.engine = engine;
} }
/**
* See {@link ScriptTemplateConfigurer#setEngineSupplier(Supplier)} documentation.
*/
public void setEngineSupplier(Supplier<ScriptEngine> engineSupplier) {
this.engineSupplier = engineSupplier;
}
/** /**
* See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation. * See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation.
*/ */
@ -175,7 +186,10 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
ScriptTemplateConfig viewConfig = autodetectViewConfig(); ScriptTemplateConfig viewConfig = autodetectViewConfig();
if (this.engine == null && viewConfig.getEngine() != null) { if (this.engine == null && viewConfig.getEngine() != null) {
setEngine(viewConfig.getEngine()); this.engine = viewConfig.getEngine();
}
if (this.engineSupplier == null && viewConfig.getEngineSupplier() != null) {
this.engineSupplier = viewConfig.getEngineSupplier();
} }
if (this.engineName == null && viewConfig.getEngineName() != null) { if (this.engineName == null && viewConfig.getEngineName() != null) {
this.engineName = viewConfig.getEngineName(); this.engineName = viewConfig.getEngineName();
@ -200,22 +214,33 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.sharedEngine = viewConfig.isSharedEngine(); this.sharedEngine = viewConfig.isSharedEngine();
} }
Assert.isTrue(!(this.engine != null && this.engineName != null), int engineCount = 0;
"You should define either 'engine' or 'engineName', not both."); if (this.engine != null) {
Assert.isTrue(!(this.engine == null && this.engineName == null), engineCount++;
"No script engine found, please specify either 'engine' or 'engineName'."); }
if (this.engineSupplier != null) {
engineCount++;
}
if (this.engineName != null) {
engineCount++;
}
Assert.isTrue(engineCount == 1,
"You should define either 'engine', 'engineSupplier' or 'engineName'.");
if (Boolean.FALSE.equals(this.sharedEngine)) { if (Boolean.FALSE.equals(this.sharedEngine)) {
Assert.isTrue(this.engineName != null, Assert.isTrue(this.engine == null,
"When 'sharedEngine' is set to false, you should specify the " + "When 'sharedEngine' is set to false, you should specify the " +
"script engine using the 'engineName' property, not the 'engine' one."); "script engine using 'engineName' or 'engineSupplier' , not 'engine'.");
} }
else if (this.engine != null) { else if (this.engine != null) {
loadScripts(this.engine); loadScripts(this.engine);
} }
else { else if (this.engineName != null) {
setEngine(createEngineFromName(this.engineName)); setEngine(createEngineFromName(this.engineName));
} }
else {
setEngine(createEngineFromSupplier());
}
if (this.renderFunction != null && this.engine != null) { if (this.renderFunction != null && this.engine != null) {
Assert.isInstanceOf(Invocable.class, this.engine, Assert.isInstanceOf(Invocable.class, this.engine,
@ -225,8 +250,12 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
protected ScriptEngine getEngine() { protected ScriptEngine getEngine() {
if (Boolean.FALSE.equals(this.sharedEngine)) { if (Boolean.FALSE.equals(this.sharedEngine)) {
Assert.state(this.engineName != null, "No engine name specified"); if (this.engineName != null) {
return createEngineFromName(this.engineName); return createEngineFromName(this.engineName);
}
else {
return createEngineFromSupplier();
}
} }
else { else {
Assert.state(this.engine != null, "No shared engine available"); Assert.state(this.engine != null, "No shared engine available");
@ -246,6 +275,16 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
return engine; return engine;
} }
private ScriptEngine createEngineFromSupplier() {
ScriptEngine engine = this.engineSupplier.get();
if (this.renderFunction != null && engine != null) {
Assert.isInstanceOf(Invocable.class, engine,
"ScriptEngine must implement Invocable when 'renderFunction' is specified");
}
loadScripts(engine);
return engine;
}
protected void loadScripts(ScriptEngine engine) { protected void loadScripts(ScriptEngine engine) {
if (!ObjectUtils.isEmpty(this.scripts)) { if (!ObjectUtils.isEmpty(this.scripts)) {
for (String script : this.scripts) { for (String script : this.scripts) {

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

@ -169,7 +169,28 @@ public class ScriptTemplateViewTests {
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() -> assertThatIllegalArgumentException().isThrownBy(() ->
this.view.setApplicationContext(this.context)) this.view.setApplicationContext(this.context))
.withMessageContaining("'engine' or 'engineName'"); .withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
}
@Test // gh-23258
public void engineAndEngineSupplierBothDefined() {
ScriptEngine engine = mock(InvocableScriptEngine.class);
this.view.setEngineSupplier(() -> engine);
this.view.setEngine(engine);
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
this.view.setApplicationContext(this.context))
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
}
@Test // gh-23258
public void engineNameAndEngineSupplierBothDefined() {
this.view.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
this.view.setEngineName("test");
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
this.view.setApplicationContext(this.context))
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
} }
@Test @Test
@ -182,6 +203,43 @@ public class ScriptTemplateViewTests {
.withMessageContaining("sharedEngine"); .withMessageContaining("sharedEngine");
} }
@Test // gh-23258
public void engineSupplierWithSharedEngine() {
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
this.configurer.setRenderObject("Template");
this.configurer.setRenderFunction("render");
this.configurer.setSharedEngine(true);
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
this.view.setApplicationContext(this.context);
ScriptEngine engine1 = this.view.getEngine();
ScriptEngine engine2 = this.view.getEngine();
assertThat(engine1).isNotNull();
assertThat(engine2).isNotNull();
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
assertThat(accessor.getPropertyValue("sharedEngine")).isEqualTo(true);
}
@SuppressWarnings("unchecked")
@Test // gh-23258
public void engineSupplierWithNonSharedEngine() {
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
this.configurer.setRenderObject("Template");
this.configurer.setRenderFunction("render");
this.configurer.setSharedEngine(false);
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
this.view.setApplicationContext(this.context);
ScriptEngine engine1 = this.view.getEngine();
ScriptEngine engine2 = this.view.getEngine();
assertThat(engine1).isNotNull();
assertThat(engine2).isNotNull();
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
assertThat(accessor.getPropertyValue("sharedEngine")).isEqualTo(false);
}
private interface InvocableScriptEngine extends ScriptEngine, Invocable { private interface InvocableScriptEngine extends ScriptEngine, Invocable {
} }

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.servlet.view.script; package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
@ -38,6 +39,13 @@ public interface ScriptTemplateConfig {
@Nullable @Nullable
ScriptEngine getEngine(); ScriptEngine getEngine();
/**
* Return the engine supplier that will be used to instantiate the {@link ScriptEngine}.
* @since 5.2
*/
@Nullable
Supplier<ScriptEngine> getEngineSupplier();
/** /**
* Return the engine name that will be used to instantiate the {@link ScriptEngine}. * Return the engine name that will be used to instantiate the {@link ScriptEngine}.
*/ */

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.servlet.view.script; package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
@ -52,6 +53,9 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
@Nullable @Nullable
private ScriptEngine engine; private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable @Nullable
private String engineName; private String engineName;
@ -96,9 +100,11 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
* If {@code renderFunction} is specified, 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. * 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 * <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)} * the script engine with this setter, but with {@link #setEngineName(String)}
* one (since it implies multiple lazy instantiations of the script engine). * or {@link #setEngineSupplier(Supplier)} since it implies multiple lazy
* instantiations of the script engine.
* @see #setEngineName(String) * @see #setEngineName(String)
* @see #setEngineSupplier(Supplier)
*/ */
public void setEngine(@Nullable ScriptEngine engine) { public void setEngine(@Nullable ScriptEngine engine) {
this.engine = engine; this.engine = engine;
@ -110,11 +116,31 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
return this.engine; return this.engine;
} }
/**
* Set the {@link ScriptEngine} supplier to use by the view, usually used with
* {@link #setSharedEngine(Boolean)} set to {@code false}.
* If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
* You must either define {@code engineSupplier}, {@code engine} or {@code engineName}.
* @since 5.2
* @see #setEngine(ScriptEngine)
* @see #setEngineName(String)
*/
public void setEngineSupplier(@Nullable Supplier<ScriptEngine> engineSupplier) {
this.engineSupplier = engineSupplier;
}
@Override
@Nullable
public Supplier<ScriptEngine> getEngineSupplier() {
return this.engineSupplier;
}
/** /**
* Set the engine name that will be used to instantiate the {@link ScriptEngine}. * Set the engine name that will be used to instantiate the {@link ScriptEngine}.
* If {@code renderFunction} is specified, 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. * You must define {@code engine} or {@code engineName}, not both.
* @see #setEngine(ScriptEngine) * @see #setEngine(ScriptEngine)
* @see #setEngineSupplier(Supplier)
*/ */
public void setEngineName(@Nullable String engineName) { public void setEngineName(@Nullable String engineName) {
this.engineName = engineName; this.engineName = engineName;
@ -131,12 +157,10 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
* of one single shared instance. This flag should be set to {@code false} for those * of one single shared instance. This flag should be set to {@code false} for those
* using non thread-safe script engines with templating libraries not designed for * using non thread-safe script engines with templating libraries not designed for
* concurrency, like Handlebars or React running on Nashorn for example. * concurrency, like Handlebars or React running on Nashorn for example.
* In this case, Java 8u60 or greater is required due to
* <a href="https://bugs.openjdk.java.net/browse/JDK-8076099">this bug</a>.
* <p>When this flag is set to {@code false}, the script engine must be specified using * <p>When this flag is set to {@code false}, the script engine must be specified using
* {@link #setEngineName(String)}. Using {@link #setEngine(ScriptEngine)} is not * {@link #setEngineName(String)} or {@link #setEngineSupplier(Supplier)}.
* possible because multiple instances of the script engine need to be created lazily * Using {@link #setEngine(ScriptEngine)} is not possible because multiple instances
* (one per thread). * of the script engine need to be created lazily (one per thread).
* @see <a href="https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html#getParameter-java.lang.String-">THREADING ScriptEngine parameter</a> * @see <a href="https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html#getParameter-java.lang.String-">THREADING ScriptEngine parameter</a>
*/ */
public void setSharedEngine(@Nullable Boolean sharedEngine) { public void setSharedEngine(@Nullable Boolean sharedEngine) {

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import javax.script.Invocable; import javax.script.Invocable;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager; import javax.script.ScriptEngineManager;
@ -88,6 +89,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
@Nullable @Nullable
private ScriptEngine engine; private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable @Nullable
private String engineName; private String engineName;
@ -138,6 +142,13 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.engine = engine; this.engine = engine;
} }
/**
* See {@link ScriptTemplateConfigurer#setEngineSupplier(Supplier)} documentation.
*/
public void setEngineSupplier(Supplier<ScriptEngine> engineSupplier) {
this.engineSupplier = engineSupplier;
}
/** /**
* See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation. * See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation.
*/ */
@ -203,7 +214,10 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
ScriptTemplateConfig viewConfig = autodetectViewConfig(); ScriptTemplateConfig viewConfig = autodetectViewConfig();
if (this.engine == null && viewConfig.getEngine() != null) { if (this.engine == null && viewConfig.getEngine() != null) {
setEngine(viewConfig.getEngine()); this.engine = viewConfig.getEngine();
}
if (this.engineSupplier == null && viewConfig.getEngineSupplier() != null) {
this.engineSupplier = viewConfig.getEngineSupplier();
} }
if (this.engineName == null && viewConfig.getEngineName() != null) { if (this.engineName == null && viewConfig.getEngineName() != null) {
this.engineName = viewConfig.getEngineName(); this.engineName = viewConfig.getEngineName();
@ -231,22 +245,33 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.sharedEngine = viewConfig.isSharedEngine(); this.sharedEngine = viewConfig.isSharedEngine();
} }
Assert.isTrue(!(this.engine != null && this.engineName != null), int engineCount = 0;
"You should define either 'engine' or 'engineName', not both."); if (this.engine != null) {
Assert.isTrue(!(this.engine == null && this.engineName == null), engineCount++;
"No script engine found, please specify either 'engine' or 'engineName'."); }
if (this.engineSupplier != null) {
engineCount++;
}
if (this.engineName != null) {
engineCount++;
}
Assert.isTrue(engineCount == 1,
"You should define either 'engine', 'engineSupplier' or 'engineName'.");
if (Boolean.FALSE.equals(this.sharedEngine)) { if (Boolean.FALSE.equals(this.sharedEngine)) {
Assert.isTrue(this.engineName != null, Assert.isTrue(this.engine == null,
"When 'sharedEngine' is set to false, you should specify the " + "When 'sharedEngine' is set to false, you should specify the " +
"script engine using the 'engineName' property, not the 'engine' one."); "script engine using 'engineName' or 'engineSupplier' , not 'engine'.");
} }
else if (this.engine != null) { else if (this.engine != null) {
loadScripts(this.engine); loadScripts(this.engine);
} }
else { else if (this.engineName != null) {
setEngine(createEngineFromName(this.engineName)); setEngine(createEngineFromName(this.engineName));
} }
else {
setEngine(createEngineFromSupplier());
}
if (this.renderFunction != null && this.engine != null) { if (this.renderFunction != null && this.engine != null) {
Assert.isInstanceOf(Invocable.class, this.engine, Assert.isInstanceOf(Invocable.class, this.engine,
@ -261,12 +286,17 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
engines = new HashMap<>(4); engines = new HashMap<>(4);
enginesHolder.set(engines); enginesHolder.set(engines);
} }
Assert.state(this.engineName != null, "No engine name specified"); String name = (this.engineName != null ? this.engineName : this.engineSupplier.getClass().getSimpleName());
Object engineKey = (!ObjectUtils.isEmpty(this.scripts) ? Object engineKey = (!ObjectUtils.isEmpty(this.scripts) ?
new EngineKey(this.engineName, this.scripts) : this.engineName); new EngineKey(name, this.scripts) : name);
ScriptEngine engine = engines.get(engineKey); ScriptEngine engine = engines.get(engineKey);
if (engine == null) { if (engine == null) {
engine = createEngineFromName(this.engineName); if (this.engineName != null) {
engine = createEngineFromName(this.engineName);
}
else {
engine = createEngineFromSupplier();
}
engines.put(engineKey, engine); engines.put(engineKey, engine);
} }
return engine; return engine;
@ -278,14 +308,21 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
} }
} }
protected ScriptEngine createEngineFromName(String engineName) { protected ScriptEngine createEngineFromName(@Nullable String engineName) {
ScriptEngineManager scriptEngineManager = this.scriptEngineManager; if (this.scriptEngineManager == null) {
if (scriptEngineManager == null) { this.scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
this.scriptEngineManager = scriptEngineManager;
} }
ScriptEngine engine = StandardScriptUtils.retrieveEngineByName(this.scriptEngineManager, engineName);
loadScripts(engine);
return engine;
}
ScriptEngine engine = StandardScriptUtils.retrieveEngineByName(scriptEngineManager, engineName); private ScriptEngine createEngineFromSupplier() {
ScriptEngine engine = this.engineSupplier.get();
if (this.renderFunction != null && engine != null) {
Assert.isInstanceOf(Invocable.class, engine,
"ScriptEngine must implement Invocable when 'renderFunction' is specified");
}
loadScripts(engine); loadScripts(engine);
return engine; return engine;
} }

67
spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java

@ -86,7 +86,7 @@ public class ScriptTemplateViewTests {
} }
@Test @Test
public void missingScriptTemplateConfig() throws Exception { public void missingScriptTemplateConfig() {
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() -> assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() ->
this.view.setApplicationContext(new StaticApplicationContext())) this.view.setApplicationContext(new StaticApplicationContext()))
.withMessageContaining("ScriptTemplateConfig"); .withMessageContaining("ScriptTemplateConfig");
@ -129,7 +129,7 @@ public class ScriptTemplateViewTests {
} }
@Test @Test
public void customEngineAndRenderFunction() throws Exception { public void customEngineAndRenderFunction() {
ScriptEngine engine = mock(InvocableScriptEngine.class); ScriptEngine engine = mock(InvocableScriptEngine.class);
given(engine.get("key")).willReturn("value"); given(engine.get("key")).willReturn("value");
this.view.setEngine(engine); this.view.setEngine(engine);
@ -164,13 +164,13 @@ public class ScriptTemplateViewTests {
} }
@Test @Test
public void nonInvocableScriptEngine() throws Exception { public void nonInvocableScriptEngine() {
this.view.setEngine(mock(ScriptEngine.class)); this.view.setEngine(mock(ScriptEngine.class));
this.view.setApplicationContext(this.wac); this.view.setApplicationContext(this.wac);
} }
@Test @Test
public void nonInvocableScriptEngineWithRenderFunction() throws Exception { public void nonInvocableScriptEngineWithRenderFunction() {
this.view.setEngine(mock(ScriptEngine.class)); this.view.setEngine(mock(ScriptEngine.class));
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() -> assertThatIllegalArgumentException().isThrownBy(() ->
@ -184,7 +184,28 @@ public class ScriptTemplateViewTests {
this.view.setRenderFunction("render"); this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() -> assertThatIllegalArgumentException().isThrownBy(() ->
this.view.setApplicationContext(this.wac)) this.view.setApplicationContext(this.wac))
.withMessageContaining("'engine' or 'engineName'"); .withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
}
@Test // gh-23258
public void engineAndEngineSupplierBothDefined() {
ScriptEngine engine = mock(InvocableScriptEngine.class);
this.view.setEngineSupplier(() -> engine);
this.view.setEngine(engine);
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
this.view.setApplicationContext(this.wac))
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
}
@Test // gh-23258
public void engineNameAndEngineSupplierBothDefined() {
this.view.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
this.view.setEngineName("test");
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
this.view.setApplicationContext(this.wac))
.withMessageContaining("You should define either 'engine', 'engineSupplier' or 'engineName'.");
} }
@Test @Test
@ -261,6 +282,42 @@ public class ScriptTemplateViewTests {
} }
@Test // gh-23258
public void engineSupplierWithSharedEngine() {
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
this.configurer.setRenderObject("Template");
this.configurer.setRenderFunction("render");
this.configurer.setSharedEngine(true);
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
this.view.setApplicationContext(this.wac);
ScriptEngine engine1 = this.view.getEngine();
ScriptEngine engine2 = this.view.getEngine();
assertThat(engine1).isNotNull();
assertThat(engine2).isNotNull();
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
assertThat(accessor.getPropertyValue("sharedEngine")).isEqualTo(true);
}
@SuppressWarnings("unchecked")
@Test // gh-23258
public void engineSupplierWithNonSharedEngine() {
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
this.configurer.setRenderObject("Template");
this.configurer.setRenderFunction("render");
this.configurer.setSharedEngine(false);
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
this.view.setApplicationContext(this.wac);
ScriptEngine engine1 = this.view.getEngine();
ScriptEngine engine2 = this.view.getEngine();
assertThat(engine1).isNotNull();
assertThat(engine2).isNotNull();
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
assertThat(accessor.getPropertyValue("sharedEngine")).isEqualTo(false);
}
private interface InvocableScriptEngine extends ScriptEngine, Invocable { private interface InvocableScriptEngine extends ScriptEngine, Invocable {
} }

Loading…
Cancel
Save