From c8f8dfa39eefbf6ff2e35d0198ace6bf64dd0f42 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Thu, 11 Jul 2019 15:48:18 +0200 Subject: [PATCH] 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 --- .../view/script/ScriptTemplateConfig.java | 10 ++- .../view/script/ScriptTemplateConfigurer.java | 30 +++++++- .../view/script/ScriptTemplateView.java | 59 ++++++++++++--- .../view/script/ScriptTemplateViewTests.java | 60 ++++++++++++++- .../view/script/ScriptTemplateConfig.java | 10 ++- .../view/script/ScriptTemplateConfigurer.java | 40 ++++++++-- .../view/script/ScriptTemplateView.java | 73 ++++++++++++++----- .../view/script/ScriptTemplateViewTests.java | 67 +++++++++++++++-- 8 files changed, 303 insertions(+), 46 deletions(-) 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 { }