diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 2e199f400c..9b8c8b05e5 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -66,6 +66,11 @@ jetty-webapp true + + org.freemarker + freemarker + true + org.hibernate hibernate-entitymanager diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java new file mode 100644 index 0000000000..2074ad54bd --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfiguration.java @@ -0,0 +1,149 @@ +/* + * 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.freemarker; + +import java.util.Map; +import java.util.Properties; + +import javax.annotation.PostConstruct; +import javax.servlet.Servlet; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +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.web.WebMvcAutoConfiguration; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.EnvironmentAware; +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.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; +import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; +import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for FreeMarker. + * + * @author Andy Wilkinson + */ +@Configuration +@ConditionalOnClass(freemarker.template.Configuration.class) +@ConditionalOnWebApplication +@AutoConfigureAfter(WebMvcAutoConfiguration.class) +public class FreeMarkerAutoConfiguration { + + public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/"; + + public static final String DEFAULT_PREFIX = ""; + + public static final String DEFAULT_SUFFIX = ".ftl"; + + @Configuration + public static class FreemarkerConfigurerConfiguration implements EnvironmentAware { + + @Autowired + private final ResourceLoader resourceLoader = new DefaultResourceLoader(); + + private RelaxedPropertyResolver environment; + + @Override + public void setEnvironment(Environment environment) { + this.environment = new RelaxedPropertyResolver(environment, + "spring.freeMarker."); + } + + @PostConstruct + public void checkTemplateLocationExists() { + Boolean checkTemplateLocation = this.environment.getProperty( + "checkTemplateLocation", Boolean.class, true); + if (checkTemplateLocation) { + Resource resource = this.resourceLoader.getResource(this.environment + .getProperty("templateLoaderPath", DEFAULT_TEMPLATE_LOADER_PATH)); + Assert.state(resource.exists(), "Cannot find template location: " + + resource + " (please add some templates " + + "or check your FreeMarker configuration)"); + } + } + + @Bean + @ConditionalOnMissingBean(name = "freeMarkerConfigurer") + public FreeMarkerConfigurer freeMarkerConfigurer() { + FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer(); + freeMarkerConfigurer.setTemplateLoaderPath(this.environment.getProperty( + "templateLoaderPath", DEFAULT_TEMPLATE_LOADER_PATH)); + freeMarkerConfigurer.setDefaultEncoding(this.environment.getProperty( + "templateEncoding", "UTF-8")); + Map settingsMap = this.environment + .getSubProperties("settings."); + Properties settings = new Properties(); + settings.putAll(settingsMap); + freeMarkerConfigurer.setFreemarkerSettings(settings); + return freeMarkerConfigurer; + } + } + + @Configuration + @ConditionalOnClass(Servlet.class) + public static class FreemarkerViewResolverConfiguration implements EnvironmentAware { + + private RelaxedPropertyResolver environment; + + @Override + public void setEnvironment(Environment environment) { + this.environment = new RelaxedPropertyResolver(environment, + "spring.freeMarker."); + } + + @Bean + @ConditionalOnMissingBean(name = "freeMarkerViewResolver") + public FreeMarkerViewResolver freeMarkerViewResolver() { + FreeMarkerViewResolver resolver = new FreeMarkerViewResolver(); + resolver.setPrefix(this.environment.getProperty("prefix", DEFAULT_PREFIX)); + resolver.setSuffix(this.environment.getProperty("suffix", DEFAULT_SUFFIX)); + resolver.setCache(this.environment.getProperty("cache", Boolean.class, true)); + resolver.setContentType(this.environment.getProperty("contentType", + "text/html")); + resolver.setViewNames(this.environment.getProperty("viewNames", + String[].class)); + resolver.setExposeRequestAttributes(this.environment.getProperty( + "exposeRequestAttributes", Boolean.class, false)); + resolver.setAllowRequestOverride(this.environment.getProperty( + "allowRequestOverride", Boolean.class, false)); + resolver.setExposeSessionAttributes(this.environment.getProperty( + "exposeSessionAttributes", Boolean.class, false)); + resolver.setAllowSessionOverride(this.environment.getProperty( + "allowSessionOverride", Boolean.class, false)); + resolver.setExposeSpringMacroHelpers(this.environment.getProperty( + "exposeSpringMacroHelpers", Boolean.class, false)); + resolver.setRequestContextAttribute(this.environment + .getProperty("requestContextAttribute")); + + // This resolver acts as a fallback resolver (e.g. like a + // InternalResourceViewResolver) so it needs to have low precedence + resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5); + + return resolver; + } + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerTemplateAvailabilityProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerTemplateAvailabilityProvider.java new file mode 100644 index 0000000000..9bdf78c768 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerTemplateAvailabilityProvider.java @@ -0,0 +1,50 @@ +/* + * 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.freemarker; + +import org.springframework.boot.web.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 FreeMarker view templates + * + * @author Andy Wilkinson + * + */ +public class FreeMarkerTemplateAvailabilityProvider implements + TemplateAvailabilityProvider { + + @Override + public boolean isTemplateAvailable(String view, Environment environment, + ClassLoader classLoader, ResourceLoader resourceLoader) { + if (ClassUtils.isPresent("freemarker.template.Configuration", + classLoader)) { + String loaderPath = environment.getProperty("spring.freemarker.templateLoaderPath", + FreeMarkerAutoConfiguration.DEFAULT_TEMPLATE_LOADER_PATH); + String prefix = environment.getProperty("spring.freemarker.prefix", + FreeMarkerAutoConfiguration.DEFAULT_PREFIX); + String suffix = environment.getProperty("spring.freemarker.suffix", + FreeMarkerAutoConfiguration.DEFAULT_SUFFIX); + return resourceLoader.getResource(loaderPath + prefix + view + suffix).exists(); + } + return false; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java index 2664b78f14..2ee9a77a3a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java @@ -50,8 +50,9 @@ import org.thymeleaf.templateresolver.TemplateResolver; /** * {@link EnableAutoConfiguration Auto-configuration} for Thymeleaf. - * + * * @author Dave Syer + * @author Andy Wilkinson */ @Configuration @ConditionalOnClass(SpringTemplateEngine.class) @@ -108,16 +109,6 @@ public class ThymeleafAutoConfiguration { protected SpringResourceResourceResolver thymeleafResourceResolver() { return new SpringResourceResourceResolver(); } - - public static boolean templateExists(Environment environment, - ResourceLoader resourceLoader, String view) { - String prefix = environment.getProperty("spring.thymeleaf.prefix", - ThymeleafAutoConfiguration.DEFAULT_PREFIX); - String suffix = environment.getProperty("spring.thymeleaf.suffix", - ThymeleafAutoConfiguration.DEFAULT_SUFFIX); - return resourceLoader.getResource(prefix + view + suffix).exists(); - } - } @Configuration diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafTemplateAvailabilityProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafTemplateAvailabilityProvider.java new file mode 100644 index 0000000000..9d53909395 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafTemplateAvailabilityProvider.java @@ -0,0 +1,49 @@ +/* + * 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.thymeleaf; + +import org.springframework.boot.web.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 + * Thymeleaf view templates + * + * @author Andy Wilkinson + * + */ +public class ThymeleafTemplateAvailabilityProvider implements + TemplateAvailabilityProvider { + + @Override + public boolean isTemplateAvailable(String view, Environment environment, + ClassLoader classLoader, ResourceLoader resourceLoader) { + if (ClassUtils.isPresent("org.thymeleaf.spring4.SpringTemplateEngine", + classLoader)) { + String prefix = environment.getProperty("spring.thymeleaf.prefix", + ThymeleafAutoConfiguration.DEFAULT_PREFIX); + String suffix = environment.getProperty("spring.thymeleaf.suffix", + ThymeleafAutoConfiguration.DEFAULT_SUFFIX); + return resourceLoader.getResource(prefix + view + suffix).exists(); + } + + return false; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java index 2435c974cf..85a1d2668f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.web; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.servlet.Servlet; @@ -33,21 +34,21 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration.DefaultTemplateResolverConfiguration; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.ErrorPage; +import org.springframework.boot.web.TemplateAvailabilityProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.expression.MapAccessor; import org.springframework.core.Ordered; +import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.util.ClassUtils; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; import org.springframework.web.servlet.DispatcherServlet; @@ -57,8 +58,9 @@ import org.springframework.web.servlet.view.BeanNameViewResolver; /** * {@link EnableAutoConfiguration Auto-configuration} to render errors via a MVC error * controller. - * + * * @author Dave Syer + * @author Andy Wilkinson */ @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) @ConditionalOnWebApplication @@ -116,22 +118,20 @@ public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustom @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (ClassUtils.isPresent("org.thymeleaf.spring4.SpringTemplateEngine", - context.getClassLoader())) { - if (DefaultTemplateResolverConfiguration.templateExists( - context.getEnvironment(), context.getResourceLoader(), "error")) { - return ConditionOutcome - .noMatch("Thymeleaf template found for error view"); + List availabilityProviders = SpringFactoriesLoader + .loadFactories(TemplateAvailabilityProvider.class, + context.getClassLoader()); + + for (TemplateAvailabilityProvider availabilityProvider : availabilityProviders) { + if (availabilityProvider.isTemplateAvailable("error", + context.getEnvironment(), context.getClassLoader(), + context.getResourceLoader())) { + return ConditionOutcome.noMatch("Template from " + + availabilityProvider + " found for error view"); } } - if (ClassUtils.isPresent("org.apache.jasper.compiler.JspConfig", - context.getClassLoader())) { - if (WebMvcAutoConfiguration.templateExists(context.getEnvironment(), - context.getResourceLoader(), "error")) { - return ConditionOutcome.noMatch("JSP template found for error view"); - } - } - return ConditionOutcome.match("no error template view detected"); + + return ConditionOutcome.match("No error template view detected"); }; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JspTemplateAvailabilityProvider.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JspTemplateAvailabilityProvider.java new file mode 100644 index 0000000000..e53b08a1fc --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JspTemplateAvailabilityProvider.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 org.springframework.boot.autoconfigure.web; + +import org.springframework.boot.web.TemplateAvailabilityProvider; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ClassUtils; + +public class JspTemplateAvailabilityProvider implements TemplateAvailabilityProvider { + + @Override + public boolean isTemplateAvailable(String view, Environment environment, + ClassLoader classLoader, ResourceLoader resourceLoader) { + if (ClassUtils.isPresent("org.apache.jasper.compiler.JspConfig", classLoader)) { + String prefix = environment.getProperty("spring.view.prefix", + WebMvcAutoConfiguration.DEFAULT_PREFIX); + String suffix = environment.getProperty("spring.view.suffix", + WebMvcAutoConfiguration.DEFAULT_SUFFIX); + return resourceLoader.getResource(prefix + view + suffix).exists(); + } + return false; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index 408b81ffb2..6e774efcd0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -43,7 +43,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -75,6 +74,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; * * @author Phillip Webb * @author Dave Syer + * @author Andy Wilkinson */ @Configuration @ConditionalOnWebApplication @@ -118,15 +118,6 @@ public class WebMvcAutoConfiguration { return new HiddenHttpMethodFilter(); } - public static boolean templateExists(Environment environment, - ResourceLoader resourceLoader, String view) { - String prefix = environment.getProperty("spring.view.prefix", - WebMvcAutoConfiguration.DEFAULT_PREFIX); - String suffix = environment.getProperty("spring.view.suffix", - WebMvcAutoConfiguration.DEFAULT_SUFFIX); - return resourceLoader.getResource(prefix + view + suffix).exists(); - } - // Defined as a nested config to ensure WebMvcConfigurerAdapter it not read when not // on the classpath @Configuration 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 41cb38870d..eb7ad634b0 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -11,9 +11,9 @@ org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.MongoRepositoriesAutoConfiguration,\ -org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ -org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\ +org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ +org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\ @@ -23,6 +23,7 @@ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\ +org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ @@ -34,3 +35,10 @@ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguratio org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration + +# Template availability providers +org.springframework.boot.web.TemplateAvailabilityProvider=\ +org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\ +org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\ +org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider + diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfigurationTests.java new file mode 100644 index 0000000000..7f37a2d295 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerAutoConfigurationTests.java @@ -0,0 +1,205 @@ +/* + * 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.freemarker; + +import java.io.File; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.support.RequestContext; +import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; +import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link FreeMarkerAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class FreeMarkerAutoConfigurationTests { + + private AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + + @Before + public void registerServletContext() { + MockServletContext servletContext = new MockServletContext(); + this.context.setServletContext(servletContext); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void defaultConfiguration() { + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + assertNotNull(this.context.getBean(FreeMarkerViewResolver.class)); + + assertNotNull(this.context.getBean(FreeMarkerConfigurer.class)); + } + + @Test(expected = BeanCreationException.class) + public void nonExistentTemplateLocation() { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.freemarker.templateLoaderPath:classpath:/does-not-exist/"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + } + + @Test + public void emptyTemplateLocation() { + new File("target/test-classes/templates/empty-directory").mkdir(); + + EnvironmentTestUtils + .addEnvironment(this.context, + "spring.freemarker.templateLoaderPath:classpath:/templates/empty-directory/"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + } + + @Test + public void defaultViewResolution() throws Exception { + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + MockHttpServletResponse response = render("home"); + String result = response.getContentAsString(); + + assertTrue("Wrong output: " + result, result.contains("home")); + assertEquals("text/html", response.getContentType()); + } + + @Test + public void customContentType() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.freemarker.contentType:application/json"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + MockHttpServletResponse response = render("home"); + String result = response.getContentAsString(); + + assertTrue("Wrong output: " + result, result.contains("home")); + assertEquals("application/json", response.getContentType()); + } + + @Test + public void customPrefix() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.freemarker.prefix:prefix/"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + MockHttpServletResponse response = render("prefixed"); + String result = response.getContentAsString(); + + assertTrue("Wrong output: " + result, result.contains("prefixed")); + } + + @Test + public void customSuffix() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.freemarker.suffix:.freemarker"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + MockHttpServletResponse response = render("suffixed"); + String result = response.getContentAsString(); + + assertTrue("Wrong output: " + result, result.contains("suffixed")); + } + + @Test + public void customTemplateLoaderPath() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.freemarker.templateLoaderPath:classpath:/custom-templates/"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + MockHttpServletResponse response = render("custom"); + String result = response.getContentAsString(); + + assertTrue("Wrong output: " + result, result.contains("custom")); + } + + @Test + public void disableCache() { + EnvironmentTestUtils + .addEnvironment(this.context, "spring.freemarker.cache:false"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + assertEquals(0, this.context.getBean(FreeMarkerViewResolver.class) + .getCacheLimit()); + } + + @SuppressWarnings("deprecation") + @Test + public void customFreeMarkerSettings() { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.freemarker.settings.boolean_format:yup,nope"); + + this.context.register(FreeMarkerAutoConfiguration.class); + this.context.refresh(); + + assertEquals("yup,nope", this.context.getBean(FreeMarkerConfigurer.class) + .getConfiguration().getSetting("boolean_format")); + } + + private MockHttpServletResponse render(String viewName) throws Exception { + + View view = this.context.getBean(FreeMarkerViewResolver.class).resolveViewName( + viewName, Locale.UK); + assertNotNull(view); + + HttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, + this.context); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + view.render(null, request, response); + + return response; + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerTemplateAvailabilityProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerTemplateAvailabilityProviderTests.java new file mode 100644 index 0000000000..3a02e833fb --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/freemarker/FreeMarkerTemplateAvailabilityProviderTests.java @@ -0,0 +1,77 @@ +/* + * 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.freemarker; + +import org.junit.Test; +import org.springframework.boot.web.TemplateAvailabilityProvider; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.env.MockEnvironment; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link FreeMarkerTemplateAvailabilityProvider}. + * + * @author Andy Wilkinson + */ +public class FreeMarkerTemplateAvailabilityProviderTests { + + private final TemplateAvailabilityProvider provider = new FreeMarkerTemplateAvailabilityProvider(); + + private final ResourceLoader resourceLoader = new DefaultResourceLoader(); + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + public void availabilityOfTemplateInDefaultLocation() { + assertTrue(this.provider.isTemplateAvailable("home", this.environment, getClass() + .getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateThatDoesNotExist() { + assertFalse(this.provider.isTemplateAvailable("whatever", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateWithCustomLoaderPath() { + this.environment.setProperty("spring.freemarker.templateLoaderPath", + "classpath:/custom-templates/"); + + assertTrue(this.provider.isTemplateAvailable("custom", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateWithCustomPrefix() { + this.environment.setProperty("spring.freemarker.prefix", "prefix/"); + + assertTrue(this.provider.isTemplateAvailable("prefixed", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateWithCustomSuffix() { + this.environment.setProperty("spring.freemarker.suffix", ".freemarker"); + + assertTrue(this.provider.isTemplateAvailable("suffixed", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafTemplateAvailabilityProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafTemplateAvailabilityProviderTests.java new file mode 100644 index 0000000000..717a04473b --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafTemplateAvailabilityProviderTests.java @@ -0,0 +1,69 @@ +/* + * 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.thymeleaf; + +import org.junit.Test; +import org.springframework.boot.web.TemplateAvailabilityProvider; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.env.MockEnvironment; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link ThymeleafTemplateAvailabilityProvider}. + * + * @author Andy Wilkinson + */ +public class ThymeleafTemplateAvailabilityProviderTests { + + private final TemplateAvailabilityProvider provider = new ThymeleafTemplateAvailabilityProvider(); + + private final ResourceLoader resourceLoader = new DefaultResourceLoader(); + + private final MockEnvironment environment = new MockEnvironment(); + + @Test + public void availabilityOfTemplateInDefaultLocation() { + assertTrue(this.provider.isTemplateAvailable("home", this.environment, getClass() + .getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateThatDoesNotExist() { + assertFalse(this.provider.isTemplateAvailable("whatever", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateWithCustomPrefix() { + this.environment.setProperty("spring.thymeleaf.prefix", + "classpath:/custom-templates/"); + + assertTrue(this.provider.isTemplateAvailable("custom", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } + + @Test + public void availabilityOfTemplateWithCustomSuffix() { + this.environment.setProperty("spring.thymeleaf.suffix", ".thymeleaf"); + + assertTrue(this.provider.isTemplateAvailable("suffixed", this.environment, + getClass().getClassLoader(), this.resourceLoader)); + } +} diff --git a/spring-boot-autoconfigure/src/test/resources/custom-templates/custom.ftl b/spring-boot-autoconfigure/src/test/resources/custom-templates/custom.ftl new file mode 100644 index 0000000000..8f4052a5c9 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/custom-templates/custom.ftl @@ -0,0 +1 @@ +custom \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/custom-templates/custom.html b/spring-boot-autoconfigure/src/test/resources/custom-templates/custom.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-autoconfigure/src/test/resources/templates/home.ftl b/spring-boot-autoconfigure/src/test/resources/templates/home.ftl new file mode 100644 index 0000000000..0247178b6e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/templates/home.ftl @@ -0,0 +1 @@ +home \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/templates/home.html b/spring-boot-autoconfigure/src/test/resources/templates/home.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-autoconfigure/src/test/resources/templates/prefix/prefixed.ftl b/spring-boot-autoconfigure/src/test/resources/templates/prefix/prefixed.ftl new file mode 100644 index 0000000000..ba8db465da --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/templates/prefix/prefixed.ftl @@ -0,0 +1 @@ +prefixed \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/templates/suffixed.freemarker b/spring-boot-autoconfigure/src/test/resources/templates/suffixed.freemarker new file mode 100644 index 0000000000..dcce46b027 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/templates/suffixed.freemarker @@ -0,0 +1 @@ +suffixed \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/test/resources/templates/suffixed.thymeleaf b/spring-boot-autoconfigure/src/test/resources/templates/suffixed.thymeleaf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 2877b9f374..e66a6e78eb 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -51,6 +51,7 @@ 1.4 1.6 1.3.0-beta14 + 2.3.20 7.0.1 1.6 2.3.0-rc-2 @@ -351,6 +352,11 @@ gemfire ${gemfire.version} + + org.freemarker + freemarker + ${freemarker.version} + org.hamcrest hamcrest-library diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 9c2e5e1c60..15abb1e057 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -80,6 +80,22 @@ content into your application; rather pick only the properties that you need. spring.thymeleaf.content-type=text/html # ;charset= is added spring.thymeleaf.cache=true # set to false for hot refresh + # FREEMARKER ({sc-spring-boot-autoconfigure}}/freemarker/FreeMarkerAutoConfiguration.{sc-ext}[FreeMarkerAutoConfiguration]) + spring.freemarker.allowRequestOverride=false + spring.freemarker.allowSessionOverride=false + spring.freemarker.cache=true + spring.freemarker.checkTemplateLocation=true + spring.freemarker.contentType=text/html + spring.freemarker.exposeRequestAttributes=false + spring.freemarker.exposeSessionAttributes=false + spring.freemarker.exposeSpringMacroHelpers=false + spring.freemarker.prefix= + spring.freemarker.requestContextAttribute= + spring.freemarker.suffix= + spring.freemarker.templateEncoding=UTF-8 + spring.freemarker.templateLoaderPath=classpath:/templates/ + spring.freemarker.viewNames= + # INTERNATIONALIZATION ({sc-spring-boot-autoconfigure}/MessageSourceAutoConfiguration.{sc-ext}[MessageSourceAutoConfiguration]) spring.messages.basename=messages spring.messages.encoding=UTF-8 diff --git a/spring-boot-docs/src/main/asciidoc/appendix-auto-configuration-classes.adoc b/spring-boot-docs/src/main/asciidoc/appendix-auto-configuration-classes.adoc index a77177b86b..1a81322ee4 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-auto-configuration-classes.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-auto-configuration-classes.adoc @@ -38,6 +38,9 @@ The following auto-configuration classes are from the `spring-boot-autoconfigure |{sc-spring-boot-autoconfigure}/web/EmbeddedServletContainerAutoConfiguration.{sc-ext}[EmbeddedServletContainerAutoConfiguration] |{dc-spring-boot-autoconfigure}/web/EmbeddedServletContainerAutoConfiguration.{dc-ext}[javadoc] +|{sc-spring-boot-autoconfigure}/freemarker/FreeMarkerAutoConfiguration.{sc-ext}[FreeMarkerAutoConfiguration] +|{dc-spring-boot-autoconfigure}/freemarker/FreeMarkerAutoConfiguration.{dc-ext}[javadoc] + |{sc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{sc-ext}[HibernateJpaAutoConfiguration] |{dc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{dc-ext}[javadoc] diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index f87d6e909b..de13c412f1 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -809,8 +809,16 @@ added. `spring.thymeleaf.suffix`, defaults ``classpath:/templates/'' and ``.html'' respectively). It can be overridden by providing a bean of the same name. -Checkout {sc-spring-boot-autoconfigure}/web/WebMvcAutoConfiguration.{sc-ext}[`WebMvcAutoConfiguration`] -and {sc-spring-boot-autoconfigure}/thymeleaf/ThymeleafAutoConfiguration.{sc-ext}[`ThymeleafAutoConfiguration`] +* If you use FreeMarker you will also have a `FreeMarkerViewResolver` with id + ``freeMarkerViewResolver''. It looks for resources in a loader path (externalized to + `spring.freemarker.templateLoaderPath`, default ``classpath:/templates/'') by + surrounding the view name with a prefix and suffix (externalized to `spring.freemarker.prefix` + and `spring.freemarker.suffix`, defaults ``'' and ``.ftl'' respectively). It can be overriden + by providing a bean of the same name. + +Check out {sc-spring-boot-autoconfigure}/web/WebMvcAutoConfiguration.{sc-ext}[`WebMvcAutoConfiguration`], +{sc-spring-boot-autoconfigure}/thymeleaf/ThymeleafAutoConfiguration.{sc-ext}[`ThymeleafAutoConfiguration`], and +{sc-spring-boot-autoconfigure}/freemarker/FreeMarkerAutoConfiguration.{sc-ext}['FreeMarkerAutoConfiguration'] @@ -1145,11 +1153,11 @@ you encounter a server error (machine clients consuming JSON and other media typ see a sensible response with the right error code). To switch it off you can set `error.whitelabel.enabled=false`, but normally in addition or alternatively to that you will want to add your own error page replacing the whitelabel one. If you are using -Thymeleaf you can do this by adding an `error.html` template. In general what you need is -a `View` that resolves with a name of `error`, and/or a `@Controller` that handles the -`/error` path. Unless you replaced some of the default configuration you should find a -`BeanNameViewResolver` in your `ApplicationContext` so a `@Bean` with id `error` would be -a simple way of doing that. +Thymeleaf or FreeMarker you can do this by adding an `error.html` or `error.ftl` template +respectively. In general what you need is a `View` that resolves with a name of `error`, +and/or a `@Controller` that handles the `/error` path. Unless you replaced some of the +default configuration you should find a `BeanNameViewResolver` in your `ApplicationContext` +so a `@Bean` with id `error` would be a simple way of doing that. Look at {sc-spring-boot-autoconfigure}/web/ErrorMvcAutoConfiguration.{sc-ext}[`ErrorMvcAutoConfiguration`] for more options. @@ -1251,7 +1259,15 @@ tools. === Reload Thymeleaf templates without restarting the container If you are using Thymeleaf, then set `spring.thymeleaf.cache` to `false`. See {sc-spring-boot-autoconfigure}/thymeleaf/ThymeleafAutoConfiguration.{sc-ext}[`ThymeleafAutoConfiguration`] -for other template customization options. +for other Thymeleaf customization options. + + + +[[howto-reload-freemarker-content]] +=== Reload FreeMarker templates without restarting the container +If you are using FreeMarker, then set `spring.freemarker.cache` to `false`. See +{sc-spring-boot-autoconfigure}/freemarker/FreeMarkerAutoConfiguration.{sc-ext}[`FreeMarkerAutoConfiguration`] +for other FreeMarker customization options. diff --git a/spring-boot-docs/src/main/asciidoc/index.adoc b/spring-boot-docs/src/main/asciidoc/index.adoc index 5498977d2b..e7d603074f 100644 --- a/spring-boot-docs/src/main/asciidoc/index.adoc +++ b/spring-boot-docs/src/main/asciidoc/index.adoc @@ -1,5 +1,5 @@ = Spring Boot Reference Guide -Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; +Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; :doctype: book :toc: :toclevels: 4 diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index c9e72a69f9..c1377c75fa 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -892,14 +892,16 @@ and it will be silently ignored by most build tools if you generate a jar. [[boot-features-spring-mvc-template-engines]] ==== Template engines As well as REST web services, you can also use Spring MVC to serve dynamic HTML content. -Spring MVC supports a variety of templating technologies including: velocity, freemarker, +Spring MVC supports a variety of templating technologies including: Velocity, FreeMarker, and JSPs. Many other templating engines also ship their own Spring MVC integrations. -Spring Boot includes auto-configuration support for the Thymeleaf templating engine. -Thymeleaf is an XML/XHTML/HTML5 template engine that can work both in web and non-web -environments. It allows you to create natural templates that can be correctly displayed -by browsers and therefore work also as static prototypes. Thymeleaf templates will be -picked up automatically from `src/main/resources/templates`. +Spring Boot includes auto-configuration support for the Thymeleaf and FreeMarker +templating engines. Thymeleaf is an XML/XHTML/HTML5 template engine that can work both in +web and non-web environments. If allows you to create natural templates that can be +correctly displayed by browsers and therefore work also as static prototypes. Both +FreeMarker and Thymeleaf templates will be picked up automatically from +`src/main/resources/templates`. + TIP: JSPs should be avoided if possible, there are several <> when using them with embedded diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index b3dd19bb20..9feef384d5 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -229,6 +229,9 @@ and Hibernate. |`spring-boot-starter-data-rest` |Support for exposing Spring Data repositories over REST via `spring-data-rest-webmvc`. +|`spring-boot-starter-freemarker` +|Support for the FreeMarker templating engine + |`spring-boot-starter-integration` |Support for common `spring-integration` modules. diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 289f89c957..83e0f32636 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -41,6 +41,7 @@ spring-boot-sample-tomcat spring-boot-sample-tomcat-multi-connectors spring-boot-sample-traditional + spring-boot-sample-web-freemarker spring-boot-sample-web-method-security spring-boot-sample-web-secure spring-boot-sample-web-static diff --git a/spring-boot-samples/spring-boot-sample-web-freemarker/pom.xml b/spring-boot-samples/spring-boot-sample-web-freemarker/pom.xml new file mode 100644 index 0000000000..94103e744a --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.1.0.BUILD-SNAPSHOT + + spring-boot-sample-web-freemarker + Spring Boot Web FreeMarker Sample + 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-freemarker + + + 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-freemarker/src/main/java/sample/freemarker/SampleWebFreeMarkerApplication.java b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/java/sample/freemarker/SampleWebFreeMarkerApplication.java new file mode 100644 index 0000000000..955aadbc93 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/java/sample/freemarker/SampleWebFreeMarkerApplication.java @@ -0,0 +1,33 @@ +/* + * 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.freemarker; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration +@ComponentScan +public class SampleWebFreeMarkerApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleWebFreeMarkerApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/java/sample/freemarker/WelcomeController.java b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/java/sample/freemarker/WelcomeController.java new file mode 100644 index 0000000000..8a97d3b81d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/java/sample/freemarker/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.freemarker; + +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-freemarker/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/resources/application.properties new file mode 100644 index 0000000000..edea37a1cf --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/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-freemarker/src/main/resources/templates/error.ftl b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/resources/templates/error.ftl new file mode 100644 index 0000000000..ace4211012 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/resources/templates/error.ftl @@ -0,0 +1,9 @@ + + + + + + Something went wrong: ${status} ${error} + + + diff --git a/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/resources/templates/welcome.ftl b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/resources/templates/welcome.ftl new file mode 100644 index 0000000000..0accea96a7 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/src/main/resources/templates/welcome.ftl @@ -0,0 +1,13 @@ + + + + + + Date: ${time?date} +
+ Time: ${time?time} +
+ Message: ${message} + + + diff --git a/spring-boot-samples/spring-boot-sample-web-freemarker/src/test/java/sample/freemarker/SampleWebFreeMarkerApplicationTests.java b/spring-boot-samples/spring-boot-sample-web-freemarker/src/test/java/sample/freemarker/SampleWebFreeMarkerApplicationTests.java new file mode 100644 index 0000000000..ffd0882a1e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-web-freemarker/src/test/java/sample/freemarker/SampleWebFreeMarkerApplicationTests.java @@ -0,0 +1,76 @@ +/* + * 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.freemarker; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +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 = SampleWebFreeMarkerApplication.class) +@WebAppConfiguration +@IntegrationTest +@DirtiesContext +public class SampleWebFreeMarkerApplicationTests { + + @Test + public void testFreeMarkerTemplate() throws Exception { + ResponseEntity entity = new TestRestTemplate().getForEntity( + "http://localhost:8080", String.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + assertTrue("Wrong body:\n" + entity.getBody(), + entity.getBody().contains("Hello, Andy")); + } + + @Test + public void testFreeMarkerErrorTemplate() 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:8080/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 c31308c2ed..5db4ec71f4 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -29,6 +29,7 @@ spring-boot-starter-data-mongodb spring-boot-starter-data-neo4j spring-boot-starter-data-rest + spring-boot-starter-freemarker spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-jetty diff --git a/spring-boot-starters/spring-boot-starter-freemarker/pom.xml b/spring-boot-starters/spring-boot-starter-freemarker/pom.xml new file mode 100644 index 0000000000..033541a1c0 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-freemarker/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 1.1.0.BUILD-SNAPSHOT + + spring-boot-starter-freemarker + Spring Boot FreeMarker Starter + Spring Boot FreeMarker Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + ${project.groupId} + spring-boot-starter-web + ${project.version} + + + org.freemarker + freemarker + + + org.springframework + spring-context-support + + + diff --git a/spring-boot-starters/spring-boot-starter-freemarker/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-freemarker/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000..3f52c55a60 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-freemarker/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: freemarker,spring-context-support \ No newline at end of file diff --git a/spring-boot-starters/spring-boot-starter-parent/pom.xml b/spring-boot-starters/spring-boot-starter-parent/pom.xml index 09980f5928..a18cfc637a 100644 --- a/spring-boot-starters/spring-boot-starter-parent/pom.xml +++ b/spring-boot-starters/spring-boot-starter-parent/pom.xml @@ -101,6 +101,11 @@ spring-boot-starter-data-rest ${spring-boot.version}
+ + org.springframework.boot + spring-boot-starter-freemarker + ${spring-boot.version} + org.springframework.boot spring-boot-starter-integration diff --git a/spring-boot/src/main/java/org/springframework/boot/web/TemplateAvailabilityProvider.java b/spring-boot/src/main/java/org/springframework/boot/web/TemplateAvailabilityProvider.java new file mode 100644 index 0000000000..c9eea3d7da --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/TemplateAvailabilityProvider.java @@ -0,0 +1,38 @@ +/* + * 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.web; + +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; + +/** + * A {@code TemplateAvailabilityProvider} indicates the availability of view templates for + * a particular templating engine such as FreeMarker or Thymeleaf + * + * @author Andy Wilkinson + * + */ +public interface TemplateAvailabilityProvider { + + /** + * Returns {@code true} if a template is available for the given {@code view}, + * otherwise {@code false} is returned. + */ + boolean isTemplateAvailable(String view, Environment environment, + ClassLoader classLoader, ResourceLoader resourceLoader); + +}