Add support for customizing FreeMarker variables
This commit updates the auto-configuration to allow custom FreeMarker variables to be provided programmatically. As these variables are usually objects, they cannot be specified via properties. Closes gh-8965
This commit is contained in:
parent
9e3e067a4c
commit
a2fafa112f
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2019 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,21 +16,30 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure.freemarker;
|
package org.springframework.boot.autoconfigure.freemarker;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
|
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for shared FreeMarker configuration.
|
* Base class for shared FreeMarker configuration.
|
||||||
*
|
*
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
abstract class AbstractFreeMarkerConfiguration {
|
abstract class AbstractFreeMarkerConfiguration {
|
||||||
|
|
||||||
private final FreeMarkerProperties properties;
|
private final FreeMarkerProperties properties;
|
||||||
|
|
||||||
protected AbstractFreeMarkerConfiguration(FreeMarkerProperties properties) {
|
private final List<FreeMarkerVariablesCustomizer> variablesCustomizers;
|
||||||
|
|
||||||
|
protected AbstractFreeMarkerConfiguration(FreeMarkerProperties properties,
|
||||||
|
ObjectProvider<FreeMarkerVariablesCustomizer> variablesCustomizers) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
this.variablesCustomizers = variablesCustomizers.orderedStream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final FreeMarkerProperties getProperties() {
|
protected final FreeMarkerProperties getProperties() {
|
||||||
|
|
@ -41,10 +50,23 @@ abstract class AbstractFreeMarkerConfiguration {
|
||||||
factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
|
factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
|
||||||
factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
|
factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
|
||||||
factory.setDefaultEncoding(this.properties.getCharsetName());
|
factory.setDefaultEncoding(this.properties.getCharsetName());
|
||||||
|
factory.setFreemarkerSettings(createFreeMarkerSettings());
|
||||||
|
factory.setFreemarkerVariables(createFreeMarkerVariables());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties createFreeMarkerSettings() {
|
||||||
Properties settings = new Properties();
|
Properties settings = new Properties();
|
||||||
settings.put("recognize_standard_file_extensions", "true");
|
settings.put("recognize_standard_file_extensions", "true");
|
||||||
settings.putAll(this.properties.getSettings());
|
settings.putAll(this.properties.getSettings());
|
||||||
factory.setFreemarkerSettings(settings);
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createFreeMarkerVariables() {
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
for (FreeMarkerVariablesCustomizer customizer : this.variablesCustomizers) {
|
||||||
|
customizer.customizeFreeMarkerVariables(variables);
|
||||||
|
}
|
||||||
|
return variables;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2019 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure.freemarker;
|
package org.springframework.boot.autoconfigure.freemarker;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
@ -32,8 +33,9 @@ import org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean;
|
||||||
@ConditionalOnNotWebApplication
|
@ConditionalOnNotWebApplication
|
||||||
class FreeMarkerNonWebConfiguration extends AbstractFreeMarkerConfiguration {
|
class FreeMarkerNonWebConfiguration extends AbstractFreeMarkerConfiguration {
|
||||||
|
|
||||||
FreeMarkerNonWebConfiguration(FreeMarkerProperties properties) {
|
FreeMarkerNonWebConfiguration(FreeMarkerProperties properties,
|
||||||
super(properties);
|
ObjectProvider<FreeMarkerVariablesCustomizer> variablesCustomizers) {
|
||||||
|
super(properties, variablesCustomizers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2019 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure.freemarker;
|
package org.springframework.boot.autoconfigure.freemarker;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
|
@ -38,8 +39,9 @@ import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewRes
|
||||||
@AutoConfigureAfter(WebFluxAutoConfiguration.class)
|
@AutoConfigureAfter(WebFluxAutoConfiguration.class)
|
||||||
class FreeMarkerReactiveWebConfiguration extends AbstractFreeMarkerConfiguration {
|
class FreeMarkerReactiveWebConfiguration extends AbstractFreeMarkerConfiguration {
|
||||||
|
|
||||||
FreeMarkerReactiveWebConfiguration(FreeMarkerProperties properties) {
|
FreeMarkerReactiveWebConfiguration(FreeMarkerProperties properties,
|
||||||
super(properties);
|
ObjectProvider<FreeMarkerVariablesCustomizer> variablesCustomizers) {
|
||||||
|
super(properties, variablesCustomizers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2021 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.freemarker;
|
||||||
import jakarta.servlet.DispatcherType;
|
import jakarta.servlet.DispatcherType;
|
||||||
import jakarta.servlet.Servlet;
|
import jakarta.servlet.Servlet;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
|
@ -47,8 +48,9 @@ import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
|
||||||
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
|
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
|
||||||
class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {
|
class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {
|
||||||
|
|
||||||
protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties) {
|
protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties,
|
||||||
super(properties);
|
ObjectProvider<FreeMarkerVariablesCustomizer> variablesCustomizers) {
|
||||||
|
super(properties, variablesCustomizers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2024 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
|
||||||
|
*
|
||||||
|
* https://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 freemarker.template.Configuration;
|
||||||
|
|
||||||
|
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface that can be implemented by beans wishing to customize the FreeMarker
|
||||||
|
* variables used as {@link Configuration#getSharedVariableNames() shared variables}
|
||||||
|
* before it is used by an auto-configured {@link FreeMarkerConfigurationFactory}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 3.4.0
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface FreeMarkerVariablesCustomizer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize the {@code variables} to be set as well-known FreeMarker objects.
|
||||||
|
* @param variables the variables to customize
|
||||||
|
* @see FreeMarkerConfigurationFactory#setFreemarkerVariables(Map)
|
||||||
|
*/
|
||||||
|
void customizeFreeMarkerVariables(Map<String, Object> variables);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -27,8 +27,14 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
import org.springframework.boot.test.system.CapturedOutput;
|
import org.springframework.boot.test.system.CapturedOutput;
|
||||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||||
import org.springframework.boot.testsupport.BuildOutput;
|
import org.springframework.boot.testsupport.BuildOutput;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.then;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link FreeMarkerAutoConfiguration}.
|
* Tests for {@link FreeMarkerAutoConfiguration}.
|
||||||
|
|
@ -80,6 +86,24 @@ class FreeMarkerAutoConfigurationTests {
|
||||||
.run((context) -> assertThat(output).doesNotContain("Cannot find template location"));
|
.run((context) -> assertThat(output).doesNotContain("Cannot find template location"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void variableCustomizerShouldBeApplied() {
|
||||||
|
FreeMarkerVariablesCustomizer customizer = mock(FreeMarkerVariablesCustomizer.class);
|
||||||
|
this.contextRunner.withBean(FreeMarkerVariablesCustomizer.class, () -> customizer)
|
||||||
|
.run((context) -> then(customizer).should().customizeFreeMarkerVariables(any()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void variableCustomizersShouldBeAppliedInOrder() {
|
||||||
|
this.contextRunner.withUserConfiguration(VariablesCustomizersConfiguration.class).run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(freemarker.template.Configuration.class);
|
||||||
|
freemarker.template.Configuration configuration = context.getBean(freemarker.template.Configuration.class);
|
||||||
|
assertThat(configuration.getSharedVariableNames()).contains("order", "one", "two");
|
||||||
|
assertThat(configuration.getSharedVariable("order")).hasToString("5");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static class DataModel {
|
public static class DataModel {
|
||||||
|
|
||||||
public String getGreeting() {
|
public String getGreeting() {
|
||||||
|
|
@ -88,4 +112,27 @@ class FreeMarkerAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class VariablesCustomizersConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(5)
|
||||||
|
FreeMarkerVariablesCustomizer variablesCustomizer() {
|
||||||
|
return (variables) -> {
|
||||||
|
variables.put("order", 5);
|
||||||
|
variables.put("one", "one");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(2)
|
||||||
|
FreeMarkerVariablesCustomizer anotherVariablesCustomizer() {
|
||||||
|
return (variables) -> {
|
||||||
|
variables.put("order", 2);
|
||||||
|
variables.put("two", "two");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,7 @@ If you add your own, you have to be aware of the order and in which position you
|
||||||
The prefix is externalized to `spring.freemarker.prefix`, and the suffix is externalized to `spring.freemarker.suffix`.
|
The prefix is externalized to `spring.freemarker.prefix`, and the suffix is externalized to `spring.freemarker.suffix`.
|
||||||
The default values of the prefix and suffix are empty and '`.ftlh`', respectively.
|
The default values of the prefix and suffix are empty and '`.ftlh`', respectively.
|
||||||
You can override `FreeMarkerViewResolver` by providing a bean of the same name.
|
You can override `FreeMarkerViewResolver` by providing a bean of the same name.
|
||||||
|
FreeMarker variables can be customized by defining a bean of type `FreeMarkerVariablesCustomizer`.
|
||||||
* If you use Groovy templates (actually, if `groovy-templates` is on your classpath), you also have a `GroovyMarkupViewResolver` named '`groovyMarkupViewResolver`'.
|
* If you use Groovy templates (actually, if `groovy-templates` is on your classpath), you also have a `GroovyMarkupViewResolver` named '`groovyMarkupViewResolver`'.
|
||||||
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`).
|
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.
|
The prefix and suffix have default values of '`classpath:/templates/`' and '`.tpl`', respectively.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue