001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.view.script;
018
019import java.nio.charset.Charset;
020import java.util.function.Supplier;
021
022import javax.script.Bindings;
023import javax.script.ScriptEngine;
024
025import org.springframework.lang.Nullable;
026
027/**
028 * An implementation of Spring MVC's {@link ScriptTemplateConfig} for creating
029 * a {@code ScriptEngine} for use in a web application.
030 *
031 * <pre class="code">
032 * // Add the following to an &#64;Configuration class
033 * &#64;Bean
034 * public ScriptTemplateConfigurer mustacheConfigurer() {
035 *    ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
036 *    configurer.setEngineName("nashorn");
037 *    configurer.setScripts("mustache.js");
038 *    configurer.setRenderObject("Mustache");
039 *    configurer.setRenderFunction("render");
040 *    return configurer;
041 * }
042 * </pre>
043 *
044 * <p><b>NOTE:</b> It is possible to use non thread-safe script engines with
045 * templating libraries not designed for concurrency, like Handlebars or React running on
046 * Nashorn, by setting the {@link #setSharedEngine sharedEngine} property to {@code false}.
047 *
048 * @author Sebastien Deleuze
049 * @since 4.2
050 * @see ScriptTemplateView
051 */
052public class ScriptTemplateConfigurer implements ScriptTemplateConfig {
053
054        @Nullable
055        private ScriptEngine engine;
056
057        @Nullable
058        private Supplier<ScriptEngine> engineSupplier;
059
060        @Nullable
061        private String engineName;
062
063        @Nullable
064        private Boolean sharedEngine;
065
066        @Nullable
067        private String[] scripts;
068
069        @Nullable
070        private String renderObject;
071
072        @Nullable
073        private String renderFunction;
074
075        @Nullable
076        private String contentType;
077
078        @Nullable
079        private Charset charset;
080
081        @Nullable
082        private String resourceLoaderPath;
083
084
085        /**
086         * Default constructor.
087         */
088        public ScriptTemplateConfigurer() {
089        }
090
091        /**
092         * Create a new ScriptTemplateConfigurer using the given engine name.
093         */
094        public ScriptTemplateConfigurer(String engineName) {
095                this.engineName = engineName;
096        }
097
098
099        /**
100         * Set the {@link ScriptEngine} to use by the view.
101         * If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
102         * You must define {@code engine} or {@code engineName}, not both.
103         * <p>When the {@code sharedEngine} flag is set to {@code false}, you should not specify
104         * the script engine with this setter, but with {@link #setEngineName(String)}
105         * or {@link #setEngineSupplier(Supplier)} since it implies multiple lazy
106         * instantiations of the script engine.
107         * @see #setEngineName(String)
108         * @see #setEngineSupplier(Supplier)
109         */
110        public void setEngine(@Nullable ScriptEngine engine) {
111                this.engine = engine;
112        }
113
114        @Override
115        @Nullable
116        public ScriptEngine getEngine() {
117                return this.engine;
118        }
119
120        /**
121         * Set the {@link ScriptEngine} supplier to use by the view, usually used with
122         * {@link #setSharedEngine(Boolean)} set to {@code false}.
123         * If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
124         * You must either define {@code engineSupplier}, {@code engine} or {@code engineName}.
125         * @since 5.2
126         * @see #setEngine(ScriptEngine)
127         * @see #setEngineName(String)
128         */
129        public void setEngineSupplier(@Nullable Supplier<ScriptEngine> engineSupplier) {
130                this.engineSupplier = engineSupplier;
131        }
132
133        @Override
134        @Nullable
135        public Supplier<ScriptEngine> getEngineSupplier() {
136                return this.engineSupplier;
137        }
138
139        /**
140         * Set the engine name that will be used to instantiate the {@link ScriptEngine}.
141         * If {@code renderFunction} is specified, the script engine must implement {@code Invocable}.
142         * You must define {@code engine} or {@code engineName}, not both.
143         * @see #setEngine(ScriptEngine)
144         * @see #setEngineSupplier(Supplier)
145         */
146        public void setEngineName(@Nullable String engineName) {
147                this.engineName = engineName;
148        }
149
150        @Override
151        @Nullable
152        public String getEngineName() {
153                return this.engineName;
154        }
155
156        /**
157         * When set to {@code false}, use thread-local {@link ScriptEngine} instances instead
158         * of one single shared instance. This flag should be set to {@code false} for those
159         * using non thread-safe script engines with templating libraries not designed for
160         * concurrency, like Handlebars or React running on Nashorn for example.
161         * <p>When this flag is set to {@code false}, the script engine must be specified using
162         * {@link #setEngineName(String)} or {@link #setEngineSupplier(Supplier)}.
163         * Using {@link #setEngine(ScriptEngine)} is not possible because multiple instances
164         * of the script engine need to be created lazily (one per thread).
165         * @see <a href="https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html#getParameter-java.lang.String-">THREADING ScriptEngine parameter</a>
166         */
167        public void setSharedEngine(@Nullable Boolean sharedEngine) {
168                this.sharedEngine = sharedEngine;
169        }
170
171        @Override
172        @Nullable
173        public Boolean isSharedEngine() {
174                return this.sharedEngine;
175        }
176
177        /**
178         * Set the scripts to be loaded by the script engine (library or user provided).
179         * Since {@code resourceLoaderPath} default value is "classpath:", you can load easily
180         * any script available on the classpath.
181         * <p>For example, in order to use a JavaScript library available as a WebJars dependency
182         * and a custom "render.js" file, you should call
183         * {@code configurer.setScripts("/META-INF/resources/webjars/library/version/library.js",
184         * "com/myproject/script/render.js");}.
185         * @see #setResourceLoaderPath
186         * @see <a href="https://www.webjars.org">WebJars</a>
187         */
188        public void setScripts(@Nullable String... scriptNames) {
189                this.scripts = scriptNames;
190        }
191
192        @Override
193        @Nullable
194        public String[] getScripts() {
195                return this.scripts;
196        }
197
198        /**
199         * Set the object where the render function belongs (optional).
200         * For example, in order to call {@code Mustache.render()}, {@code renderObject}
201         * should be set to {@code "Mustache"} and {@code renderFunction} to {@code "render"}.
202         */
203        public void setRenderObject(@Nullable String renderObject) {
204                this.renderObject = renderObject;
205        }
206
207        @Override
208        @Nullable
209        public String getRenderObject() {
210                return this.renderObject;
211        }
212
213        /**
214         * Set the render function name (optional). If not specified, the script templates
215         * will be evaluated with {@link ScriptEngine#eval(String, Bindings)}.
216         * <p>This function will be called with the following parameters:
217         * <ol>
218         * <li>{@code String template}: the template content</li>
219         * <li>{@code Map model}: the view model</li>
220         * <li>{@code RenderingContext context}: the rendering context (since 5.0)</li>
221         * </ol>
222         * @see RenderingContext
223         */
224        public void setRenderFunction(@Nullable String renderFunction) {
225                this.renderFunction = renderFunction;
226        }
227
228        @Override
229        @Nullable
230        public String getRenderFunction() {
231                return this.renderFunction;
232        }
233
234        /**
235         * Set the content type to use for the response.
236         * ({@code text/html} by default).
237         * @since 4.2.1
238         */
239        public void setContentType(@Nullable String contentType) {
240                this.contentType = contentType;
241        }
242
243        /**
244         * Return the content type to use for the response.
245         * @since 4.2.1
246         */
247        @Override
248        @Nullable
249        public String getContentType() {
250                return this.contentType;
251        }
252
253        /**
254         * Set the charset used to read script and template files.
255         * ({@code UTF-8} by default).
256         */
257        public void setCharset(@Nullable Charset charset) {
258                this.charset = charset;
259        }
260
261        @Override
262        @Nullable
263        public Charset getCharset() {
264                return this.charset;
265        }
266
267        /**
268         * Set the resource loader path(s) via a Spring resource location.
269         * Accepts multiple locations as a comma-separated list of paths.
270         * Standard URLs like "file:" and "classpath:" and pseudo URLs are supported
271         * as understood by Spring's {@link org.springframework.core.io.ResourceLoader}.
272         * Relative paths are allowed when running in an ApplicationContext.
273         * <p>Default is "classpath:".
274         */
275        public void setResourceLoaderPath(@Nullable String resourceLoaderPath) {
276                this.resourceLoaderPath = resourceLoaderPath;
277        }
278
279        @Override
280        @Nullable
281        public String getResourceLoaderPath() {
282                return this.resourceLoaderPath;
283        }
284
285}