Create spring-boot-mustache module

This commit is contained in:
Moritz Halbritter
2025-03-24 11:57:37 +01:00
committed by Phillip Webb
parent d75e5bef41
commit 61cb3c8d81
48 changed files with 176 additions and 113 deletions

View File

@@ -73,6 +73,7 @@ include "spring-boot-project:spring-boot-jsonb"
include "spring-boot-project:spring-boot-kafka"
include "spring-boot-project:spring-boot-liquibase"
include "spring-boot-project:spring-boot-mail"
include "spring-boot-project:spring-boot-mustache"
include "spring-boot-project:spring-boot-parent"
include "spring-boot-project:spring-boot-pulsar"
include "spring-boot-project:spring-boot-r2dbc"

View File

@@ -18,7 +18,6 @@ dependencies {
optional("com.google.code.gson:gson")
optional("com.oracle.database.jdbc:ucp11")
optional("com.oracle.database.jdbc:ojdbc11")
optional("com.samskivert:jmustache")
optional("com.zaxxer:HikariCP")
optional("io.netty:netty-tcnative-boringssl-static")
optional("io.projectreactor:reactor-tools")

View File

@@ -73,7 +73,6 @@ dependencies {
optional("com.oracle.database.jdbc:ojdbc11")
optional("com.oracle.database.jdbc:ucp11")
optional("com.querydsl:querydsl-core")
optional("com.samskivert:jmustache")
optional("io.lettuce:lettuce-core")
optional("io.projectreactor.netty:reactor-netty-http")
optional("io.r2dbc:r2dbc-spi")
@@ -217,6 +216,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-undertow"))
testImplementation(project(":spring-boot-project:spring-boot-mustache"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
testImplementation("ch.qos.logback:logback-classic")

View File

@@ -682,18 +682,6 @@
"reason": "Narayana support has moved to third party starter."
}
},
{
"name": "spring.mustache.prefix",
"defaultValue": "classpath:/templates/"
},
{
"name": "spring.mustache.reactive.media-types",
"defaultValue": "text/html;charset=UTF-8"
},
{
"name": "spring.mustache.suffix",
"defaultValue": ".mustache"
},
{
"name": "spring.mvc.converters.preferred-json-mapper",
"type": "java.lang.String",

View File

@@ -8,7 +8,6 @@ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer
# Template Availability Providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

View File

@@ -54,7 +54,6 @@ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfigurati
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration

View File

@@ -32,11 +32,11 @@ import reactor.core.scheduler.Schedulers;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.mustache.autoconfigure.MustacheAutoConfiguration;
import org.springframework.boot.reactor.netty.autoconfigure.NettyReactiveWebServerAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;

View File

@@ -2010,6 +2010,7 @@ bom {
"spring-boot-loader-classic",
"spring-boot-loader-tools",
"spring-boot-mail",
"spring-boot-mustache",
"spring-boot-properties-migrator",
"spring-boot-pulsar",
"spring-boot-r2dbc",

View File

@@ -85,6 +85,7 @@ dependencies {
autoConfiguration(project(path: ":spring-boot-project:spring-boot-kafka", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-liquibase", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-mail", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-mustache", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-pulsar", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-reactor-netty", configuration: "autoConfigurationMetadata"))
@@ -123,6 +124,7 @@ dependencies {
configurationProperties(project(path: ":spring-boot-project:spring-boot-kafka", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-liquibase", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-mail", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-mustache", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-pulsar", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-r2dbc", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-reactor-netty", configuration: "configurationPropertiesMetadata"))

View File

@@ -233,11 +233,11 @@ javadoc:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurati
It looks for resources in a loader path by surrounding the view name with a prefix and suffix (externalized to `spring.groovy.template.prefix` and `spring.groovy.template.suffix`).
The prefix and suffix have default values of '`classpath:/templates/`' and '`.tpl`', respectively.
You can override javadoc:org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver[] by providing a bean of the same name.
* If you use Mustache, you also have a javadoc:org.springframework.boot.web.servlet.view.MustacheViewResolver[] named '`mustacheViewResolver`'.
* If you use Mustache, you also have a javadoc:org.springframework.boot.mustache.servlet.view.MustacheViewResolver[] named '`mustacheViewResolver`'.
It looks for resources by surrounding the view name with a prefix and suffix.
The prefix is `spring.mustache.prefix`, and the suffix is `spring.mustache.suffix`.
The values of the prefix and suffix default to '`classpath:/templates/`' and '`.mustache`', respectively.
You can override javadoc:org.springframework.boot.web.servlet.view.MustacheViewResolver[] by providing a bean of the same name.
You can override javadoc:org.springframework.boot.mustache.servlet.view.MustacheViewResolver[] by providing a bean of the same name.
For more detail, see the following sections:

View File

@@ -0,0 +1,28 @@
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Mustache"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api("com.samskivert:jmustache")
api("org.springframework:spring-web")
optional("jakarta.servlet:jakarta.servlet-api")
optional("org.springframework:spring-webmvc")
optional("org.springframework:spring-webflux")
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-reactor-netty"))
testImplementation(project(":spring-boot-project:spring-boot-tomcat"))
testImplementation("io.projectreactor:reactor-test")
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.TemplateLoader;
@@ -36,7 +36,7 @@ import org.springframework.context.annotation.Import;
*
* @author Dave Syer
* @author Brian Clozel
* @since 1.2.2
* @since 4.0.0
*/
@AutoConfiguration
@ConditionalOnClass(Mustache.class)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -31,7 +31,7 @@ import org.springframework.util.MimeType;
* {@link ConfigurationProperties @ConfigurationProperties} for Mustache.
*
* @author Dave Syer
* @since 1.2.2
* @since 4.0.0
*/
@ConfigurationProperties("spring.mustache")
public class MustacheProperties {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import com.samskivert.mustache.Mustache.Compiler;
@@ -23,7 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.reactive.result.view.MustacheViewResolver;
import org.springframework.boot.mustache.reactive.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import java.io.InputStreamReader;
import java.io.Reader;
@@ -35,7 +35,7 @@ import org.springframework.core.io.ResourceLoader;
* partials (i.e. tiles-like features).
*
* @author Dave Syer
* @since 1.2.2
* @since 4.0.0
* @see Mustache
* @see Resource
*/

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import com.samskivert.mustache.Mustache.Compiler;
@@ -23,7 +23,7 @@ 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.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.boot.mustache.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.core.env.Environment;
@@ -27,7 +27,7 @@ import org.springframework.util.ClassUtils;
*
* @author Dave Syer
* @author Madhura Bhave
* @since 1.2.2
* @since 4.0.0
*/
public class MustacheTemplateAvailabilityProvider implements TemplateAvailabilityProvider {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2025 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.
@@ -17,4 +17,4 @@
/**
* Auto-configuration for Mustache.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.web.reactive.result.view;
package org.springframework.boot.mustache.reactive.view;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -44,7 +44,7 @@ import org.springframework.web.server.ServerWebExchange;
* Spring WebFlux {@link View} using the Mustache template engine.
*
* @author Brian Clozel
* @since 2.0.0
* @since 4.0.0
*/
public class MustacheView extends AbstractUrlBasedView {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.web.reactive.result.view;
package org.springframework.boot.mustache.reactive.view;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
@@ -28,7 +28,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
*
* @author Brian Clozel
* @author Marten Deinum
* @since 2.0.0
* @since 4.0.0
*/
public class MustacheViewResolver extends UrlBasedViewResolver {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2025 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.
@@ -18,4 +18,4 @@
* Additional {@link org.springframework.web.reactive.result.view.View Views} for use with
* WebFlux.
*/
package org.springframework.boot.web.reactive.result.view;
package org.springframework.boot.mustache.reactive.view;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.web.servlet.view;
package org.springframework.boot.mustache.servlet.view;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -37,7 +37,7 @@ import org.springframework.web.servlet.view.AbstractTemplateView;
* @author Brian Clozel
* @author Dave Syer
* @author Phillip Webb
* @since 2.0.0
* @since 4.0.0
*/
public class MustacheView extends AbstractTemplateView {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.web.servlet.view;
package org.springframework.boot.mustache.servlet.view;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
@@ -27,7 +27,7 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView;
* Spring MVC {@link ViewResolver} for Mustache.
*
* @author Brian Clozel
* @since 2.0.0
* @since 4.0.0
*/
public class MustacheViewResolver extends AbstractTemplateViewResolver {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2025 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.
@@ -17,4 +17,4 @@
/**
* Additional {@link org.springframework.web.servlet.View Views} for use with Web MVC.
*/
package org.springframework.boot.web.servlet.view;
package org.springframework.boot.mustache.servlet.view;

View File

@@ -0,0 +1,21 @@
{
"groups": [],
"properties": [
{
"name": "spring.mustache.prefix",
"defaultValue": "classpath:/templates/"
},
{
"name": "spring.mustache.reactive.media-types",
"defaultValue": "text/html;charset=UTF-8"
},
{
"name": "spring.mustache.suffix",
"defaultValue": ".mustache"
}
],
"hints": [],
"ignored": {
"properties": []
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.mustache.autoconfigure.MustacheTemplateAvailabilityProvider

View File

@@ -0,0 +1 @@
org.springframework.boot.mustache.autoconfigure.MustacheAutoConfiguration

View File

@@ -14,31 +14,37 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import java.util.Date;
import com.samskivert.mustache.Mustache;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.mustache.reactive.view.MustacheView;
import org.springframework.boot.mustache.reactive.view.MustacheViewResolver;
import org.springframework.boot.reactor.netty.autoconfigure.NettyReactiveWebServerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.reactive.result.view.MustacheView;
import org.springframework.boot.web.reactive.result.view.MustacheViewResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.assertj.core.api.Assertions.assertThat;
@@ -47,6 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* and {@link MustacheView}.
*
* @author Brian Clozel
* @author Moritz Halbritter
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive")
class MustacheAutoConfigurationReactiveIntegrationTests {
@@ -81,11 +88,26 @@ class MustacheAutoConfigurationReactiveIntegrationTests {
}
@Configuration(proxyBeanMethods = false)
@Import({ NettyReactiveWebServerAutoConfiguration.class, WebFluxAutoConfiguration.class,
HttpHandlerAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
@Import({ NettyReactiveWebServerAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
@Controller
@EnableWebFlux
static class Application {
@Bean
WebFluxConfigurer registerViewResolvers(ObjectProvider<ViewResolver> viewResolvers) {
return new WebFluxConfigurer() {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
viewResolvers.orderedStream().forEach(registry::viewResolver);
}
};
}
@Bean
HttpHandler httpHandler(ApplicationContext context) {
return WebHttpHandlerBuilder.applicationContext(context).build();
}
@RequestMapping("/")
String home(Model model) {
model.addAttribute("time", new Date());
@@ -106,9 +128,9 @@ class MustacheAutoConfigurationReactiveIntegrationTests {
MustacheViewResolver viewResolver() {
Mustache.Compiler compiler = Mustache.compiler()
.withLoader(new MustacheResourceTemplateLoader(
"classpath:/org/springframework/boot/autoconfigure/mustache/", ".html"));
"classpath:/org/springframework/boot/mustache/autoconfigure/", ".html"));
MustacheViewResolver resolver = new MustacheViewResolver(compiler);
resolver.setPrefix("classpath:/org/springframework/boot/autoconfigure/mustache/");
resolver.setPrefix("classpath:/org/springframework/boot/mustache/autoconfigure/");
resolver.setSuffix(".html");
return resolver;
}

View File

@@ -14,39 +14,34 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.mustache.servlet.view.MustacheView;
import org.springframework.boot.mustache.servlet.view.MustacheViewResolver;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.view.MustacheView;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.DispatcherServlet;
import static org.assertj.core.api.Assertions.assertThat;
@@ -55,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* and {@link MustacheView}.
*
* @author Dave Syer
* @author Moritz Halbritter
*/
@DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@@ -71,12 +67,12 @@ class MustacheAutoConfigurationServletIntegrationTests {
}
@Test
void contextLoads() {
void shouldRenderTemplate() {
String source = "Hello {{arg}}!";
Template tmpl = Mustache.compiler().compile(source);
Map<String, String> context = new HashMap<>();
context.put("arg", "world");
assertThat(tmpl.execute(context)).isEqualTo("Hello world!");
Assertions.assertThat(tmpl.execute(context)).isEqualTo("Hello world!");
}
@Test
@@ -92,7 +88,7 @@ class MustacheAutoConfigurationServletIntegrationTests {
}
@Configuration(proxyBeanMethods = false)
@MinimalWebConfiguration
@Import(MinimalWebConfiguration.class)
@Controller
static class Application {
@@ -116,9 +112,9 @@ class MustacheAutoConfigurationServletIntegrationTests {
MustacheViewResolver viewResolver() {
Mustache.Compiler compiler = Mustache.compiler()
.withLoader(new MustacheResourceTemplateLoader(
"classpath:/org/springframework/boot/autoconfigure/mustache/", ".html"));
"classpath:/org/springframework/boot/mustache/autoconfigure/", ".html"));
MustacheViewResolver resolver = new MustacheViewResolver(compiler);
resolver.setPrefix("classpath:/org/springframework/boot/autoconfigure/mustache/");
resolver.setPrefix("classpath:/org/springframework/boot/mustache/autoconfigure/");
resolver.setSuffix(".html");
return resolver;
}
@@ -129,12 +125,14 @@ class MustacheAutoConfigurationServletIntegrationTests {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ TomcatServletWebServerAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
@Configuration(proxyBeanMethods = false)
@Import({ TomcatServletWebServerAutoConfiguration.class })
static class MinimalWebConfiguration {
@Bean
DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}

View File

@@ -14,22 +14,23 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import java.util.Arrays;
import java.util.function.Supplier;
import com.samskivert.mustache.Mustache;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.mustache.servlet.view.MustacheViewResolver;
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
@@ -70,7 +71,7 @@ class MustacheAutoConfigurationTests {
assertThat(context).hasSingleBean(Mustache.Compiler.class);
assertThat(context).hasSingleBean(MustacheResourceTemplateLoader.class);
assertThat(context).hasSingleBean(MustacheViewResolver.class);
assertThat(context.getBean(Mustache.Compiler.class).standardsMode).isTrue();
Assertions.assertThat(context.getBean(Mustache.Compiler.class).standardsMode).isTrue();
});
}
@@ -81,7 +82,7 @@ class MustacheAutoConfigurationTests {
assertThat(context).hasSingleBean(MustacheResourceTemplateLoader.class);
assertThat(context).doesNotHaveBean(MustacheViewResolver.class);
assertThat(context)
.hasSingleBean(org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class);
.hasSingleBean(org.springframework.boot.mustache.reactive.view.MustacheViewResolver.class);
});
}
@@ -92,7 +93,7 @@ class MustacheAutoConfigurationTests {
assertThat(context).hasSingleBean(Mustache.Compiler.class);
assertThat(context).hasSingleBean(MustacheResourceTemplateLoader.class);
assertThat(context)
.doesNotHaveBean(org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class);
.doesNotHaveBean(org.springframework.boot.mustache.reactive.view.MustacheViewResolver.class);
});
}
@@ -104,8 +105,8 @@ class MustacheAutoConfigurationTests {
assertThat(context).hasSingleBean(MustacheResourceTemplateLoader.class);
assertThat(context).doesNotHaveBean(MustacheViewResolver.class);
assertThat(context)
.hasSingleBean(org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class);
assertThat(context.getBean(Mustache.Compiler.class).standardsMode).isTrue();
.hasSingleBean(org.springframework.boot.mustache.reactive.view.MustacheViewResolver.class);
Assertions.assertThat(context.getBean(Mustache.Compiler.class).standardsMode).isTrue();
});
}
@@ -130,13 +131,13 @@ class MustacheAutoConfigurationTests {
@Test
void defaultReactiveViewResolverConfiguration() {
configure(new ReactiveWebApplicationContextRunner()).run((context) -> {
org.springframework.boot.web.reactive.result.view.MustacheViewResolver viewResolver = context
.getBean(org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class);
org.springframework.boot.mustache.reactive.view.MustacheViewResolver viewResolver = context
.getBean(org.springframework.boot.mustache.reactive.view.MustacheViewResolver.class);
assertThat(viewResolver).extracting("charset").isEqualTo("UTF-8");
assertThat(viewResolver).extracting("prefix").isEqualTo("classpath:/templates/");
assertThat(viewResolver).extracting("requestContextAttribute").isNull();
assertThat(viewResolver).extracting("suffix").isEqualTo(".mustache");
assertThat(viewResolver.getSupportedMediaTypes())
Assertions.assertThat(viewResolver.getSupportedMediaTypes())
.containsExactly(MediaType.parseMediaType("text/html;charset=UTF-8"));
});
}
@@ -248,7 +249,7 @@ class MustacheAutoConfigurationTests {
* Reactive MustacheViewResolver
*/
REACTIVE(ReactiveWebApplicationContextRunner::new,
org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class);
org.springframework.boot.mustache.reactive.view.MustacheViewResolver.class);
private final Supplier<AbstractApplicationContextRunner<?, ?, ?>> runner;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import com.samskivert.mustache.Mustache;
import org.junit.jupiter.api.Test;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,11 +14,12 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.mustache;
package org.springframework.boot.mustache.autoconfigure;
import java.util.Collections;
import com.samskivert.mustache.Mustache;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -29,8 +30,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration Tests for {@link MustacheAutoConfiguration} outside of a web application.
*
@@ -45,7 +44,8 @@ class MustacheStandaloneIntegrationTests {
@Test
void directCompilation() {
assertThat(this.compiler.compile("Hello: {{world}}").execute(Collections.singletonMap("world", "World")))
Assertions
.assertThat(this.compiler.compile("Hello: {{world}}").execute(Collections.singletonMap("world", "World")))
.isEqualTo("Hello: World");
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.web.reactive.result.view;
package org.springframework.boot.mustache.reactive.view;
import java.time.Duration;

View File

@@ -14,13 +14,14 @@
* limitations under the License.
*/
package org.springframework.boot.web.reactive.result.view;
package org.springframework.boot.mustache.reactive.view;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import com.samskivert.mustache.Mustache;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;
@@ -30,8 +31,6 @@ import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MustacheView}.
*
@@ -53,7 +52,7 @@ class MustacheViewTests {
view.render(Collections.singletonMap("World", "Spring"), MediaType.TEXT_HTML, exchange)
.block(Duration.ofSeconds(30));
StepVerifier.create(exchange.getResponse().getBodyAsString())
.assertNext((body) -> assertThat(body).isEqualToIgnoringWhitespace("Hello Spring"))
.assertNext((body) -> Assertions.assertThat(body).isEqualToIgnoringWhitespace("Hello Spring"))
.expectComplete()
.verify(Duration.ofSeconds(30));
}

View File

@@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.boot.web.servlet.view;
package org.springframework.boot.mustache.servlet.view;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -62,7 +63,7 @@ class MustacheViewResolverTests {
void setsContentType() throws Exception {
this.resolver.setContentType("application/octet-stream");
View view = this.resolver.resolveViewName("template", null);
assertThat(view.getContentType()).isEqualTo("application/octet-stream");
Assertions.assertThat(view.getContentType()).isEqualTo("application/octet-stream");
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.web.servlet.view;
package org.springframework.boot.mustache.servlet.view;
import java.util.Collections;

View File

@@ -6,5 +6,5 @@ description = "Starter for building web applications using Mustache views"
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("com.samskivert:jmustache")
api(project(":spring-boot-project:spring-boot-mustache"))
}

View File

@@ -101,6 +101,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-actuator"))
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-freemarker"))
testImplementation(project(":spring-boot-project:spring-boot-mustache"))
testImplementation(project(":spring-boot-project:spring-boot-testcontainers"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("ch.qos.logback:logback-classic")

View File

@@ -2,7 +2,7 @@
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
optional:org.springframework.boot.freemarker.autoconfigure.FreeMarkerAutoConfiguration
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration
optional:org.springframework.boot.mustache.autoconfigure.MustacheAutoConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
optional:org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration

View File

@@ -4,7 +4,7 @@ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration
optional:org.springframework.boot.freemarker.autoconfigure.FreeMarkerAutoConfiguration
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration
optional:org.springframework.boot.mustache.autoconfigure.MustacheAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
optional:org.springframework.boot.groovy.template.autoconfigure.GroovyTemplateAutoConfiguration

View File

@@ -20,12 +20,12 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;
import org.springframework.boot.freemarker.autoconfigure.FreeMarkerAutoConfiguration;
import org.springframework.boot.mustache.autoconfigure.MustacheAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration;
import org.springframework.context.ApplicationContext;

View File

@@ -19,7 +19,6 @@ package org.springframework.boot.test.autoconfigure.web.servlet;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
@@ -27,6 +26,7 @@ import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfigurati
import org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration;
import org.springframework.boot.freemarker.autoconfigure.FreeMarkerAutoConfiguration;
import org.springframework.boot.groovy.template.autoconfigure.GroovyTemplateAutoConfiguration;
import org.springframework.boot.mustache.autoconfigure.MustacheAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;