Add script based templating support
This commit adds support for script based templating. Any templating library running on top of a JSR-223 ScriptEngine that implements Invocable like Nashorn or JRuby could be used. For example, in order to render Mustache templates thanks to the Nashorn Javascript engine provided with Java 8+, you should declare the following configuration: @Configuration @EnableWebMvc public class MustacheConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.scriptTemplate(); } @Bean public ScriptTemplateConfigurer configurer() { ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer(); configurer.setEngineName("nashorn"); configurer.setScripts("mustache.js"); configurer.setRenderObject("Mustache"); configurer.setRenderFunction("render"); return configurer; } } The XML counterpart is: <beans> <mvc:annotation-driven /> <mvc:view-resolvers> <mvc:script-template /> </mvc:view-resolvers> <mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render"> <mvc:script location="mustache.js" /> </mvc:script-template-configurer> </beans> Tested with: - Handlebars running on Nashorn - Mustache running on Nashorn - React running on Nashorn - EJS running on Nashorn - ERB running on JRuby - String templates running on Jython Issue: SPR-12266
This commit is contained in:
parent
b6327acec8
commit
a3159dfbf2
|
@ -47,6 +47,7 @@ configure(allprojects) { project ->
|
|||
ext.javamailVersion = "1.5.3"
|
||||
ext.jettyVersion = "9.2.10.v20150310"
|
||||
ext.jodaVersion = "2.7"
|
||||
ext.jrubyVersion = "1.7.19"
|
||||
ext.jtaVersion = "1.2"
|
||||
ext.junitVersion = "4.12"
|
||||
ext.nettyVersion = "4.0.26.Final"
|
||||
|
@ -469,7 +470,7 @@ project("spring-context") {
|
|||
optional("org.aspectj:aspectjweaver:${aspectjVersion}")
|
||||
optional("org.codehaus.groovy:groovy-all:${groovyVersion}")
|
||||
optional("org.beanshell:bsh:2.0b4")
|
||||
optional("org.jruby:jruby:1.7.19")
|
||||
optional("org.jruby:jruby:${jrubyVersion}")
|
||||
testCompile("javax.inject:javax.inject-tck:1")
|
||||
testCompile("org.javamoney:moneta:1.0-RC3")
|
||||
testCompile("commons-dbcp:commons-dbcp:1.4")
|
||||
|
@ -898,6 +899,12 @@ project("spring-webmvc") {
|
|||
testCompile("commons-io:commons-io:1.3")
|
||||
testCompile("joda-time:joda-time:${jodaVersion}")
|
||||
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
|
||||
testCompile("org.webjars:mustachejs:0.8.2")
|
||||
testCompile("org.webjars:handlebars:3.0.0-1")
|
||||
testCompile("org.webjars:react:0.12.2")
|
||||
testCompile("org.webjars:underscorejs:1.8.2")
|
||||
testCompile("org.jruby:jruby:${jrubyVersion}")
|
||||
testCompile("org.python:jython-standalone:2.5.3")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -24,6 +24,7 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
|
|||
*
|
||||
* @author Keith Donald
|
||||
* @author Jeremy Grelle
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.0
|
||||
*/
|
||||
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
@ -42,6 +43,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport {
|
|||
registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.config;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
|
||||
/**
|
||||
* Parse the <mvc:script-template-configurer> MVC namespace element and register a
|
||||
* {@code ScriptTemplateConfigurer} bean.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
*/
|
||||
public class ScriptTemplateConfigurerBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
|
||||
|
||||
public static final String BEAN_NAME = "mvcScriptTemplateConfigurer";
|
||||
|
||||
|
||||
@Override
|
||||
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
|
||||
return BEAN_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBeanClassName(Element element) {
|
||||
return "org.springframework.web.servlet.view.script.ScriptTemplateConfigurer";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "script");
|
||||
if (!childElements.isEmpty()) {
|
||||
List<String> locations = new ArrayList<String>(childElements.size());
|
||||
for (Element childElement : childElements) {
|
||||
locations.add(childElement.getAttribute("location"));
|
||||
}
|
||||
builder.addPropertyValue("scripts", locations.toArray(new String[locations.size()]));
|
||||
}
|
||||
builder.addPropertyValue("engineName", element.getAttribute("engine-name"));
|
||||
if (element.hasAttribute("render-object")) {
|
||||
builder.addPropertyValue("renderObject", element.getAttribute("render-object"));
|
||||
}
|
||||
if (element.hasAttribute("render-function")) {
|
||||
builder.addPropertyValue("renderFunction", element.getAttribute("render-function"));
|
||||
}
|
||||
if (element.hasAttribute("charset")) {
|
||||
builder.addPropertyValue("charset", Charset.forName(element.getAttribute("charset")));
|
||||
}
|
||||
if (element.hasAttribute("resource-loader-path")) {
|
||||
builder.addPropertyValue("resourceLoaderPath", element.getAttribute("resource-loader-path"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEligibleAttribute(String name) {
|
||||
return (name.equals("engine-name") || name.equals("scripts") || name.equals("render-object") ||
|
||||
name.equals("render-function") || name.equals("charset") || name.equals("resource-loader-path"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -37,6 +37,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
|||
import org.springframework.web.servlet.view.ViewResolverComposite;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
|
||||
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
|
||||
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
|
||||
|
||||
|
@ -60,6 +61,8 @@ import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
|
|||
* @see TilesConfigurerBeanDefinitionParser
|
||||
* @see FreeMarkerConfigurerBeanDefinitionParser
|
||||
* @see VelocityConfigurerBeanDefinitionParser
|
||||
* @see GroovyMarkupConfigurerBeanDefinitionParser
|
||||
* @see ScriptTemplateConfigurerBeanDefinitionParser
|
||||
*/
|
||||
public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
|
@ -72,7 +75,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
ManagedList<Object> resolvers = new ManagedList<Object>(4);
|
||||
resolvers.setSource(context.extractSource(element));
|
||||
String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy", "bean", "ref"};
|
||||
String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy", "script-template", "bean", "ref"};
|
||||
|
||||
for (Element resolverElement : DomUtils.getChildElementsByTagName(element, names)) {
|
||||
String name = resolverElement.getLocalName();
|
||||
|
@ -106,6 +109,10 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser {
|
|||
resolverBeanDef.getPropertyValues().add("suffix", ".tpl");
|
||||
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
|
||||
}
|
||||
else if ("script-template".equals(name)) {
|
||||
resolverBeanDef = new RootBeanDefinition(ScriptTemplateViewResolver.class);
|
||||
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
|
||||
}
|
||||
else if ("bean-name".equals(name)) {
|
||||
resolverBeanDef = new RootBeanDefinition(BeanNameViewResolver.class);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -37,6 +37,8 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
|
|||
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
|
||||
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
|
||||
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
|
||||
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
|
||||
|
@ -233,6 +235,22 @@ public class ViewResolverRegistry {
|
|||
return registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a script template view resolver with an empty default view name prefix and suffix.
|
||||
* @since 4.2
|
||||
*/
|
||||
public UrlBasedViewResolverRegistration scriptTemplate() {
|
||||
if (this.applicationContext != null && !hasBeanOfType(ScriptTemplateConfigurer.class)) {
|
||||
throw new BeanInitializationException("In addition to a script template view resolver " +
|
||||
"there must also be a single ScriptTemplateConfig bean in this web application context " +
|
||||
"(or its parent): ScriptTemplateConfigurer is the usual implementation. " +
|
||||
"This bean may be given any name.");
|
||||
}
|
||||
ScriptRegistration registration = new ScriptRegistration();
|
||||
this.viewResolvers.add(registration.getViewResolver());
|
||||
return registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bean name view resolver that interprets view names as the names
|
||||
* of {@link org.springframework.web.servlet.View} beans.
|
||||
|
@ -324,4 +342,12 @@ public class ViewResolverRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
private static class ScriptRegistration extends UrlBasedViewResolverRegistration {
|
||||
|
||||
private ScriptRegistration() {
|
||||
super(new ScriptTemplateViewResolver());
|
||||
getViewResolver();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by objects that configure and manage a
|
||||
* {@link ScriptEngine} for automatic lookup in a web environment.
|
||||
* Detected and used by {@link ScriptTemplateView}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface ScriptTemplateConfig {
|
||||
|
||||
ScriptEngine getEngine();
|
||||
|
||||
String getRenderObject();
|
||||
|
||||
String getRenderFunction();
|
||||
|
||||
Charset getCharset();
|
||||
|
||||
ResourceLoader getResourceLoader();
|
||||
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of Spring MVC's {@link ScriptTemplateConfig} for creating
|
||||
* a {@code ScriptEngine} for use in a web application.
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* // Add the following to an @Configuration class
|
||||
*
|
||||
* @Bean
|
||||
* public ScriptTemplateConfigurer mustacheConfigurer() {
|
||||
*
|
||||
* ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
* configurer.setEngineName("nashorn");
|
||||
* configurer.setScripts("mustache.js");
|
||||
* configurer.setRenderObject("Mustache");
|
||||
* configurer.setRenderFunction("render");
|
||||
* return configurer;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
* @see ScriptTemplateView
|
||||
*/
|
||||
public class ScriptTemplateConfigurer implements ScriptTemplateConfig, ApplicationContextAware, InitializingBean {
|
||||
|
||||
private ScriptEngine engine;
|
||||
|
||||
private String engineName;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private String[] scripts;
|
||||
|
||||
private String renderObject;
|
||||
|
||||
private String renderFunction;
|
||||
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
private String resourceLoaderPath = "classpath:";
|
||||
|
||||
/**
|
||||
* Set the {@link ScriptEngine} to use by the view.
|
||||
* The script engine must implement {@code Invocable}.
|
||||
* You must define {@code engine} or {@code engineName}, not both.
|
||||
*/
|
||||
public void setEngine(ScriptEngine engine) {
|
||||
Assert.isInstanceOf(Invocable.class, engine);
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptEngine getEngine() {
|
||||
return this.engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the engine name that will be used to instantiate the {@link ScriptEngine}.
|
||||
* The script engine must implement {@code Invocable}.
|
||||
* You must define {@code engine} or {@code engineName}, not both.
|
||||
*/
|
||||
public void setEngineName(String engineName) {
|
||||
this.engineName = engineName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
protected ApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scripts to be loaded by the script engine (library or user provided).
|
||||
* Since {@code resourceLoaderPath} default value is "classpath:", you can load easily
|
||||
* any script available on the classpath.
|
||||
*
|
||||
* For example, in order to use a Javascript library available as a WebJars dependency
|
||||
* and a custom "render.js" file, you should call
|
||||
* {@code configurer.setScripts("/META-INF/resources/webjars/library/version/library.js",
|
||||
* "com/myproject/script/render.js");}.
|
||||
*
|
||||
* @see #setResourceLoaderPath(String)
|
||||
* @see <a href="http://www.webjars.org">WebJars</a>
|
||||
*/
|
||||
public void setScripts(String... scriptNames) {
|
||||
this.scripts = scriptNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRenderObject() {
|
||||
return renderObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object where belongs the render function (optional).
|
||||
* For example, in order to call {@code Mustache.render()}, {@code renderObject}
|
||||
* should be set to {@code "Mustache"} and {@code renderFunction} to {@code "render"}.
|
||||
*/
|
||||
public void setRenderObject(String renderObject) {
|
||||
this.renderObject = renderObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRenderFunction() {
|
||||
return renderFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the render function name (mandatory). This function will be called with the
|
||||
* following parameters:
|
||||
* <ol>
|
||||
* <li>{@code template}: the view template content (String)</li>
|
||||
* <li>{@code model}: the view model (Map)</li>
|
||||
* </ol>
|
||||
*/
|
||||
public void setRenderFunction(String renderFunction) {
|
||||
this.renderFunction = renderFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the charset used to read script and template files.
|
||||
* ({@code UTF-8} by default).
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Charset getCharset() {
|
||||
return this.charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource loader path(s) via a Spring resource location.
|
||||
* Accepts multiple locations as a comma-separated list of paths.
|
||||
* Standard URLs like "file:" and "classpath:" and pseudo URLs are supported
|
||||
* as understood by Spring's {@link org.springframework.core.io.ResourceLoader}.
|
||||
* Relative paths are allowed when running in an ApplicationContext.
|
||||
* Default is "classpath:".
|
||||
*/
|
||||
public void setResourceLoaderPath(String resourceLoaderPath) {
|
||||
this.resourceLoaderPath = resourceLoaderPath;
|
||||
}
|
||||
|
||||
public String getResourceLoaderPath() {
|
||||
return resourceLoaderPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLoader getResourceLoader() {
|
||||
return resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (this.engine == null) {
|
||||
this.engine = createScriptEngine();
|
||||
}
|
||||
Assert.state(this.renderFunction != null, "renderFunction property must be defined.");
|
||||
this.resourceLoader = new DefaultResourceLoader(createClassLoader());
|
||||
if (this.scripts != null) {
|
||||
try {
|
||||
for (String script : this.scripts) {
|
||||
this.engine.eval(read(script));
|
||||
}
|
||||
}
|
||||
catch (ScriptException e) {
|
||||
throw new IllegalStateException("could not load script", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ClassLoader createClassLoader() throws IOException {
|
||||
String[] paths = StringUtils.commaDelimitedListToStringArray(this.resourceLoaderPath);
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
for (String path : paths) {
|
||||
Resource[] resources = getApplicationContext().getResources(path);
|
||||
if (resources.length > 0) {
|
||||
for (Resource resource : resources) {
|
||||
if (resource.exists()) {
|
||||
urls.add(resource.getURL());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ClassLoader classLoader = getApplicationContext().getClassLoader();
|
||||
return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader);
|
||||
}
|
||||
|
||||
private Reader read(String path) throws IOException {
|
||||
Resource resource = this.resourceLoader.getResource(path);
|
||||
Assert.state(resource.exists(), "Resource " + path + " not found.");
|
||||
return new InputStreamReader(resource.getInputStream());
|
||||
}
|
||||
|
||||
protected ScriptEngine createScriptEngine() throws IOException {
|
||||
if (this.engine != null && this.engineName != null) {
|
||||
throw new IllegalStateException("You should define engine or engineName properties, not both.");
|
||||
}
|
||||
if (this.engineName != null) {
|
||||
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName(this.engineName);
|
||||
Assert.state(scriptEngine != null, "No engine \"" + this.engineName + "\" found.");
|
||||
Assert.state(scriptEngine instanceof Invocable, "Script engine should be instance of Invocable");
|
||||
this.engine = scriptEngine;
|
||||
}
|
||||
Assert.state(this.engine != null, "No script engine found, please specify valid engine or engineName properties.");
|
||||
return this.engine;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.servlet.view.AbstractUrlBasedView;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.web.servlet.view.AbstractUrlBasedView AbstractUrlBasedView}
|
||||
* designed to run any template library based on a JSR-223 script engine.
|
||||
*
|
||||
* <p>Nashorn Javascript engine requires Java 8+.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
* @see ScriptTemplateConfigurer
|
||||
* @see ScriptTemplateViewResolver
|
||||
*/
|
||||
public class ScriptTemplateView extends AbstractUrlBasedView {
|
||||
|
||||
private ScriptEngine engine;
|
||||
|
||||
private String renderObject;
|
||||
|
||||
private String renderFunction;
|
||||
|
||||
private Charset charset;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Set the {@link ScriptEngine} to use in this view.
|
||||
* <p>If not set, the engine is auto-detected by looking up up a single
|
||||
* {@link ScriptTemplateConfig} bean in the web application context and using
|
||||
* it to obtain the configured {@code ScriptEngine} instance.
|
||||
* @see ScriptTemplateConfig
|
||||
*/
|
||||
public void setEngine(ScriptEngine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the render function name. This function will be called with the
|
||||
* following parameters:
|
||||
* <ol>
|
||||
* <li>{@code template}: the view template content (String)</li>
|
||||
* <li>{@code model}: the view model (Map)</li>
|
||||
* </ol>
|
||||
* <p>If not set, the function name is auto-detected by looking up up a single
|
||||
* {@link ScriptTemplateConfig} bean in the web application context and using
|
||||
* it to obtain the configured {@code functionName} property.
|
||||
* @see ScriptTemplateConfig
|
||||
*/
|
||||
public void setRenderFunction(String functionName) {
|
||||
this.renderFunction = functionName;
|
||||
}
|
||||
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initApplicationContext(ApplicationContext context) {
|
||||
super.initApplicationContext(context);
|
||||
ScriptTemplateConfig viewConfig = autodetectViewConfig();
|
||||
if (this.engine == null) {
|
||||
this.engine = viewConfig.getEngine();
|
||||
Assert.state(this.engine != null, "Script engine should not be null.");
|
||||
Assert.state(this.engine instanceof Invocable, "Script engine should be instance of Invocable");
|
||||
}
|
||||
if (this.resourceLoader == null) {
|
||||
this.resourceLoader = viewConfig.getResourceLoader();
|
||||
}
|
||||
if (this.renderObject == null) {
|
||||
this.renderObject = viewConfig.getRenderObject();
|
||||
}
|
||||
if (this.renderFunction == null) {
|
||||
this.renderFunction = viewConfig.getRenderFunction();
|
||||
}
|
||||
if (this.charset == null) {
|
||||
this.charset = viewConfig.getCharset();
|
||||
}
|
||||
}
|
||||
|
||||
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
|
||||
try {
|
||||
return BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(), ScriptTemplateConfig.class, true, false);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
throw new ApplicationContextException("Expected a single ScriptTemplateConfig bean in the current " +
|
||||
"Servlet web application context or the parent root context: ScriptTemplateConfigurer is " +
|
||||
"the usual implementation. This bean may have any name.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
Assert.notNull("Render function must not be null", this.renderFunction);
|
||||
try {
|
||||
String template = getTemplate(getUrl());
|
||||
Object html = null;
|
||||
if (this.renderObject != null) {
|
||||
Object thiz = engine.eval(this.renderObject);
|
||||
html = ((Invocable)this.engine).invokeMethod(thiz, this.renderFunction, template, model);
|
||||
}
|
||||
else {
|
||||
html = ((Invocable)this.engine).invokeFunction(this.renderFunction, template, model);
|
||||
}
|
||||
response.getWriter().write(String.valueOf(html));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("failed to render template", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getTemplate(String path) throws IOException {
|
||||
Resource resource = this.resourceLoader.getResource(path);
|
||||
Assert.state(resource.exists(), "Resource " + path + " not found.");
|
||||
return StreamUtils.copyToString(resource.getInputStream(), this.charset);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import org.springframework.web.servlet.view.UrlBasedViewResolver;
|
||||
|
||||
/**
|
||||
* Convenience subclass of
|
||||
* {@link org.springframework.web.servlet.view.UrlBasedViewResolver}
|
||||
* that supports {@link ScriptTemplateView} and custom subclasses of it.
|
||||
*
|
||||
* <p>The view class for all views created by this resolver can be specified
|
||||
* via the {@link #setViewClass(Class)} property.
|
||||
*
|
||||
* <p><b>Note:</b> When chaining ViewResolvers this resolver will check for the
|
||||
* existence of the specified template resources and only return a non-null
|
||||
* View object if a template is actually found.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
* @see ScriptTemplateConfigurer
|
||||
*/
|
||||
public class ScriptTemplateViewResolver extends UrlBasedViewResolver {
|
||||
|
||||
public ScriptTemplateViewResolver() {
|
||||
setViewClass(requiredViewClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> requiredViewClass() {
|
||||
return ScriptTemplateView.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -973,7 +973,7 @@
|
|||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Register a TilesViewResolver based on Tiles 3.x.
|
||||
To configure Tiles you must also add a top-level <mvc:tiles> element
|
||||
To configure Tiles you must also add a top-level <mvc:tiles-configurer> element
|
||||
or declare a TilesConfigurer bean.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
@ -983,7 +983,7 @@
|
|||
<xsd:documentation><![CDATA[
|
||||
Register a FreeMarkerViewResolver.
|
||||
By default, ".ftl" is configured as a view name suffix.
|
||||
To configure FreeMarker you must also add a top-level <mvc:freemarker> element
|
||||
To configure FreeMarker you must also add a top-level <mvc:freemarker-configurer> element
|
||||
or declare a FreeMarkerConfigurer bean.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
@ -993,7 +993,7 @@
|
|||
<xsd:documentation><![CDATA[
|
||||
Register a VelocityViewResolver.
|
||||
By default, ".vm" is configured as a view name suffix.
|
||||
To configure Velocity you must also add a top-level <mvc:velocity> element
|
||||
To configure Velocity you must also add a top-level <mvc:velocity-configurer> element
|
||||
or declare a VelocityConfigurer bean.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
@ -1003,11 +1003,20 @@
|
|||
<xsd:documentation><![CDATA[
|
||||
Register a GroovyMarkupViewResolver.
|
||||
By default, ".tpl" is configured as a view name suffix.
|
||||
To configure the Groovy markup template engine you must also add a top-level <mvc:groovy-markup> element
|
||||
To configure the Groovy markup template engine you must also add a top-level <mvc:groovy-configurer> element
|
||||
or declare a GroovyMarkupConfigurer bean.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="script-template" type="urlViewResolverType">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Register a ScriptTemplateViewResolver.
|
||||
To configure the Script engine you must also add a top-level <mvc:script-template-configurer> element
|
||||
or declare a ScriptTemplateConfigurer bean.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="bean-name" maxOccurs="1">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
|
@ -1163,4 +1172,66 @@
|
|||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="script-template-configurer">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Configure the script engine for view resolution by registering a ScriptTemplateConfigurer
|
||||
bean. This is a shortcut alternative to declaring a ScriptTemplateConfigurer bean directly.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="script" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="location" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The location of the script to be loaded by the script engine (library or user provided).
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="engine-name" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The script engine name to use by the view.
|
||||
The script engine must implement Invocable.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="render-object" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The object where belong the render function.
|
||||
For example, in order to call Mustache.render(), renderObject
|
||||
should be set to Mustache and renderFunction to render.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="render-function" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Set the render function name.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="charset" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Set the charset used to read script and template files (UTF-8 by default).
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="resource-loader-path" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The script engine resource loader path via a Spring resource location.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
</xsd:schema>
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -129,6 +130,8 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
|
|||
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
|
||||
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
|
||||
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
|
||||
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
|
||||
|
@ -720,11 +723,11 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testViewResolution() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-view-resolution.xml", 6);
|
||||
loadBeanDefinitions("mvc-config-view-resolution.xml", 7);
|
||||
|
||||
ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
|
||||
assertNotNull(compositeResolver);
|
||||
assertEquals("Actual: " + compositeResolver.getViewResolvers(), 8, compositeResolver.getViewResolvers().size());
|
||||
assertEquals("Actual: " + compositeResolver.getViewResolvers(), 9, compositeResolver.getViewResolvers().size());
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, compositeResolver.getOrder());
|
||||
|
||||
List<ViewResolver> resolvers = compositeResolver.getViewResolvers();
|
||||
|
@ -759,8 +762,15 @@ public class MvcNamespaceTests {
|
|||
assertEquals(".tpl", accessor.getPropertyValue("suffix"));
|
||||
assertEquals(1024, accessor.getPropertyValue("cacheLimit"));
|
||||
|
||||
assertEquals(InternalResourceViewResolver.class, resolvers.get(6).getClass());
|
||||
resolver = resolvers.get(6);
|
||||
assertThat(resolver, instanceOf(ScriptTemplateViewResolver.class));
|
||||
accessor = new DirectFieldAccessor(resolver);
|
||||
assertEquals("", accessor.getPropertyValue("prefix"));
|
||||
assertEquals("", accessor.getPropertyValue("suffix"));
|
||||
assertEquals(1024, accessor.getPropertyValue("cacheLimit"));
|
||||
|
||||
assertEquals(InternalResourceViewResolver.class, resolvers.get(7).getClass());
|
||||
assertEquals(InternalResourceViewResolver.class, resolvers.get(8).getClass());
|
||||
|
||||
TilesConfigurer tilesConfigurer = appContext.getBean(TilesConfigurer.class);
|
||||
assertNotNull(tilesConfigurer);
|
||||
|
@ -787,11 +797,21 @@ public class MvcNamespaceTests {
|
|||
assertEquals("/test", groovyMarkupConfigurer.getResourceLoaderPath());
|
||||
assertTrue(groovyMarkupConfigurer.isAutoIndent());
|
||||
assertFalse(groovyMarkupConfigurer.isCacheTemplates());
|
||||
|
||||
ScriptTemplateConfigurer scriptTemplateConfigurer = appContext.getBean(ScriptTemplateConfigurer.class);
|
||||
assertNotNull(scriptTemplateConfigurer);
|
||||
assertEquals("Mustache", scriptTemplateConfigurer.getRenderObject());
|
||||
assertEquals("render", scriptTemplateConfigurer.getRenderFunction());
|
||||
assertEquals(StandardCharsets.ISO_8859_1, scriptTemplateConfigurer.getCharset());
|
||||
assertEquals("classpath:", scriptTemplateConfigurer.getResourceLoaderPath());
|
||||
String[] scripts = { "/META-INF/resources/webjars/mustachejs/0.8.2/mustache.js" };
|
||||
accessor = new DirectFieldAccessor(scriptTemplateConfigurer);
|
||||
assertArrayEquals(scripts, (String[]) accessor.getPropertyValue("scripts"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testViewResolutionWithContentNegotiation() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 6);
|
||||
loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 7);
|
||||
|
||||
ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
|
||||
assertNotNull(compositeResolver);
|
||||
|
@ -801,7 +821,7 @@ public class MvcNamespaceTests {
|
|||
List<ViewResolver> resolvers = compositeResolver.getViewResolvers();
|
||||
assertEquals(ContentNegotiatingViewResolver.class, resolvers.get(0).getClass());
|
||||
ContentNegotiatingViewResolver cnvr = (ContentNegotiatingViewResolver) resolvers.get(0);
|
||||
assertEquals(6, cnvr.getViewResolvers().size());
|
||||
assertEquals(7, cnvr.getViewResolvers().size());
|
||||
assertEquals(1, cnvr.getDefaultViews().size());
|
||||
assertTrue(cnvr.isUseNotAcceptableStatusCode());
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
|
@ -34,6 +34,8 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
|
|||
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
|
||||
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
|
||||
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
|
||||
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
|
||||
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
|
||||
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
|
||||
|
@ -60,6 +62,7 @@ public class ViewResolverRegistryTests {
|
|||
context.registerSingleton("velocityConfigurer", VelocityConfigurer.class);
|
||||
context.registerSingleton("tilesConfigurer", TilesConfigurer.class);
|
||||
context.registerSingleton("groovyMarkupConfigurer", GroovyMarkupConfigurer.class);
|
||||
context.registerSingleton("scriptTemplateConfigurer", ScriptTemplateConfigurer.class);
|
||||
this.registry = new ViewResolverRegistry();
|
||||
this.registry.setApplicationContext(context);
|
||||
this.registry.setContentNegotiationManager(new ContentNegotiationManager());
|
||||
|
@ -182,6 +185,20 @@ public class ViewResolverRegistryTests {
|
|||
checkPropertyValues(resolver, "prefix", "", "suffix", ".tpl");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptTemplate() {
|
||||
this.registry.scriptTemplate().prefix("/").suffix(".html").cache(true);
|
||||
ScriptTemplateViewResolver resolver = checkAndGetResolver(ScriptTemplateViewResolver.class);
|
||||
checkPropertyValues(resolver, "prefix", "/", "suffix", ".html", "cacheLimit", 1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptTemplateDefaultValues() {
|
||||
this.registry.scriptTemplate();
|
||||
ScriptTemplateViewResolver resolver = checkAndGetResolver(ScriptTemplateViewResolver.class);
|
||||
checkPropertyValues(resolver, "prefix", "", "suffix", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentNegotiation() {
|
||||
MappingJackson2JsonView view = new MappingJackson2JsonView();
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for ERB templates running on JRuby.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class ErbJrubyScriptTemplateTests {
|
||||
|
||||
private WebApplicationContext webAppContext;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webAppContext = mock(WebApplicationContext.class);
|
||||
this.servletContext = new MockServletContext();
|
||||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderTemplate() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Layout example");
|
||||
model.put("body", "This is the body");
|
||||
MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/erb/template.erb", model);
|
||||
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
|
||||
response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> model) throws Exception {
|
||||
ScriptTemplateView view = createViewWithUrl(viewUrl);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
view.renderMergedOutputModel(model, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private ScriptTemplateView createViewWithUrl(String viewUrl) throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ScriptTemplatingConfiguration.class);
|
||||
ctx.refresh();
|
||||
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
view.setApplicationContext(ctx);
|
||||
view.setUrl(viewUrl);
|
||||
view.afterPropertiesSet();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ScriptTemplatingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer jrubyConfigurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setScripts("org/springframework/web/servlet/view/script/erb/render.rb");
|
||||
configurer.setEngineName("jruby");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for Handlebars templates running on Nashorn Javascript engine.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class HandlebarsNashornScriptTemplateTests {
|
||||
|
||||
private WebApplicationContext webAppContext;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webAppContext = mock(WebApplicationContext.class);
|
||||
this.servletContext = new MockServletContext();
|
||||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderTemplate() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Layout example");
|
||||
model.put("body", "This is the body");
|
||||
MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/handlebars/template.html", model);
|
||||
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
|
||||
response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> model) throws Exception {
|
||||
ScriptTemplateView view = createViewWithUrl(viewUrl);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
view.renderMergedOutputModel(model, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private ScriptTemplateView createViewWithUrl(String viewUrl) throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ScriptTemplatingConfiguration.class);
|
||||
ctx.refresh();
|
||||
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
view.setApplicationContext(ctx);
|
||||
view.setUrl(viewUrl);
|
||||
view.afterPropertiesSet();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ScriptTemplatingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer handlebarsConfigurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts( "org/springframework/web/servlet/view/script/handlebars/polyfill.js",
|
||||
"/META-INF/resources/webjars/handlebars/3.0.0-1/handlebars.js",
|
||||
"org/springframework/web/servlet/view/script/handlebars/render.js");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for Mustache templates running on Nashorn Javascript engine.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class MustacheNashornScriptTemplateTests {
|
||||
|
||||
private WebApplicationContext webAppContext;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webAppContext = mock(WebApplicationContext.class);
|
||||
this.servletContext = new MockServletContext();
|
||||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderTemplate() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Layout example");
|
||||
model.put("body", "This is the body");
|
||||
MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/mustache/template.html", model);
|
||||
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
|
||||
response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> model) throws Exception {
|
||||
ScriptTemplateView view = createViewWithUrl(viewUrl);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
view.renderMergedOutputModel(model, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private ScriptTemplateView createViewWithUrl(String viewUrl) throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ScriptTemplatingConfiguration.class);
|
||||
ctx.refresh();
|
||||
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
view.setApplicationContext(ctx);
|
||||
view.setUrl(viewUrl);
|
||||
view.afterPropertiesSet();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ScriptTemplatingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer mustacheConfigurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("/META-INF/resources/webjars/mustachejs/0.8.2/mustache.js");
|
||||
configurer.setRenderObject("Mustache");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for React templates running on Nashorn Javascript engine.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class ReactNashornScriptTemplateTests {
|
||||
|
||||
private WebApplicationContext webAppContext;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webAppContext = mock(WebApplicationContext.class);
|
||||
this.servletContext = new MockServletContext();
|
||||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderJavascriptTemplate() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Layout example");
|
||||
model.put("body", "This is the body");
|
||||
MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/react/template.js", model);
|
||||
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
|
||||
response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderJsxTemplate() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Layout example");
|
||||
model.put("body", "This is the body");
|
||||
MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/react/template.jsx", model);
|
||||
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
|
||||
response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> model) throws Exception {
|
||||
ScriptTemplateView view = createViewWithUrl(viewUrl);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
view.renderMergedOutputModel(model, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private ScriptTemplateView createViewWithUrl(String viewUrl) throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
if (viewUrl.endsWith(".jsx")) {
|
||||
ctx.register(JsxTemplatingConfiguration.class);
|
||||
}
|
||||
else {
|
||||
ctx.register(JavascriptTemplatingConfiguration.class);
|
||||
}
|
||||
ctx.refresh();
|
||||
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
view.setApplicationContext(ctx);
|
||||
view.setUrl(viewUrl);
|
||||
view.afterPropertiesSet();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class JavascriptTemplatingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer reactConfigurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts( "org/springframework/web/servlet/view/script/react/polyfill.js",
|
||||
"/META-INF/resources/webjars/react/0.12.2/react.js",
|
||||
"/META-INF/resources/webjars/react/0.12.2/JSXTransformer.js",
|
||||
"org/springframework/web/servlet/view/script/react/render.js");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class JsxTemplatingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer reactConfigurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts( "org/springframework/web/servlet/view/script/react/polyfill.js",
|
||||
"/META-INF/resources/webjars/react/0.12.2/react.js",
|
||||
"/META-INF/resources/webjars/react/0.12.2/JSXTransformer.js",
|
||||
"org/springframework/web/servlet/view/script/react/render.js");
|
||||
configurer.setRenderFunction("renderJsx");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ScriptTemplateConfigurer}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class ScriptTemplateConfigurerTests {
|
||||
|
||||
private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/script/";
|
||||
|
||||
private StaticApplicationContext applicationContext;
|
||||
|
||||
private ScriptTemplateConfigurer configurer;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.applicationContext = new StaticApplicationContext();
|
||||
this.configurer = new ScriptTemplateConfigurer();
|
||||
this.configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customEngineAndRenderFunction() throws Exception {
|
||||
this.configurer.setApplicationContext(this.applicationContext);
|
||||
ScriptEngine engine = mock(InvocableScriptEngine.class);
|
||||
given(engine.get("key")).willReturn("value");
|
||||
this.configurer.setEngine(engine);
|
||||
this.configurer.setRenderFunction("render");
|
||||
this.configurer.afterPropertiesSet();
|
||||
|
||||
engine = this.configurer.getEngine();
|
||||
assertNotNull(engine);
|
||||
assertEquals("value", engine.get("key"));
|
||||
assertNull(this.configurer.getRenderObject());
|
||||
assertEquals("render", this.configurer.getRenderFunction());
|
||||
assertEquals(StandardCharsets.UTF_8, this.configurer.getCharset());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nonInvocableScriptEngine() throws Exception {
|
||||
this.configurer.setApplicationContext(this.applicationContext);
|
||||
ScriptEngine engine = mock(ScriptEngine.class);
|
||||
this.configurer.setEngine(engine);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void noRenderFunctionDefined() throws Exception {
|
||||
this.configurer.setApplicationContext(this.applicationContext);
|
||||
ScriptEngine engine = mock(InvocableScriptEngine.class);
|
||||
this.configurer.setEngine(engine);
|
||||
this.configurer.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parentLoader() throws Exception {
|
||||
|
||||
this.configurer.setApplicationContext(this.applicationContext);
|
||||
|
||||
ClassLoader classLoader = this.configurer.createClassLoader();
|
||||
assertNotNull(classLoader);
|
||||
URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
|
||||
assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(1));
|
||||
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(),
|
||||
Matchers.endsWith("org/springframework/web/servlet/view/script/"));
|
||||
|
||||
this.configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH + ",classpath:org/springframework/web/servlet/view/");
|
||||
classLoader = this.configurer.createClassLoader();
|
||||
assertNotNull(classLoader);
|
||||
urlClassLoader = (URLClassLoader) classLoader;
|
||||
assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(2));
|
||||
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(),
|
||||
Matchers.endsWith("org/springframework/web/servlet/view/script/"));
|
||||
assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(),
|
||||
Matchers.endsWith("org/springframework/web/servlet/view/"));
|
||||
}
|
||||
|
||||
|
||||
private interface InvocableScriptEngine extends ScriptEngine, Invocable {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ScriptTemplateViewResolver}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class ScriptTemplateViewResolverTests {
|
||||
|
||||
@Test
|
||||
public void viewClass() throws Exception {
|
||||
ScriptTemplateViewResolver resolver = new ScriptTemplateViewResolver();
|
||||
Assert.assertEquals(ScriptTemplateView.class, resolver.requiredViewClass());
|
||||
DirectFieldAccessor viewAccessor = new DirectFieldAccessor(resolver);
|
||||
Class viewClass = (Class) viewAccessor.getPropertyValue("viewClass");
|
||||
Assert.assertEquals(ScriptTemplateView.class, viewClass);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ScriptTemplateView}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class ScriptTemplateViewTests {
|
||||
|
||||
private WebApplicationContext webAppContext;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webAppContext = mock(WebApplicationContext.class);
|
||||
this.servletContext = new MockServletContext();
|
||||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingScriptTemplateConfig() throws Exception {
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
given(this.webAppContext.getBeansOfType(ScriptTemplateConfig.class, true, false))
|
||||
.willReturn(new HashMap<String, ScriptTemplateConfig>());
|
||||
|
||||
view.setUrl("sampleView");
|
||||
try {
|
||||
view.setApplicationContext(this.webAppContext);
|
||||
fail();
|
||||
}
|
||||
catch (ApplicationContextException ex) {
|
||||
assertTrue(ex.getMessage().contains("ScriptTemplateConfig"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dectectScriptTemplateConfig() throws Exception {
|
||||
InvocableScriptEngine engine = mock(InvocableScriptEngine.class);
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngine(engine);
|
||||
configurer.setRenderObject("Template");
|
||||
configurer.setRenderFunction("render");
|
||||
configurer.setCharset(StandardCharsets.ISO_8859_1);
|
||||
Map<String, ScriptTemplateConfig> configMap = new HashMap<String, ScriptTemplateConfig>();
|
||||
configMap.put("scriptTemplateConfigurer", configurer);
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
given(this.webAppContext.getBeansOfType(ScriptTemplateConfig.class, true, false)).willReturn(configMap);
|
||||
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(view);
|
||||
view.setUrl("sampleView");
|
||||
view.setApplicationContext(this.webAppContext);
|
||||
assertEquals(engine, accessor.getPropertyValue("engine"));
|
||||
assertEquals(StandardCharsets.ISO_8859_1, accessor.getPropertyValue("charset"));
|
||||
assertEquals("Template", accessor.getPropertyValue("renderObject"));
|
||||
assertEquals("render", accessor.getPropertyValue("renderFunction"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nonInvocableScriptEngine() throws Exception {
|
||||
ScriptEngine engine = mock(ScriptEngine.class);
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngine(engine);
|
||||
fail();
|
||||
}
|
||||
|
||||
private interface InvocableScriptEngine extends ScriptEngine, Invocable {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view.script;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for String templates running on Jython.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class StringJythonScriptTemplateTests {
|
||||
|
||||
private WebApplicationContext webAppContext;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webAppContext = mock(WebApplicationContext.class);
|
||||
this.servletContext = new MockServletContext();
|
||||
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderTemplate() throws Exception {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("title", "Layout example");
|
||||
model.put("body", "This is the body");
|
||||
MockHttpServletResponse response = renderViewWithModel("org/springframework/web/servlet/view/script/python/template.html", model);
|
||||
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
|
||||
response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> model) throws Exception {
|
||||
ScriptTemplateView view = createViewWithUrl(viewUrl);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
view.renderMergedOutputModel(model, request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private ScriptTemplateView createViewWithUrl(String viewUrl) throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ScriptTemplatingConfiguration.class);
|
||||
ctx.refresh();
|
||||
|
||||
ScriptTemplateView view = new ScriptTemplateView();
|
||||
view.setApplicationContext(ctx);
|
||||
view.setUrl(viewUrl);
|
||||
view.afterPropertiesSet();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ScriptTemplatingConfiguration {
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer jythonConfigurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setScripts("org/springframework/web/servlet/view/script/python/render.py");
|
||||
configurer.setEngineName("jython");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
<mvc:freemarker />
|
||||
<mvc:velocity />
|
||||
<mvc:groovy />
|
||||
<mvc:script-template />
|
||||
</mvc:view-resolvers>
|
||||
|
||||
<mvc:tiles-configurer check-refresh="true">
|
||||
|
@ -40,5 +41,7 @@
|
|||
|
||||
<mvc:groovy-configurer />
|
||||
|
||||
<mvc:script-template-configurer engine-name="nashorn" render-function="render"/>
|
||||
|
||||
</beans>
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
<mvc:freemarker prefix="freemarker-" suffix=".freemarker" view-names="my*,*Report" />
|
||||
<mvc:velocity cache-views="false" />
|
||||
<mvc:groovy />
|
||||
<mvc:script-template />
|
||||
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" />
|
||||
<ref bean="customResolver" />
|
||||
</mvc:view-resolvers>
|
||||
|
@ -33,5 +34,9 @@
|
|||
|
||||
<mvc:groovy-configurer resource-loader-path="/test" cache-templates="false" auto-indent="true" />
|
||||
|
||||
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render" charset="ISO-8859-1" resource-loader-path="classpath:">
|
||||
<mvc:script location="/META-INF/resources/webjars/mustachejs/0.8.2/mustache.js" />
|
||||
</mvc:script-template-configurer>
|
||||
|
||||
</beans>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
require 'erb'
|
||||
require 'ostruct'
|
||||
require 'java'
|
||||
|
||||
# Renders an ERB template against a hashmap of variables.
|
||||
# template should be a Java InputStream
|
||||
def render(template, variables)
|
||||
context = OpenStruct.new(variables).instance_eval do
|
||||
variables.each do |k, v|
|
||||
instance_variable_set(k, v) if k[0] == '@'
|
||||
end
|
||||
|
||||
def partial(partial_name, options={})
|
||||
new_variables = marshal_dump.merge(options[:locals] || {})
|
||||
Java::Pavo::ERB.render(partial_name, new_variables)
|
||||
end
|
||||
|
||||
binding
|
||||
end
|
||||
ERB.new(template).result(context);
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
<html><head><title><%= title %></title></head><body><p><%= body %></p></body></html>
|
|
@ -0,0 +1 @@
|
|||
var window = {};
|
|
@ -0,0 +1,5 @@
|
|||
// TODO Manage compiled template cache
|
||||
function render(template, model) {
|
||||
var compiledTemplate = Handlebars.compile(template);
|
||||
return compiledTemplate(model);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<html><head><title>{{title}}</title></head><body><p>{{body}}</p></body></html>
|
|
@ -0,0 +1 @@
|
|||
<html><head><title>{{title}}</title></head><body><p>{{body}}</p></body></html>
|
|
@ -0,0 +1,5 @@
|
|||
from string import Template
|
||||
|
||||
def render(template, model):
|
||||
s = Template(template)
|
||||
return s.substitute(model)
|
|
@ -0,0 +1 @@
|
|||
<html><head><title>$title</title></head><body><p>$body</p></body></html>
|
|
@ -0,0 +1,5 @@
|
|||
var global = this;
|
||||
var console = {};
|
||||
console.debug = print;
|
||||
console.warn = print;
|
||||
console.log = print;
|
|
@ -0,0 +1,13 @@
|
|||
function render(template, model) {
|
||||
// Create a real Javascript Object from the model Map
|
||||
var data = {};
|
||||
for(var k in model) data[k]=model[k];
|
||||
var element = React.createElement(eval(template), data);
|
||||
// Should use React.renderToString in production
|
||||
return React.renderToStaticMarkup(element);
|
||||
}
|
||||
|
||||
function renderJsx(template, model) {
|
||||
var jsTemplate = JSXTransformer.transform(template).code;
|
||||
return render(jsTemplate, model);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
React.createClass({
|
||||
render: function() {
|
||||
return React.createElement("html", null, React.createElement("head", null, React.createElement("title", null, this.props.title)), React.createElement("body", null, React.createElement("p", null, this.props.body)));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
React.createClass({
|
||||
render: function() {
|
||||
return <html><head><title>{this.props.title}</title></head><body><p>{this.props.body}</p></body></html>
|
||||
}
|
||||
});
|
|
@ -4762,7 +4762,7 @@ And the same in XML:
|
|||
</mvc:view-resolvers>
|
||||
----
|
||||
|
||||
Note however that FreeMarker, Velocity, Tiles, and Groovy Markup also require
|
||||
Note however that FreeMarker, Velocity, Tiles, Groovy Markup and script templates also require
|
||||
configuration of the underlying view technology.
|
||||
|
||||
The MVC namespace provides dedicated elements. For example with FreeMarker:
|
||||
|
|
|
@ -2357,6 +2357,192 @@ https://spring.io/blog/2009/03/16/adding-an-atom-view-to-an-application-using-sp
|
|||
|
||||
|
||||
|
||||
[[view-script]]
|
||||
== Script templates
|
||||
|
||||
It is possible to integrate any templating library running on top of a JSR-223
|
||||
script engine in web applications using Spring. The following describes in a
|
||||
broad way how to do this. The script engine must implement both `ScriptEngine`
|
||||
and `Invocable` interfaces.
|
||||
|
||||
It has been tested with:
|
||||
|
||||
* http://handlebarsjs.com/[Handlebars] running on http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
* https://mustache.github.io/[Mustache] running on http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
* http://facebook.github.io/react/[React] running on http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
* http://www.embeddedjs.com/[EJS] running on http://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
* http://www.stuartellis.eu/articles/erb/[ERB] running on http://jruby.org[JRuby]
|
||||
* https://docs.python.org/2/library/string.html#template-strings[String templates] running on http://www.jython.org/[Jython]
|
||||
|
||||
[[view-script-dependencies]]
|
||||
=== Dependencies
|
||||
|
||||
To be able to use script templates integration, you need to have available in your classpath
|
||||
the script engine:
|
||||
|
||||
* http://openjdk.java.net/projects/nashorn/[Nashorn] Javascript engine is provided builtin with Java 8+
|
||||
* http://docs.oracle.com/javase/7/docs/technotes/guides/scripting/programmer_guide/#jsengine[Rhino]
|
||||
Javascript engine is provided builtin with Java 6 and Java 7.
|
||||
Please notice that using Rhino is not recommended since it does not
|
||||
support running most template engines.
|
||||
* http://jruby.org[JRuby] dependency should be added in order to get Ruby support.
|
||||
* http://www.jython.org[Jython] dependency should be added in order to get Python support.
|
||||
|
||||
You should also need to add dependencies for your script based template engine. For example,
|
||||
for Javascript you can use http://www.webjars.org/[WebJars] to add Maven/Gradle dependencies
|
||||
in order to make your javascript libraries available in the classpath.
|
||||
|
||||
|
||||
[[view-script-integrate]]
|
||||
=== How to integrate script based templating
|
||||
|
||||
To be able to use script templates, you have to configure it in order to specify various parameters
|
||||
like the script engine to use, the script files to load and what function should be called to
|
||||
render the templates. This is done thanks to a `ScriptTemplateConfigurer` bean and optional script
|
||||
files.
|
||||
|
||||
For example, in order to render Mustache templates thanks to the Nashorn Javascript engine
|
||||
provided with Java 8+, you should declare the following configuration:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class MustacheConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.scriptTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer configurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("mustache.js");
|
||||
configurer.setRenderObject("Mustache");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The XML counterpart using MVC namespace is:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven />
|
||||
|
||||
<mvc:view-resolvers>
|
||||
<mvc:script-template />
|
||||
</mvc:view-resolvers>
|
||||
|
||||
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
|
||||
<mvc:script location="mustache.js" />
|
||||
</mvc:script-template-configurer>
|
||||
----
|
||||
|
||||
The controller is exactly what you should expect:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class SampleController {
|
||||
|
||||
@RequestMapping
|
||||
public ModelAndView test() {
|
||||
ModelAndView mav = new ModelAndView();
|
||||
mav.addObject("title", "Sample title").addObject("body", "Sample body");
|
||||
mav.setViewName("template.html");
|
||||
return mav;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
And the Mustache template is:
|
||||
|
||||
[source,html,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
<html>
|
||||
<head>
|
||||
<title>{{title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>{{body}}</p>
|
||||
</body>
|
||||
</html>
|
||||
----
|
||||
|
||||
The render function is called with the following parameters:
|
||||
|
||||
* template: the view template content (String)
|
||||
* model: the view model (Map)
|
||||
|
||||
`Mustache.render()` is natively compatible with this signature, so you can call it directly.
|
||||
|
||||
If your templating technology requires some customization, you may provide a script that
|
||||
implements a custom render function. For example, http://handlebarsjs.com[Handlerbars]
|
||||
needs to compile templates before using them, and requires a
|
||||
http://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
|
||||
browser facilities not available in the server-side script engine.
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class MustacheConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.scriptTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer configurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
`polyfill.js` only defines the `window` object needed by Handlebars to run properly:
|
||||
|
||||
[source,javascript,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
var window = {};
|
||||
----
|
||||
|
||||
This basic `render.js` implementation compiles the template before using it. A production
|
||||
ready implementation should also store and reused cached templates / pre-compiled templates.
|
||||
This can be done on the script side, as well as any customization you need (managing
|
||||
template engine configuration for example).
|
||||
|
||||
[source,javascript,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
function render(template, model) {
|
||||
var compiledTemplate = Handlebars.compile(template);
|
||||
return compiledTemplate(model);
|
||||
}
|
||||
----
|
||||
|
||||
Check out Spring script templates unit tests
|
||||
(https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script[java],
|
||||
https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script[resources])
|
||||
for more configuration examples.
|
||||
|
||||
|
||||
|
||||
|
||||
[[view-xml-marshalling]]
|
||||
== XML Marshalling View
|
||||
The `MarshallingView` uses an XML `Marshaller` defined in the `org.springframework.oxm`
|
||||
|
|
Loading…
Reference in New Issue