Add jmustache support

The package names changed a bit from the prototype project, but wuth vanilla
autconfiguration usage that shouldn't matter. Follows closely the Groovy
templates support. Templates live in classpath:/templates/*.html by default.

Fixes gh-2242
pull/2320/head
Dave Syer 10 years ago
parent 9dd4d43cec
commit 2729c747ca

@ -60,6 +60,11 @@
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>

@ -0,0 +1,115 @@
/*
* Copyright 2013-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.boot.autoconfigure.mustache;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver;
import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Collector;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.TemplateLoader;
/**
* @author Dave Syer
* @since 1.2.2
*
*/
@Configuration
@ConditionalOnClass(Mustache.class)
@EnableConfigurationProperties(MustacheProperties.class)
public class MustacheAutoConfiguration {
@Autowired
private MustacheProperties mustache;
@Autowired
private Environment environment;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void checkTemplateLocationExists() {
if (this.mustache.isCheckTemplateLocation()) {
TemplateLocation location = new TemplateLocation(this.mustache.getPrefix());
Assert.state(location.exists(this.applicationContext),
"Cannot find template location: " + location
+ " (please add some templates, check your Mustache "
+ "configuration, or set spring.mustache.template."
+ "check-template-location=false)");
}
}
@Bean
@ConditionalOnMissingBean(Mustache.Compiler.class)
public Mustache.Compiler mustacheCompiler(TemplateLoader mustacheTemplateLoader) {
return Mustache.compiler().withLoader(mustacheTemplateLoader)
.withCollector(collector());
}
private Collector collector() {
MustacheEnvironmentCollector collector = new MustacheEnvironmentCollector();
collector.setEnvironment(this.environment);
return collector;
}
@Bean
@ConditionalOnMissingBean(TemplateLoader.class)
public MustacheResourceTemplateLoader mustacheTemplateLoader() {
MustacheResourceTemplateLoader loader = new MustacheResourceTemplateLoader(
this.mustache.getPrefix(), this.mustache.getSuffix());
loader.setCharset(this.mustache.getCharset());
return loader;
}
@Configuration
@ConditionalOnWebApplication
protected static class MustacheWebConfiguration {
@Autowired
private MustacheProperties mustache;
@Bean
@ConditionalOnMissingBean(MustacheViewResolver.class)
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
MustacheViewResolver resolver = new MustacheViewResolver();
resolver.setPrefix(this.mustache.getPrefix());
resolver.setSuffix(this.mustache.getSuffix());
resolver.setCache(this.mustache.isCache());
resolver.setViewNames(this.mustache.getViewNames());
resolver.setContentType(this.mustache.getContentType());
resolver.setCompiler(mustacheCompiler);
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
}

@ -0,0 +1,99 @@
/*
* Copyright 2012-2013 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.boot.autoconfigure.mustache;
import org.springframework.beans.factory.FactoryBean;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Collector;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.Escaper;
import com.samskivert.mustache.Mustache.Formatter;
import com.samskivert.mustache.Mustache.TemplateLoader;
/**
* Factory for a Mustache compiler with custom strategies. For building a
* <code>@Bean</code> definition in Java it probably doesn't help to use this factory
* since the underlying fluent API is actually richer.
*
* @see MustacheResourceTemplateLoader
*
* @author Dave Syer
* @since 1.2.2
*
*/
public class MustacheCompilerFactoryBean implements FactoryBean<Mustache.Compiler> {
private String delims;
private TemplateLoader templateLoader;
private Formatter formatter;
private Escaper escaper;
private Collector collector;
private Compiler compiler;
public void setDelims(String delims) {
this.delims = delims;
}
public void setTemplateLoader(TemplateLoader templateLoader) {
this.templateLoader = templateLoader;
}
public void setFormatter(Formatter formatter) {
this.formatter = formatter;
}
public void setEscaper(Escaper escaper) {
this.escaper = escaper;
}
public void setCollector(Collector collector) {
this.collector = collector;
}
@Override
public Mustache.Compiler getObject() throws Exception {
this.compiler = Mustache.compiler();
if (this.delims != null) {
this.compiler = this.compiler.withDelims(this.delims);
}
if (this.templateLoader != null) {
this.compiler = this.compiler.withLoader(this.templateLoader);
}
if (this.formatter != null) {
this.compiler = this.compiler.withFormatter(this.formatter);
}
if (this.escaper != null) {
this.compiler = this.compiler.withEscaper(this.escaper);
}
if (this.collector != null) {
this.compiler = this.compiler.withCollector(this.collector);
}
return this.compiler;
}
@Override
public Class<?> getObjectType() {
return Mustache.Compiler.class;
}
@Override
public boolean isSingleton() {
return false;
}
}

@ -0,0 +1,64 @@
package org.springframework.boot.autoconfigure.mustache;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.bind.PropertySourcesPropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import com.samskivert.mustache.DefaultCollector;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.VariableFetcher;
/**
* @author Dave Syer
* @since 1.2.2
*/
public class MustacheEnvironmentCollector extends DefaultCollector implements
EnvironmentAware {
private ConfigurableEnvironment environment;
private Map<String, Object> target;
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
this.target = new HashMap<String, Object>();
new RelaxedDataBinder(this.target).bind(new PropertySourcesPropertyValues(
this.environment.getPropertySources()));
}
@Override
public Mustache.VariableFetcher createFetcher(Object ctx, String name) {
VariableFetcher fetcher = super.createFetcher(ctx, name);
if (fetcher != null) {
return fetcher;
}
if (this.environment.containsProperty(name)) {
return new VariableFetcher() {
@Override
public Object get(Object ctx, String name) throws Exception {
return MustacheEnvironmentCollector.this.environment
.getProperty(name);
}
};
}
if (this.target.containsKey(name)) {
return new VariableFetcher() {
@Override
public Object get(Object ctx, String name) throws Exception {
return MustacheEnvironmentCollector.this.target.get(name);
}
};
}
return null;
}
}

