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
This commit is contained in:
Sebastien Deleuze 2019-07-11 15:48:18 +02:00
parent 01125a571c
commit c8f8dfa39e
8 changed files with 303 additions and 46 deletions

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings;
import javax.script.ScriptEngine;
@ -38,6 +39,13 @@ public interface ScriptTemplateConfig {
@Nullable
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}.
*/

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings;
import javax.script.ScriptEngine;
@ -52,6 +53,9 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
@Nullable
private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable
private String engineName;
@ -94,8 +98,10 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
* 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)}
* 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 #setEngineSupplier(Supplier)
*/
public void setEngine(@Nullable ScriptEngine engine) {
this.engine = engine;
@ -107,11 +113,31 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
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}.
* 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)
* @see #setEngineSupplier(Supplier)
*/
public void setEngineName(@Nullable String engineName) {
this.engineName = engineName;

View File

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

View File

@ -169,7 +169,28 @@ public class ScriptTemplateViewTests {
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
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
@ -182,6 +203,43 @@ public class ScriptTemplateViewTests {
.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 {
}

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings;
import javax.script.ScriptEngine;
@ -38,6 +39,13 @@ public interface ScriptTemplateConfig {
@Nullable
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}.
*/

View File

@ -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");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.web.servlet.view.script;
import java.nio.charset.Charset;
import java.util.function.Supplier;
import javax.script.Bindings;
import javax.script.ScriptEngine;
@ -52,6 +53,9 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
@Nullable
private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable
private String engineName;
@ -96,9 +100,11 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
* 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)}
* one (since it implies multiple lazy instantiations of the script engine).
* the script engine with this setter, but with {@link #setEngineName(String)}
* or {@link #setEngineSupplier(Supplier)} since it implies multiple lazy
* instantiations of the script engine.
* @see #setEngineName(String)
* @see #setEngineSupplier(Supplier)
*/
public void setEngine(@Nullable ScriptEngine engine) {
this.engine = engine;
@ -110,11 +116,31 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
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}.
* 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)
* @see #setEngineSupplier(Supplier)
*/
public void setEngineName(@Nullable String 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
* using non thread-safe script engines with templating libraries not designed for
* 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
* {@link #setEngineName(String)}. Using {@link #setEngine(ScriptEngine)} is not
* possible because multiple instances of the script engine need to be created lazily
* (one per thread).
* {@link #setEngineName(String)} or {@link #setEngineSupplier(Supplier)}.
* Using {@link #setEngine(ScriptEngine)} is not possible because multiple instances
* 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>
*/
public void setSharedEngine(@Nullable Boolean sharedEngine) {

View File

@ -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");
* 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.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
@ -88,6 +89,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
@Nullable
private ScriptEngine engine;
@Nullable
private Supplier<ScriptEngine> engineSupplier;
@Nullable
private String engineName;
@ -138,6 +142,13 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.engine = engine;
}
/**
* See {@link ScriptTemplateConfigurer#setEngineSupplier(Supplier)} documentation.
*/
public void setEngineSupplier(Supplier<ScriptEngine> engineSupplier) {
this.engineSupplier = engineSupplier;
}
/**
* See {@link ScriptTemplateConfigurer#setEngineName(String)} documentation.
*/
@ -203,7 +214,10 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
ScriptTemplateConfig viewConfig = autodetectViewConfig();
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) {
this.engineName = viewConfig.getEngineName();
@ -231,22 +245,33 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
this.sharedEngine = viewConfig.isSharedEngine();
}
Assert.isTrue(!(this.engine != null && this.engineName != null),
"You should define either 'engine' or 'engineName', not both.");
Assert.isTrue(!(this.engine == null && this.engineName == null),
"No script engine found, please specify either 'engine' or 'engineName'.");
int engineCount = 0;
if (this.engine != null) {
engineCount++;
}
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)) {
Assert.isTrue(this.engineName != null,
Assert.isTrue(this.engine == null,
"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) {
loadScripts(this.engine);
}
else {
else if (this.engineName != null) {
setEngine(createEngineFromName(this.engineName));
}
else {
setEngine(createEngineFromSupplier());
}
if (this.renderFunction != null && this.engine != null) {
Assert.isInstanceOf(Invocable.class, this.engine,
@ -261,12 +286,17 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
engines = new HashMap<>(4);
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) ?
new EngineKey(this.engineName, this.scripts) : this.engineName);
new EngineKey(name, this.scripts) : name);
ScriptEngine engine = engines.get(engineKey);
if (engine == null) {
engine = createEngineFromName(this.engineName);
if (this.engineName != null) {
engine = createEngineFromName(this.engineName);
}
else {
engine = createEngineFromSupplier();
}
engines.put(engineKey, engine);
}
return engine;
@ -278,14 +308,21 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
}
protected ScriptEngine createEngineFromName(String engineName) {
ScriptEngineManager scriptEngineManager = this.scriptEngineManager;
if (scriptEngineManager == null) {
scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
this.scriptEngineManager = scriptEngineManager;
protected ScriptEngine createEngineFromName(@Nullable String engineName) {
if (this.scriptEngineManager == null) {
this.scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
}
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);
return engine;
}

View File

@ -86,7 +86,7 @@ public class ScriptTemplateViewTests {
}
@Test
public void missingScriptTemplateConfig() throws Exception {
public void missingScriptTemplateConfig() {
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() ->
this.view.setApplicationContext(new StaticApplicationContext()))
.withMessageContaining("ScriptTemplateConfig");
@ -129,7 +129,7 @@ public class ScriptTemplateViewTests {
}
@Test
public void customEngineAndRenderFunction() throws Exception {
public void customEngineAndRenderFunction() {
ScriptEngine engine = mock(InvocableScriptEngine.class);
given(engine.get("key")).willReturn("value");
this.view.setEngine(engine);
@ -164,13 +164,13 @@ public class ScriptTemplateViewTests {
}
@Test
public void nonInvocableScriptEngine() throws Exception {
public void nonInvocableScriptEngine() {
this.view.setEngine(mock(ScriptEngine.class));
this.view.setApplicationContext(this.wac);
}
@Test
public void nonInvocableScriptEngineWithRenderFunction() throws Exception {
public void nonInvocableScriptEngineWithRenderFunction() {
this.view.setEngine(mock(ScriptEngine.class));
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
@ -184,7 +184,28 @@ public class ScriptTemplateViewTests {
this.view.setRenderFunction("render");
assertThatIllegalArgumentException().isThrownBy(() ->
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
@ -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 {
}