Upgrading to Thymeleaf 3 and Re-Implementing Rich HTML Email in Spring - java

I am migrating my project from Thymeleaf 2 to 3 and I'm having an issue with the email template resolver interfering with my web template resolver.
When we first implemented Thymeleaf we followed this document to allow for email templates and web templates and had no issues (section 4.2):
http://www.thymeleaf.org/doc/articles/springmail.html
During conversion to Thymeleaf 3, I worked with ONLY the one template resolver for HTML templates (using the SpringResourceTemplateResolver) and the conversion was fine. I was able to run my application without any problems and the view templates all rendered without problems.
However, when including the email template resolver (using the ClassLoaderTemplateResolver), the following error occurs when going to any controller endpoint that returns a view:
java.io.FileNotFoundException: ClassLoader resource "thymeleaf/thymeleaf/login.html" does not exist
Which indicates it is trying to load the template from the ClassLoaderTemplateResolver and not the SpringResourceTemplateResolver. In Thymeleaf 2 setting the Order attribute solved this problem but this doesn't appear to work in Thymeleaf 3.
I couldn't find any help in the Thymeleaf 3 documentation for having 2 Template resolvers like there was in Thymeleaf 2 docs.
Any suggestions on how to get this working or documentation that I may have overlooked?

I have this same setup (a ClassLoaderTemplateResolver for emails and a SpringResourceTemplateResolver for html page templates). I think you need to set this property:
resolver.setCheckExistence(true);
On whichever of your template resolvers happens first, otherwise spring will assume it exists and you will see the error your seeing.

You can use the following setup..
AppConfig.java
#Configuration
#EnableWebMvc
#ComponentScan("myapp")
public class AppConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(webTemplateEngine());
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return resolver;
}
#Bean
public TemplateEngine webTemplateEngine() {
//this method must be defined as a bean otherwise i18n messages are not found
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(true);
engine.addTemplateResolver(webTemplateResolver());
return engine;
}
private ITemplateResolver webTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/thymeleaf/");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setSuffix(".html");
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return resolver;
}
#Bean
public TemplateEngine emailTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(emailTemplateResolver());
return templateEngine;
}
private ITemplateResolver emailTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/thymeleaf/email/");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setSuffix(".html");
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return resolver;
}
}
And then where you need to use the email template engine, just autowire the emailTemplateEngine and use it
#Autowired
private TemplateEngine emailTemplateEngine;

Related

Thymeleaf cannot detect templates inside spring-boot project

