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);
+
+}