diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java index 8efe9308d73..91cfa2c1e8d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfig.java +++ b/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"); * 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 getEngineSupplier(); + /** * Return the engine name that will be used to instantiate the {@link ScriptEngine}. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java index 85910300346..ad823180100 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateConfigurer.java +++ b/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"); * 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 engineSupplier; + @Nullable private String engineName; @@ -94,8 +98,10 @@ public class ScriptTemplateConfigurer implements ScriptTemplateConfig { * You must define {@code engine} or {@code engineName}, not both. *

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 engineSupplier) { + this.engineSupplier = engineSupplier; + } + + @Override + @Nullable + public Supplier 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; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java index 98131de7751..9fb6f456171 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java +++ b/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.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 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 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) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java index fdacfcfd6e2..aa1b39773a4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script/ScriptTemplateViewTests.java +++ b/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"); 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 { } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java index d4e1d3d89dd..aeda2dde061 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfig.java +++ b/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"); * 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 getEngineSupplier(); + /** * Return the engine name that will be used to instantiate the {@link ScriptEngine}. */ diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java index 71563ed505e..5fa445e5d99 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateConfigurer.java +++ b/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"); * 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 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. *

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 engineSupplier) { + this.engineSupplier = engineSupplier; + } + + @Override + @Nullable + public Supplier 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 - * this bug. *

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 THREADING ScriptEngine parameter */ public void setSharedEngine(@Nullable Boolean sharedEngine) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java index 3e2242663cd..5a2ff4c44fa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java +++ b/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"); * 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 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 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; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java index 363f95e5529..03079a76646 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script/ScriptTemplateViewTests.java @@ -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 { }