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 8efe9308d7..91cfa2c1e8 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 8591030034..ad82318010 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 98131de775..9fb6f45617 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 fdacfcfd6e..aa1b39773a 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 d4e1d3d89d..aeda2dde06 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 71563ed505..5fa445e5d9 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 3e2242663c..5a2ff4c44f 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 363f95e552..03079a7664 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 { }