@ -0,0 +1,60 @@
/*
* Copyright 2013-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.boot.autoconfigure.mustache;
import org.springframework.boot.autoconfigure.template.AbstractViewResolverProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Dave Syer
* @since 1.2.2
*
*/
@ConfigurationProperties(prefix = "spring.mustache")
public class MustacheProperties extends AbstractViewResolverProperties {
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
/**
* Prefix to apply to template names.
*/
private String prefix = DEFAULT_PREFIX;
/**
* Suffix to apply to template names.
*/
private String suffix = DEFAULT_SUFFIX;
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}

@ -0,0 +1,83 @@
/*
* Copyright 2012-2013 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.boot.autoconfigure.mustache;
import java.io.InputStreamReader;
import java.io.Reader;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.TemplateLoader;
/**
* Mustache TemplateLoader implementation that uses a prefix, suffix and the Spring
* Resource abstraction to load a template from a file, classpath, URL etc. A
* TemplateLoader is needed in the Compiler when you want to render partials (i.e.
* tiles-like fetaures).
*
* @see Mustache
* @see Resource
*
* @author Dave Syer
* @since 1.2.2
*
*/
public class MustacheResourceTemplateLoader implements TemplateLoader, ResourceLoaderAware {
private String prefix = "";
private String suffix = "";
private String charSet = "UTF-8";
private ResourceLoader resourceLoader = new DefaultResourceLoader();
public MustacheResourceTemplateLoader() {
}
public MustacheResourceTemplateLoader(String prefix, String suffix) {
super();
this.prefix = prefix;
this.suffix = suffix;
}
/**
* @param charSet the charSet to set
*/
public void setCharset(String charSet) {
this.charSet = charSet;
}
/**
* @param resourceLoader the resourceLoader to set
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public Reader getTemplate(String name) throws Exception {
return new InputStreamReader(resourceLoader.getResource(prefix + name + suffix)
.getInputStream(), charSet);
}
}

@ -0,0 +1,46 @@
/*
* Copyright 2012-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.boot.autoconfigure.mustache;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
/**
* {@link TemplateAvailabilityProvider} that provides availability information for
* Mustache view templates
*
* @author Dave Syer
* @since 1.2.2
*/
public class MustacheTemplateAvailabilityProvider implements TemplateAvailabilityProvider {
@Override
public boolean isTemplateAvailable(String view, Environment environment,
ClassLoader classLoader, ResourceLoader resourceLoader) {
if (ClassUtils.isPresent("com.samskivert.mustache.Template", classLoader)) {
String prefix = environment.getProperty("spring.mustache.prefix",
MustacheProperties.DEFAULT_PREFIX);
String suffix = environment.getProperty("spring.mustache.suffix",
MustacheProperties.DEFAULT_SUFFIX);
return resourceLoader.getResource(prefix + view + suffix).exists();
}
return false;
}
}

@ -0,0 +1,46 @@
/*
* Copyright 2012-2013 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.boot.autoconfigure.mustache.web;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.view.AbstractTemplateView;
import com.samskivert.mustache.Template;
/**
* @author Dave Syer
*
*/
public class MustacheView extends AbstractTemplateView {
private final Template template;
public MustacheView(Template template) {
this.template = template;
}
@Override
protected void renderMergedTemplateModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
template.execute(model, response.getWriter());
}
}

