From 2729c747ca715b5b4ebdb39afba37fa8044d86dd Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 9 Jan 2015 13:50:39 +0000 Subject: [PATCH] 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 --- spring-boot-autoconfigure/pom.xml | 5 + .../mustache/MustacheAutoConfiguration.java | 115 ++++++++++++++++ .../mustache/MustacheCompilerFactoryBean.java | 99 ++++++++++++++ .../MustacheEnvironmentCollector.java | 64 +++++++++ .../mustache/MustacheProperties.java | 60 ++++++++ .../MustacheResourceTemplateLoader.java | 83 ++++++++++++ .../MustacheTemplateAvailabilityProvider.java | 46 +++++++ .../mustache/web/MustacheView.java | 46 +++++++ .../mustache/web/MustacheViewResolver.java | 90 ++++++++++++ .../main/resources/META-INF/spring.factories | 2 + .../mustache/ApplicationTests.java | 128 ++++++++++++++++++ .../mustache/AutoApplicationTests.java | 102 ++++++++++++++ .../MustacheTemplateStandaloneTests.java | 62 +++++++++ .../mustache/MustacheViewResolverTests.java | 76 +++++++++++ .../mustache/MustacheViewTests.java | 64 +++++++++ .../resources/mustache-templates/content.html | 2 + .../resources/mustache-templates/foo.html | 1 + .../resources/mustache-templates/foo_de.html | 0 .../resources/mustache-templates/home.html | 9 ++ .../resources/mustache-templates/layout.html | 15 ++ .../resources/mustache-templates/partial.html | 15 ++ spring-boot-dependencies/pom.xml | 17 ++- spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-web-mustache/pom.xml | 48 +++++++ .../SampleWebMustacheApplication.java | 29 ++++ .../sample/mustache/WelcomeController.java | 39 ++++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/templates/error.html | 9 ++ .../src/main/resources/templates/welcome.html | 13 ++ .../SampleWebMustacheApplicationTests.java | 80 +++++++++++ spring-boot-starters/pom.xml | 1 + .../spring-boot-starter-mustache/pom.xml | 44 ++++++ .../main/resources/META-INF/spring.provides | 1 + 33 files changed, 1365 insertions(+), 2 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheCompilerFactoryBean.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheEnvironmentCollector.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheResourceTemplateLoader.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateAvailabilityProvider.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheView.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheViewResolver.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/ApplicationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/AutoApplicationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateStandaloneTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewResolverTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewTests.java create mode 100644 spring-boot-autoconfigure/src/test/resources/mustache-templates/content.html create mode 100644 spring-boot-autoconfigure/src/test/resources/mustache-templates/foo.html create mode 100644 spring-boot-autoconfigure/src/test/resources/mustache-templates/foo_de.html create mode 100644 spring-boot-autoconfigure/src/test/resources/mustache-templates/home.html create mode 100644 spring-boot-autoconfigure/src/test/resources/mustache-templates/layout.html create mode 100644 spring-boot-autoconfigure/src/test/resources/mustache-templates/partial.html create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/SampleWebMustacheApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/WelcomeController.java create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/error.html create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/welcome.html create mode 100644 spring-boot-samples/spring-boot-sample-web-mustache/src/test/java/sample/mustache/SampleWebMustacheApplicationTests.java create mode 100644 spring-boot-starters/spring-boot-starter-mustache/pom.xml create mode 100644 spring-boot-starters/spring-boot-starter-mustache/src/main/resources/META-INF/spring.provides diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index c6f9d6ed40..d64d4ae0ce 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -60,6 +60,11 @@ gson true + + com.samskivert + jmustache + true + org.flywaydb flyway-core diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfiguration.java new file mode 100644 index 0000000000..8e62efa04c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfiguration.java @@ -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; + } + + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheCompilerFactoryBean.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheCompilerFactoryBean.java new file mode 100644 index 0000000000..6433e6bd8e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheCompilerFactoryBean.java @@ -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 + * @Bean 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 { + + 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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheEnvironmentCollector.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheEnvironmentCollector.java new file mode 100644 index 0000000000..1447ee1d86 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheEnvironmentCollector.java @@ -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 target; + + @Override + public void setEnvironment(Environment environment) { + this.environment = (ConfigurableEnvironment) environment; + this.target = new HashMap(); + 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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheProperties.java new file mode 100644 index 0000000000..78b5c534b4 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheProperties.java @@ -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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheResourceTemplateLoader.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheResourceTemplateLoader.java new file mode 100644 index 0000000000..8f1da7191c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheResourceTemplateLoader.java @@ -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); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateAvailabilityProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateAvailabilityProvider.java new file mode 100644 index 0000000000..0bab6ae08a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateAvailabilityProvider.java @@ -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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheView.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheView.java new file mode 100644 index 0000000000..f89c192a87 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheView.java @@ -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 model, + HttpServletRequest request, HttpServletResponse response) throws Exception { + template.execute(model, response.getWriter()); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheViewResolver.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheViewResolver.java new file mode 100644 index 0000000000..995d400001 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/web/MustacheViewResolver.java @@ -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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index c3459b716a..1db59da3c7 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -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,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/ApplicationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/ApplicationTests.java new file mode 100644 index 0000000000..a6d91b3c60 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/ApplicationTests.java @@ -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 context = new HashMap(); + 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 model) { + model.put("time", new Date()); + model.put("message", "Hello World"); + model.put("title", "Hello App"); + return "home"; + } + + @RequestMapping("/partial") + public String layout(Map 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); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/AutoApplicationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/AutoApplicationTests.java new file mode 100644 index 0000000000..64ab771ad4 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/AutoApplicationTests.java @@ -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 model) { + model.put("time", new Date()); + model.put("message", "Hello World"); + model.put("title", "Hello App"); + return "home"; + } + + @RequestMapping("/partial") + public String layout(Map 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); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateStandaloneTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateStandaloneTests.java new file mode 100644 index 0000000000..5036c38956 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheTemplateStandaloneTests.java @@ -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 { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewResolverTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewResolverTests.java new file mode 100644 index 0000000000..f4167b3a9e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewResolverTests.java @@ -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"))); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewTests.java new file mode 100644 index 0000000000..5675683e05 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mustache/MustacheViewTests.java @@ -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()); + } + +} diff --git a/spring-boot-autoconfigure/src/test/resources/mustache-templates/content.html b/spring-boot-autoconfigure/src/test/resources/mustache-templates/content.html new file mode 100644 index 0000000000..3addef973e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/mustache-templates/content.html @@ -0,0 +1,2 @@ +

A Message

+
{{message}} at {{time}}
diff --git a/spring-boot-autoconfigure/src/test/resources/mustache-templates/foo.html b/spring-boot-autoconfigure/src/test/resources/mustache-templates/foo.html new file mode 100644 index 0000000000..18624afa99 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/mustache-templates/foo.html @@ -0,0 +1 @@ +Hello {{World}} \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/mustache-templates/foo_de.html b/spring-boot-autoconfigure/src/test/resources/mustache-templates/foo_de.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-autoconfigure/src/test/resources/mustache-templates/home.html b/spring-boot-autoconfigure/src/test/resources/mustache-templates/home.html new file mode 100644 index 0000000000..0e134df2ec --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/mustache-templates/home.html @@ -0,0 +1,9 @@ + + +{{title}} + + +

A Message

+
{{message}} at {{time}}
+ + \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/mustache-templates/layout.html b/spring-boot-autoconfigure/src/test/resources/mustache-templates/layout.html new file mode 100644 index 0000000000..762ded630f --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/mustache-templates/layout.html @@ -0,0 +1,15 @@ + + +{{title}} + + + +
{{#include}}{{body}}{{/include}}
+ + \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/mustache-templates/partial.html b/spring-boot-autoconfigure/src/test/resources/mustache-templates/partial.html new file mode 100644 index 0000000000..6bea208a58 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/mustache-templates/partial.html @@ -0,0 +1,15 @@ + + +{{title}} + + + +
{{>content}}
+ + \ No newline at end of file diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 614faac264..479a15f289 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -1,4 +1,6 @@ - + + 4.0.0 org.springframework.boot spring-boot-dependencies @@ -88,6 +90,7 @@ 2.14 9.2.4.v20141103 2.2.0.v201112011158 + 1.9 2.5 1.2.3 0.9.1 @@ -324,6 +327,11 @@ spring-boot-starter-mobile 1.2.2.BUILD-SNAPSHOT
+ + org.springframework.boot + spring-boot-starter-mustache + 1.2.2.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-redis @@ -483,6 +491,11 @@ json-path ${json-path.version} + + com.samskivert + jmustache + ${jmustache.version} + com.sun.mail javax.mail @@ -1613,7 +1626,7 @@ true - spring-milestones diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 3e4f78ca0f..08a469415d 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -68,6 +68,7 @@ spring-boot-sample-web-freemarker spring-boot-sample-web-groovy-templates spring-boot-sample-web-method-security + spring-boot-sample-web-mustache spring-boot-sample-web-secure spring-boot-sample-web-secure-custom spring-boot-sample-web-secure-jdbc diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/pom.xml b/spring-boot-samples/spring-boot-sample-web-mustache/pom.xml new file mode 100644 index 0000000000..dd060b9fed --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.2.2.BUILD-SNAPSHOT + + spring-boot-sample-web-mustache + spring-boot-sample-web-mustache + Spring Boot Web FreeMarker Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + / + + + + org.springframework.boot + spring-boot-starter-mustache + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/SampleWebMustacheApplication.java b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/SampleWebMustacheApplication.java new file mode 100644 index 0000000000..1956dddd3c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/SampleWebMustacheApplication.java @@ -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); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/WelcomeController.java b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/WelcomeController.java new file mode 100644 index 0000000000..e09adb5198 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/java/sample/mustache/WelcomeController.java @@ -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 model) { + model.put("time", new Date()); + model.put("message", this.message); + return "welcome"; + } + +} diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/application.properties new file mode 100644 index 0000000000..edea37a1cf --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/application.properties @@ -0,0 +1 @@ +application.message: Hello, Andy \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/error.html b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/error.html new file mode 100644 index 0000000000..d291da6217 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/error.html @@ -0,0 +1,9 @@ + + + + + + Something went wrong: {{status}} {{error}} + + + diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/welcome.html b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/welcome.html new file mode 100644 index 0000000000..713b7da0a1 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/src/main/resources/templates/welcome.html @@ -0,0 +1,13 @@ + + + + + + Date: {{time.date}} +
+ Time: {{time.time}} +
+ Message: {{message}} + + + diff --git a/spring-boot-samples/spring-boot-sample-web-mustache/src/test/java/sample/mustache/SampleWebMustacheApplicationTests.java b/spring-boot-samples/spring-boot-sample-web-mustache/src/test/java/sample/mustache/SampleWebMustacheApplicationTests.java new file mode 100644 index 0000000000..2ecbc8c364 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-mustache/src/test/java/sample/mustache/SampleWebMustacheApplicationTests.java @@ -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 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 requestEntity = new HttpEntity(headers); + + ResponseEntity 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")); + } + +} diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index e63c2af2f7..3436e2cd07 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -45,6 +45,7 @@ spring-boot-starter-log4j2 spring-boot-starter-mail spring-boot-starter-mobile + spring-boot-starter-mustache spring-boot-starter-actuator spring-boot-starter-parent spring-boot-starter-redis diff --git a/spring-boot-starters/spring-boot-starter-mustache/pom.xml b/spring-boot-starters/spring-boot-starter-mustache/pom.xml new file mode 100644 index 0000000000..7916970390 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-mustache/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 1.2.2.BUILD-SNAPSHOT + + spring-boot-starter-mustache + spring-boot-starter-mustache + Spring Boot FreeMarker Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + com.samskivert + jmustache + + + org.springframework + spring-core + + + commons-logging + commons-logging + + + + + diff --git a/spring-boot-starters/spring-boot-starter-mustache/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-mustache/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000..8e2ea4759f --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-mustache/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: jmustache \ No newline at end of file