FreemarkerConfigurer.java
package fr.sii.ogham.spring.template;
import static fr.sii.ogham.core.util.ConfigurationValueUtils.firstValue;
import static java.util.Optional.ofNullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties;
import org.springframework.context.ApplicationContext;
import fr.sii.ogham.core.builder.configurer.MessagingConfigurerAdapter;
import fr.sii.ogham.email.builder.EmailBuilder;
import fr.sii.ogham.sms.builder.SmsBuilder;
import fr.sii.ogham.spring.common.OghamTemplateProperties;
import fr.sii.ogham.spring.common.SpringMessagingConfigurer;
import fr.sii.ogham.spring.email.OghamEmailProperties;
import fr.sii.ogham.spring.sms.OghamSmsProperties;
import fr.sii.ogham.spring.template.freemarker.SpringBeansTemplateHashModelEx;
import fr.sii.ogham.template.freemarker.FreeMarkerParser;
import fr.sii.ogham.template.freemarker.FreemarkerConstants;
import fr.sii.ogham.template.freemarker.builder.AbstractFreemarkerBuilder;
import fr.sii.ogham.template.freemarker.builder.FreemarkerEmailBuilder;
import fr.sii.ogham.template.freemarker.builder.FreemarkerSmsBuilder;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
/**
* Integrates with Spring templating system by using Freemarker
* {@link Configuration} object provided by Spring and by using Spring
* properties defined with prefix {@code spring.freemarker} (see
* {@link FreeMarkerProperties}).
*
* If both Spring property and Ogham property is defined, Ogham property is
* used.
*
* For example, if the file application.properties contains the following
* configuration:
*
* <pre>
* spring.freemarker.prefix=/email/
* ogham.email.freemarker.path-prefix=/foo/
* </pre>
*
* The {@link FreeMarkerParser} will use the templates in "/foo/".
*
* <p>
* This configurer is also useful to support property naming variants (see
* <a href=
* "https://github.com/spring-projects/spring-boot/wiki/relaxed-binding-2.0">Relaxed
* Binding</a>).
*
* <p>
* If ogham.freemarker.enable-spring-beans is true (default value), then Spring
* Beans are available from the template using syntax
* {@code @beanName.method(args)}.
*
* <p>
* If {@code ogham.freemarker.static-method-access.enable} is true (default
* value), then static methods can be called from templates using
*
* <pre>
* {@code statics['full.package.name.ClassName'].method(args)}
* </pre>
*
* If {@code ogham.freemarker.static-method-access.variable-name} value is
* changed (default value is 'statics'), then static methods can be called from
* templates using another variable name. For example, configuring
* {@code ogham.freemarker.static-method-access.variable-name=global} gives
* access to static methods using name global:
*
* <pre>
* {@code global['full.package.name.ClassName'].method(args)}
* </pre>
*
*
* @author Aurélien Baudet
*
*/
public class FreemarkerConfigurer extends MessagingConfigurerAdapter implements SpringMessagingConfigurer {
private static final int SPRING_CONFIGURER_PRIORITY_OFFSET = 1000;
private static final Logger LOG = LoggerFactory.getLogger(FreemarkerConfigurer.class);
private final Configuration emailConfiguration;
private final Configuration smsConfiguration;
private final OghamCommonTemplateProperties templateProperties;
private final OghamEmailProperties emailProperties;
private final OghamSmsProperties smsProperties;
private final FreeMarkerProperties springProperties;
private final OghamFreemarkerProperties oghamFreemarkerProperties;
private final ApplicationContext applicationContext;
public FreemarkerConfigurer(Configuration emailConfiguration, Configuration smsConfiguration, OghamCommonTemplateProperties templateProperties, OghamEmailProperties emailProperties,
OghamSmsProperties smsProperties, FreeMarkerProperties springProperties, OghamFreemarkerProperties oghamFreemarkerProperties, ApplicationContext applicationContext) {
super();
this.emailConfiguration = emailConfiguration;
this.smsConfiguration = smsConfiguration;
this.templateProperties = templateProperties;
this.emailProperties = emailProperties;
this.smsProperties = smsProperties;
this.springProperties = springProperties;
this.oghamFreemarkerProperties = oghamFreemarkerProperties;
this.applicationContext = applicationContext;
}
@Override
public void configure(EmailBuilder emailBuilder) {
AbstractFreemarkerBuilder<?, ?> builder = emailBuilder.template(FreemarkerEmailBuilder.class);
builder.mergeConfiguration(emailConfiguration);
// specific Ogham properties explicitly take precedence over Spring
// properties
if (springProperties != null) {
applySpringConfiguration(builder);
}
if (emailProperties != null) {
applyOghamConfiguration(builder, emailProperties);
}
if (oghamFreemarkerProperties.getSpringBeans().isEnable()) {
registerSpringBeans(builder, emailConfiguration);
}
if (oghamFreemarkerProperties.getStaticMethodAccess().isEnable()) {
registerStatics(builder);
}
}
@Override
public void configure(SmsBuilder smsBuilder) {
AbstractFreemarkerBuilder<?, ?> builder = smsBuilder.template(FreemarkerSmsBuilder.class);
builder.mergeConfiguration(smsConfiguration);
// specific Ogham properties explicitly take precedence over Spring
// properties
if (springProperties != null) {
applySpringConfiguration(builder);
}
if (smsProperties != null) {
applyOghamConfiguration(builder, smsProperties);
}
if (oghamFreemarkerProperties.getSpringBeans().isEnable()) {
registerSpringBeans(builder, smsConfiguration);
}
if (oghamFreemarkerProperties.getStaticMethodAccess().isEnable()) {
registerStatics(builder);
}
}
@Override
public int getOrder() {
return FreemarkerConstants.DEFAULT_FREEMARKER_EMAIL_CONFIGURER_PRIORITY + SPRING_CONFIGURER_PRIORITY_OFFSET;
}
private void applyOghamConfiguration(AbstractFreemarkerBuilder<?, ?> builder, OghamTemplateProperties props) {
LOG.debug("[{}] apply ogham configuration properties to {}", this, builder);
// @formatter:off
builder
.classpath()
.pathPrefix()
.value(ofNullable(firstValue(props.getFreemarker().getClasspath().getPathPrefix(),
props.getTemplate().getClasspath().getPathPrefix(),
props.getFreemarker().getPathPrefix(),
props.getTemplate().getPathPrefix(),
templateProperties.getPathPrefix())))
.and()
.pathSuffix()
.value(ofNullable(firstValue(props.getFreemarker().getClasspath().getPathSuffix(),
props.getTemplate().getClasspath().getPathSuffix(),
props.getFreemarker().getPathSuffix(),
props.getTemplate().getPathSuffix(),
templateProperties.getPathSuffix())))
.and()
.and()
.file()
.pathPrefix()
.value(ofNullable(firstValue(props.getFreemarker().getFile().getPathPrefix(),
props.getTemplate().getFile().getPathPrefix(),
props.getFreemarker().getPathPrefix(),
props.getTemplate().getPathPrefix(),
templateProperties.getPathPrefix())))
.and()
.pathSuffix()
.value(ofNullable(firstValue(props.getFreemarker().getFile().getPathSuffix(),
props.getTemplate().getFile().getPathSuffix(),
props.getFreemarker().getPathSuffix(),
props.getTemplate().getPathSuffix(),
templateProperties.getPathSuffix())));
builder
.configuration()
.defaultEncoding().value(ofNullable(oghamFreemarkerProperties.getDefaultEncoding()));
// @formatter:on
}
private void applySpringConfiguration(AbstractFreemarkerBuilder<?, ?> builder) {
LOG.debug("[{}] apply spring configuration properties to {}", this, builder);
// @formatter:off
builder
.classpath()
.pathPrefix().value(ofNullable(springProperties.getPrefix())).and()
.pathSuffix().value(ofNullable(springProperties.getSuffix())).and()
.and()
.file()
.pathPrefix().value(ofNullable(springProperties.getPrefix())).and()
.pathSuffix().value(ofNullable(springProperties.getSuffix()));
builder
.configuration()
.defaultEncoding().value(ofNullable(springProperties.getCharsetName()));
// @formatter:on
}
private void registerSpringBeans(AbstractFreemarkerBuilder<?, ?> builder, Configuration configuration) {
builder.configuration().addSharedVariables(new SpringBeansTemplateHashModelEx(applicationContext, getBeansWrapper(configuration)));
}
private static BeansWrapper getBeansWrapper(Configuration configuration) {
ObjectWrapper objectWrapper = configuration.getObjectWrapper();
if (objectWrapper instanceof BeansWrapper) {
return (BeansWrapper) objectWrapper;
}
return new BeansWrapperBuilder(configuration.getIncompatibleImprovements()).build();
}
private void registerStatics(AbstractFreemarkerBuilder<?, ?> builder) {
builder.configuration()
.enableStaticMethodAccess(true)
.staticMethodAccessVariableName(oghamFreemarkerProperties.getStaticMethodAccess().getVariableName());
}
}