@ -0,0 +1,90 @@
/*
* Copyright 2012-2013 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.boot.autoconfigure.mustache.web;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Locale;
import org.springframework.beans.propertyeditors.LocaleEditor;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Template;
/**
* @author Dave Syer
*
*/
public class MustacheViewResolver extends UrlBasedViewResolver {
private Compiler compiler = Mustache.compiler();
public MustacheViewResolver() {
setViewClass(MustacheView.class);
}
/**
* @param compiler the compiler to set
*/
public void setCompiler(Compiler compiler) {
this.compiler = compiler;
}
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
Resource resource = resolveResource(viewName, locale);
if (resource == null) {
return null;
}
MustacheView view = new MustacheView(createTemplate(resource));
view.setApplicationContext(getApplicationContext());
view.setServletContext(getServletContext());
return view;
}
private Template createTemplate(Resource resource) throws IOException {
return compiler.compile(new InputStreamReader(resource.getInputStream()));
}
private Resource resolveResource(String viewName, Locale locale) {
String l10n = "";
if (locale != null) {
LocaleEditor localeEditor = new LocaleEditor();
localeEditor.setValue(locale);
l10n = "_" + localeEditor.getAsText();
}
return resolveFromLocale(viewName, l10n);
}
private Resource resolveFromLocale(String viewName, String locale) {
Resource resource = getApplicationContext().getResource(
getPrefix() + viewName + locale + getSuffix());
if (resource == null || !resource.exists()) {
if (locale.isEmpty()) {
return null;
}
int index = locale.lastIndexOf("_");
return resolveFromLocale(viewName, locale.substring(0, index));
}
return resource;
}
}

@ -44,6 +44,7 @@ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoCo
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\
@ -69,6 +70,7 @@ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.velocity.VelocityTemplateAvailabilityProvider,\

