Browse Source
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-12266pull/783/head
36 changed files with 1792 additions and 16 deletions
@ -0,0 +1,84 @@
@@ -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")); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,44 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,97 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,21 @@
@@ -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 @@
@@ -0,0 +1 @@
|
||||
<html><head><title><%= title %></title></head><body><p><%= body %></p></body></html> |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
var window = {}; |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
// TODO Manage compiled template cache
|
||||
function render(template, model) { |
||||
var compiledTemplate = Handlebars.compile(template); |
||||
return compiledTemplate(model); |
||||
} |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
<html><head><title>{{title}}</title></head><body><p>{{body}}</p></body></html> |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
<html><head><title>{{title}}</title></head><body><p>{{body}}</p></body></html> |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
from string import Template |
||||
|
||||
def render(template, model): |
||||
s = Template(template) |
||||
return s.substitute(model) |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
<html><head><title>$title</title></head><body><p>$body</p></body></html> |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
var global = this; |
||||
var console = {}; |
||||
console.debug = print; |
||||
console.warn = print; |
||||
console.log = print; |
||||
@ -0,0 +1,13 @@
@@ -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 @@
@@ -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 @@
@@ -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> |
||||
} |
||||
}); |
||||
Loading…
Reference in new issue