I have the following project structure in my Spring boot app, in which I want to use Thymeleaf
projectName
-Gradle-Module1(Spring boot module)
-build
-src
-main
-resources
-templates
index.html
build.gradle
-Gradle-Module2
...
build.gradle
...
but the spring-boot cannot find my template directory and is showing warning
Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
PS: I am using #EnableAutoConfiguration
In my controller code I am doing something like:
#Controller
#EnableAutoConfiguration
public class BaseController {
#RequestMapping(value = "/")
public String index() {
return "index.html";
}
}
and index.html file just prints hello world.
So typically it should look inside src/resources/templates/(of same Gradle module I suppose), but somehow it is not able to find it.
When I try to access localhost:8080 I am getting below error
Error resolving template "index.html", template might not exist or might not be accessible by any of the configured Template Resolvers`
Is there anything I am missing?
Any pointers appreciated. Thanks.
You have to configure Thymeleaf as follows:
#Configuration
public class ThymeleafConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setCacheable(false);
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.addTemplateResolver(templateResolver());
return springTemplateEngine;
}
#Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
return viewResolver;
}
}
Spring doc recommends to add #EnableAutoConfiguration annotation to your primary #Configuration class.
Also it seems you have wrong project structure, the typical package hierarchy is:
src
|- main
|- java
|- resources
|- static
|- templates
|- test
In this case your templates will be in src/main/resources/templates, not in src/resources/templates/.
You should only return the file name. E.g without the .hmtl
#RequestMapping(value = "/")
public String index() {
return "index";
}
#GetMapping("/")
public String index() {
return "index";
}

How to map Spring MVC controllers to a specific folder?

I'm working on a project using Spring MVC, Spring Boot, Gradle and Thymeleaf. I was wondering if there's a way to avoid controllers returning lengthy String paths to a specific view in a subfolder?
My views are currently in resources/templates/views/home/
public String index( Model model ) {
return "views/home/index";
}
I'm looking to only have to return something like home/index or home/someotherpage for the HomeController.
Thanks!
Yes, it is possible by defining templateResolver bean in your configuration:
#Configuration
public class TemplateConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setCacheable(false);
templateResolver.setPrefix("classpath:/templates/views/");
templateResolver.setSuffix(".html");
return templateResolver;
}
}
Now you can use your return index page in your controller as:
public String index( Model model ) {
return "home/index";
}

What is the best way to configure PropertySourcesPlaceholderConfigurer / resource folders?

I have a (gradle/java/eclipse) project with what seems to me to be quite a standard folder structure
...main
java
...controller
...service
resources
webapp
...resources
WEB-INF
I'm having an issue I just don't understand although I have worked around it in a very messy way. If I specify the controllers folder in the component scan of my WebMvcConfigurerAdapter then the services classes can't pick up properties using the configured PropertySourcesPlaceholderConfigurer bean. If I broaden the component scan out the jsp files don't pick up the css includes!
So with this config class all is well but the properties aren't resolved int he service implementation class
config class
#EnableWebMvc
#Configuration
#ComponentScan({ "comm.app.controller" })
#PropertySource({ "classpath:app.properties" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
//used to handle html put and delete rest calls
#Bean(name = "multipartResolver")
public CommonsMultipartResolver createMultipartResolver() {
CommonsMultipartResolver resolver=new CommonsMultipartResolver();
resolver.setDefaultEncoding("utf-8");
return resolver;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Service implementation class begins
#Service("appService")
#PropertySource({ "classpath:app.properties" })
public class appServiceImpl implements appService {
private RestTemplate restTemplate = new RestTemplate();
#Value("${property.reference}")
private String propref;
...
In this case ${property.reference} is not picked up but the view page styling (picked up from ...webapp\resources... .css) is fine.
If I change
#ComponentScan({ "comm.app.controller" })
to
#ComponentScan({ "comm.app" })
The properties are picked up (presumably because the propertyConfig bean comes into scope ?) but the local styling files can't be found ? Any link references to files webapp\resources... .css fails.
In the end I came to a bad (!?) solution of
1) keeping the scope of #ComponentScan({ "comm.app.controller" })
2) hacking the serviceimplementation class as so...
#Configuration
#Service("appService")
#PropertySource({ "classpath:app.properties" })
public class appServiceImpl implements appService {
private RestTemplate restTemplate = new RestTemplate();
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
#Value("${property.reference}")
private String propref;
...
Can anyone tell me if I've configured the path the the resources file incorrectly or maybe should be doing something differently with the propertyConfig bean ? (maybe injecting it or declaring another one in a different configuration file ?)

Spring i18n question marks instead of text

I hava i18n, but experience problems with russian letters. I have question marks ????? instead of text. Configuration:
#Bean
public LocaleResolver localeResolver(){
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setDefaultLocale(new Locale("ru"));
resolver.setCookieName("locale");
resolver.setCookieMaxAge(60 * 60 * 24 * 365 * 10);
return resolver;
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor changeInterceptor = new LocaleChangeInterceptor();
changeInterceptor.setParamName("lang");
return changeInterceptor;
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:/i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
Also I am using Thymeleaf.
<h1 th:text="#{message}"></h1>
Just a guess: in "addInterceptor" you are constructing the LocaleChangeInterceptor manually yourself. If this class depends on other spring beans (like MessageSource) then these are not injected. Find a way to let spring instantiate the LocaleChangeInterceptor (e.g. by using one of the various getBean() of the application context).
Your files encoding is wrong. You should set it to UTF-8. If you use Intellij IDEA you can find it in the right bottom corner. If this option is disabled go to Settings -> Editor -> File Encodings and change the Default encoding for properties files parameter

Spring bean messageSouce cannot be in ApplicationConfig and WebMvcConfig at the same time

I need to put the #Bean SpringTemplateEngine in the ApplicationConfig so that I can #Autowire it into a #Component that generates emails. (See Thymeleaf-Spring4 unable to autowire TemplateEngine and Rich HTML email in Spring with Thymeleaf). Therefore the #Bean(name = "messageSource") needs to be in the ApplicationConfig as well. However, I have a WebMvcConfig that extends WebMvcConfigurationSupport and the method #Override Validator getValidator() needs to setValidationMessageSource with messageSource(). However, since I moved it to the ApplicationConfig I don't know how to get at it from WebMvcConfig. I started with spring-mvc-quickstart-archetype from kolorobot. Instead of finding a whole new configuration archetype, maybe someone here can point me in a helpful direction.
Relevant Code for ApplicationConfig
#Configuration
#ComponentScan(basePackageClasses = Application.class, excludeFilters = #Filter ({Controller.class, Configuration.class}))
class ApplicationConfig {
*** Incomplete Example ***
#Bean(name = "messageSource")
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(MESSAGE_SOURCE);
messageSource.setCacheSeconds(5);
return messageSource;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
Set<TemplateResolver> resolvers = new HashSet<TemplateResolver>();
resolvers.add(classLoaderTemplateResolver());
resolvers.add(mvcTemplateResolver());
templateEngine.setTemplateResolvers(resolvers);
templateEngine.setMessageSource(messageSource());
templateEngine.addDialect(new SpringSecurityDialect());
return templateEngine;
}
}
And Relevant Code for WebMvcConfig
#Configuration
#ComponentScan(basePackageClasses = {Application.class}, includeFilters = #Filter (Controller.class), useDefaultFilters = false)
class WebMvcConfig extends WebMvcConfigurationSupport {
*** Incomplete Example ***
#Override
public Validator getValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
}
The issue seems to be that I cannot move the getValidator() to the ApplicationConfig() because it must override the WebMvcConfigurationSupport method.
I got it working by creating two separate SpringTemplateEngine configurations, one in the MvcWebConfig for view rendering and one in ApplicationConfig for handling emails. I put the email messages in a separate file and called setTemplateEngineMessageSource instead of setMessageSource as per documentation.

Categories