@ -0,0 +1,128 @@
package org.springframework.boot.autoconfigure.mustache;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader;
import org.springframework.boot.autoconfigure.mustache.ApplicationTests.Application;
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest("server.port:0")
@WebAppConfiguration
public class ApplicationTests {
@Autowired
private EmbeddedWebApplicationContext context;
private int port;
@Before
public void init() {
this.port = this.context.getEmbeddedServletContainer().getPort();
}
@Test
public void contextLoads() {
String source = "Hello {{arg}}!";
Template tmpl = Mustache.compiler().compile(source);
Map<String, String> context = new HashMap<String, String>();
context.put("arg", "world");
assertEquals("Hello world!", tmpl.execute(context)); // returns "Hello world!"
}
@Test
public void testHomePage() throws Exception {
String body = new TestRestTemplate().getForObject(
"http://localhost:" + this.port, String.class);
assertTrue(body.contains("Hello World"));
}
@Test
public void testPartialPage() throws Exception {
String body = new TestRestTemplate().getForObject("http://localhost:" + this.port
+ "/partial", String.class);
assertTrue(body.contains("Hello World"));
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected static @interface MinimalWebConfiguration {
}
@Configuration
@MinimalWebConfiguration
@Controller
public static class Application {
@RequestMapping("/")
public String home(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", "Hello World");
model.put("title", "Hello App");
return "home";
}
@RequestMapping("/partial")
public String layout(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", "Hello World");
model.put("title", "Hello App");
return "partial";
}
@Bean
public MustacheViewResolver viewResolver() {
MustacheViewResolver resolver = new MustacheViewResolver();
resolver.setPrefix("classpath:/mustache-templates/");
resolver.setSuffix(".html");
resolver.setCompiler(Mustache.compiler().withLoader(
new MustacheResourceTemplateLoader("classpath:/mustache-templates/",
".html")));
return resolver;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
}

@ -0,0 +1,102 @@
package org.springframework.boot.autoconfigure.mustache;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.AutoApplicationTests.Application;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest({ "server.port:0",
"spring.mustache.prefix:classpath:/mustache-templates/" })
@WebAppConfiguration
public class AutoApplicationTests {
@Autowired
private EmbeddedWebApplicationContext context;
private int port;
@Before
public void init() {
this.port = this.context.getEmbeddedServletContainer().getPort();
}
@Test
public void testHomePage() throws Exception {
String body = new TestRestTemplate().getForObject(
"http://localhost:" + this.port, String.class);
assertTrue(body.contains("Hello World"));
}
@Test
public void testPartialPage() throws Exception {
String body = new TestRestTemplate().getForObject("http://localhost:" + this.port
+ "/partial", String.class);
assertTrue(body.contains("Hello World"));
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ MustacheAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected static @interface MinimalWebConfiguration {
}
@Configuration
@MinimalWebConfiguration
@Controller
public static class Application {
@RequestMapping("/")
public String home(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", "Hello World");
model.put("title", "Hello App");
return "home";
}
@RequestMapping("/partial")
public String layout(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", "Hello World");
model.put("title", "Hello App");
return "partial";
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
}

@ -0,0 +1,62 @@
package org.springframework.boot.autoconfigure.mustache;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheTemplateStandaloneTests.Application;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.samskivert.mustache.Mustache;
import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest({ "spring.main.web_environment=false", "env.foo=Heaven", "foo=World" })
public class MustacheTemplateStandaloneTests {
@Autowired
private Mustache.Compiler compiler;
@Test
public void directCompilation() throws Exception {
assertEquals(
"Hello: World",
this.compiler.compile("Hello: {{world}}").execute(
Collections.singletonMap("world", "World")));
}
@Test
public void environmentCollectorCompoundKey() throws Exception {
assertEquals("Hello: Heaven", this.compiler.compile("Hello: {{env.foo}}")
.execute(new Object()));
}
@Test
public void environmentCollectorCompoundKeyStandard() throws Exception {
assertEquals(
"Hello: Heaven",
this.compiler.standardsMode(true).compile("Hello: {{env.foo}}")
.execute(new Object()));
}
@Test
public void environmentCollectorSimpleKey() throws Exception {
assertEquals("Hello: World",
this.compiler.compile("Hello: {{foo}}").execute(new Object()));
}
@Configuration
@Import({ MustacheAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected static class Application {
}
}

@ -0,0 +1,76 @@
/*
* Copyright 2012-2013 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.boot.autoconfigure.mustache;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author Dave Syer
*
*/
public class MustacheViewResolverTests {
private MustacheViewResolver resolver = new MustacheViewResolver();
@Before
public void init() {
this.resolver.setApplicationContext(new StaticWebApplicationContext());
this.resolver.setServletContext(new MockServletContext());
this.resolver.setPrefix("classpath:/mustache-templates/");
this.resolver.setSuffix(".html");
}
@Test
public void resolveNonExistent() throws Exception {
assertNull(this.resolver.resolveViewName("bar", null));
}
@Test
public void resolveNullLocale() throws Exception {
assertNotNull(this.resolver.resolveViewName("foo", null));
}
@Test
public void resolveDefaultLocale() throws Exception {
assertNotNull(this.resolver.resolveViewName("foo", Locale.US));
}
@Test
public void resolveDoubleLocale() throws Exception {
assertNotNull(this.resolver.resolveViewName("foo", Locale.CANADA_FRENCH));
}
@Test
public void resolveTripleLocale() throws Exception {
assertNotNull(this.resolver.resolveViewName("foo", new Locale("en", "GB", "cy")));
}
@Test
public void resolveSpecificLocale() throws Exception {
assertNotNull(this.resolver.resolveViewName("foo", new Locale("de")));
}
}

@ -0,0 +1,64 @@
/*
* Copyright 2012-2013 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.boot.autoconfigure.mustache;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.mustache.web.MustacheView;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import com.samskivert.mustache.Mustache;
import static org.junit.Assert.assertEquals;
/**
* @author Dave Syer
*
*/
public class MustacheViewTests {
private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletResponse response = new MockHttpServletResponse();
private AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
@Before
public void init() {
this.context.refresh();
MockServletContext servletContext = new MockServletContext();
this.context.setServletContext(servletContext);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.context);
}
@Test
public void viewResolvesHandlebars() throws Exception {
MustacheView view = new MustacheView(Mustache.compiler().compile("Hello {{msg}}"));
view.setApplicationContext(this.context);
view.render(Collections.singletonMap("msg", "World"), this.request, this.response);
assertEquals("Hello World", this.response.getContentAsString());
}
}

@ -0,0 +1,2 @@
<h2>A Message</h2>
<div>{{message}} at {{time}}</div>

@ -0,0 +1,9 @@
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h2>A Message</h2>
<div>{{message}} at {{time}}</div>
</body>
</html>

@ -0,0 +1,15 @@
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<div id="navbar" class="navbar navbar-default" role="navigation">
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/"> Home </a></li>
</ul>
</div>
</div>
<div class="jumbotron">{{#include}}{{body}}{{/include}}</div>
</body>
</html>

@ -0,0 +1,15 @@
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<div id="navbar" class="navbar navbar-default" role="navigation">
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/"> Home </a></li>
</ul>
</div>
</div>
<div class="jumbotron">{{>content}}</div>
</body>
</html>

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
@ -88,6 +90,7 @@
<jersey.version>2.14</jersey.version>
<jetty.version>9.2.4.v20141103</jetty.version>
<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
<jmustache.version>1.9</jmustache.version>
<joda-time.version>2.5</joda-time.version>
<jolokia.version>1.2.3</jolokia.version>
<json-path.version>0.9.1</json-path.version>
@ -324,6 +327,11 @@
<artifactId>spring-boot-starter-mobile</artifactId>
<version>1.2.2.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mustache</artifactId>
<version>1.2.2.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
@ -483,6 +491,11 @@
<artifactId>json-path</artifactId>
<version>${json-path.version}</version>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<version>${jmustache.version}</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>

@ -68,6 +68,7 @@
<module>spring-boot-sample-web-freemarker</module>
<module>spring-boot-sample-web-groovy-templates</module>
<module>spring-boot-sample-web-method-security</module>
<module>spring-boot-sample-web-mustache</module>
<module>spring-boot-sample-web-secure</module>
<module>spring-boot-sample-web-secure-custom</module>
<module>spring-boot-sample-web-secure-jdbc</module>

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.2.2.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-web-mustache</artifactId>
<name>spring-boot-sample-web-mustache</name>
<description>Spring Boot Web FreeMarker Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mustache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,29 @@
/*
* Copyright 2012-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 sample.mustache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SampleWebMustacheApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleWebMustacheApplication.class, args);
}
}

@ -0,0 +1,39 @@
/*
* Copyright 2012-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 sample.mustache;
import java.util.Date;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WelcomeController {
@Value("${application.message:Hello World}")
private String message = "Hello World";
@RequestMapping("/")
public String welcome(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", this.message);
return "welcome";
}
}

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<body>
Something went wrong: {{status}} {{error}}
</body>
</html>

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<body>
Date: {{time.date}}
<br>
Time: {{time.time}}
<br>
Message: {{message}}
</body>
</html>

@ -0,0 +1,80 @@
/*
* Copyright 2012-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 sample.mustache;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Basic integration tests for FreeMarker application.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleWebMustacheApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@DirtiesContext
public class SampleWebMustacheApplicationTests {
@Value("${local.server.port}")
private int port;
@Test
public void testMustacheTemplate() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.port, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body:\n" + entity.getBody(),
entity.getBody().contains("Hello, Andy"));
}
@Test
public void testMustacheErrorTemplate() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
ResponseEntity<String> responseEntity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/does-not-exist", HttpMethod.GET,
requestEntity, String.class);
assertEquals(HttpStatus.NOT_FOUND, responseEntity.getStatusCode());
assertTrue("Wrong body:\n" + responseEntity.getBody(), responseEntity.getBody()
.contains("Something went wrong: 404 Not Found"));
}
}

@ -45,6 +45,7 @@
<module>spring-boot-starter-log4j2</module>
<module>spring-boot-starter-mail</module>
<module>spring-boot-starter-mobile</module>
<module>spring-boot-starter-mustache</module>
<module>spring-boot-starter-actuator</module>
<module>spring-boot-starter-parent</module>
<module>spring-boot-starter-redis</module>

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.2.2.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-mustache</artifactId>
<name>spring-boot-starter-mustache</name>
<description>Spring Boot FreeMarker Starter</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
Loading…
Cancel
Save