I have a problem with finding a thymeleaf template on docker. I'm familiar with the leading slash problem in ordinary controllers. However I'd now like to use thymeleaf to render email bodies.
My code looks like this:
#Component
public class HtmlEmailDocumenter implements Documenter {
#Autowired
SpringTemplateEngine thymeleafTemplateEngine;
// more dependencies
#Override
public void accept(Documentable documentable){
Context thymeleafContext = new Context();
thymeleafContext.setVariable("doc", documentable);
String template = "mail/default";
String htmlBody = thymeleafTemplateEngine.process(template, thymeleafContext);
// send the email...
}
}
The template in question is under /src/main/resources/templates/mail/default.html. I have alternatively tried using "mail/default.html" in the source code. Doesn't work either in docker.
Any hints welcome.
I am doing something similar in non-Boot Spring application by defining the template engine template and resolver as follows:
#Bean
public ResourceBundleMessageSource emailMessageSource() {
final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("templates/email/mailMessages");
return messageSource;
}
#Bean
public TemplateEngine emailTemplateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(htmlTemplateResolver());
templateEngine.setTemplateEngineMessageSource(emailMessageSource());
return templateEngine;
}
private ITemplateResolver htmlTemplateResolver() {
final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setOrder(Integer.valueOf(2));
templateResolver.setPrefix("/templates/email/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false);
return templateResolver;
}
I then refer to an email message template using just the name of the template within the /templates/email directory without extension.
Related
Since Thymeleaf 3, Thymeleaf prefers the use of SpringResourceTemplateResolver (https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html). So I decided to go from ClassLoaderTemplateResolver to SpringResourceTemplateResolver:
#Configuration
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.setOrder(1);
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(0);
resolver.setCheckExistence(true);
return resolver;
}
#Bean
public SpringResourceTemplateResolver templateResolver2() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/templates-2/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(1);
resolver.setCheckExistence(true);
return resolver;
}
}
Unfortunately, when implementig like this, I'll get an error:
Error resolving template [index], template might not exist or might not be accessible by any of the configured Template Resolvers.
To be honest, I've simple replaced ClassLoaderTemplateResolver with SpringResourceTemplateResolver in the hope, this will work. It doesn't. But searching for a working solution dealing with two template locations, all I find are outdated samples using ClassLoaderTemplateResolvers.
Trying to implement the code snippet provided by Thymeleaf as shown here https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html won't work either when using two template directories, besides the fact, that this code itself uses the deprecated WebMvcConfigurerAdapter.
Is there any example how to configure a Spring Boot application using Thymeleaf having two or more template locations which isn't completely outdated?
So after a while and many many tryouts I've finally got the templates working. Since there isn't any decent answer to my question flying round the internet, I will post my solution for others:
#Configuration
public class ThymeleafConfig implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(#Autowired ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("classpath:/templates-1/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setOrder(1);
templateResolver.setCheckExistence(true); /* FYI: necessary to chain TemplateResolvers */
templateResolver.setCacheable(false); /* FYI: during development -> false, so that we can see changes we make */
return templateResolver;
}
#Bean
public SpringResourceTemplateResolver templateResolver2() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("classpath:/templates-2/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setOrder(2);
templateResolver.setCheckExistence(true); /* FYI: necessary to chain TemplateResolvers */
templateResolver.setCacheable(false); /* FYI: during development -> false, so that we can see changes we make */
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
/* SpringTemplateEngine automatically applies SpringStandardDialect and
enables Spring's own MessageSource message resolution mechanisms. */
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(this.templateResolver());
templateEngine.addTemplateResolver(this.templateResolver2());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(this.templateEngine());
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setCache(false); /* FYI: during development -> false */
viewResolver.setOrder(1);
return viewResolver;
}
}
I hope this will help others to save time and nerves^^.
If you are using Spring Boot, you can add an extra resolver on top of the default one like this:
First, add this to application.properties:
# This ensures that the default HTML template resolver of Thymeleaf has priority over our custom SVG resolver
spring.thymeleaf.template-resolver-order=0
Then add the extra resolver in a #Configuration class:
#Configuration
public class MyApplicationConfiguration {
#Bean
public ITemplateResolver svgTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/svg/");
resolver.setSuffix(".svg");
resolver.setTemplateMode("XML");
return resolver;
}
}
There is no need to manually define SpringTemplateEngine and ViewResolver beans.
You can disable caching during development using spring.thymeleaf.cache=false.
I had to define a template resolver to get a custom dialect working, however this broke the auto-reloading of Thymeleaf templates. How can I re-enable this functionality? Alternatively, how do I add a custom dialect without having to define a template resolver?
#Configuration
public class ThymeleafConfig {
#Bean
public SpringTemplateEngine templateEngine(TagCacheService tagCacheService) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
ClassLoaderTemplateResolver primaryResolver = new ClassLoaderTemplateResolver();
primaryResolver.setPrefix("templates/");
primaryResolver.setSuffix(".html");
primaryResolver.setTemplateMode(TemplateMode.HTML);
primaryResolver.setCharacterEncoding("UTF-8");
primaryResolver.setOrder(0);
primaryResolver.setCheckExistence(true);
templateEngine.addTemplateResolver(primaryResolver);
templateEngine.addDialect(new LayoutDialect());
templateEngine.addDialect(new SpringSecurityDialect());
templateEngine.addDialect(new MyDialect(tagCacheService));
return templateEngine;
}
}
I just checked the source code of org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration and I see that other dialects there are registered by just declaring an instance of the dialect as a bean. So maybe try that?
#Bean
public MyDialect myDialect(TagCacheService tagCacheService) {
return new MyDialect(tagCacheService);
}
To enable auto-reloading of templates on a custom template resolver, the set setCacheable property of the resolver to false. You can read this value from application properties to make it more versatile.
// Get value from config
#Value("${spring.thymeleaf.cache}")
private boolean enableCache;
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
ClassLoaderTemplateResolver primaryResolver = new ClassLoaderTemplateResolver();
[ ... ]
primaryResolver.setCacheable(enableCache); // Set to false to enable reloading
[ ... ]
}
I currently have a Spring Boot application that is using React JS for the front end. I'm running inside Visual Studio, and via webpack, I'm compiling resources and outputting them to a Maven target folder. You can see the structure of the directory with the Java classes sitting inside the 'classes' folder and the client application sitting now inside the webapp folder - notice the index.html.
As this is a single page application I only need this page to resolve. As such my controller is configured to return the index file.
#SpringBootApplication
public class AdminApplication
{
public static void main( String[] args )
{
SpringApplication.run( AdminApplication.class, args );
}
}
#Controller
public class DefaultController
{
#RequestMapping( "/**" )
public ModelAndView reactApp()
{
return new ModelAndView( "index" );
}
}
Now. My problem comes with telling my view resolver to talk to this directory.
As you can see from the commented code, I've tried only a couple of hundred options for trying to get it to resolve the view. I've got the full file path to my project directory available on the documentRoot so if necessary and thought potentially I would need that.
I've put a breakpoint in the controller and this definitely does get hit, it just returns a 404 when trying to find the relevant view each time. Any guidance on what my viewResolver might need to look like appreciated. I can see there's a couple of Classes that may or may not be correct:
e.g. SpringResourceTemplateResolver and ClassLoaderTemplateResolver - not sure on which of these is most relevant for my requirements
#EnableWebMvc
#Configuration
public class MvcConfig implements WebMvcConfigurer,ApplicationContextAware {
private ApplicationContext context;
#Value("${server.document-root:}")
private String documentRoot;
#Override
public void setApplicationContext( ApplicationContext applicationContext ) {
this.context = applicationContext;
}
// private ITemplateResolver htmlTemplateResolver() {
// SpringResourceTemplateResolver resolver = new
// SpringResourceTemplateResolver();
// resolver.setApplicationContext(applicationContext);
// resolver.setPrefix(documentRoot);
// resolver.setCacheable(false);
// resolver.setTemplateMode(TemplateMode.HTML);
// return resolver;
// }
// #Bean
// public SpringResourceTemplateResolver templateResolver() {
// SpringResourceTemplateResolver templateResolver = new
// SpringResourceTemplateResolver();
// templateResolver.setPrefix( "/webapp/" );
// templateResolver.setCacheable(false);
// templateResolver.setSuffix(".html");
// templateResolver.setTemplateMode("HTML");
// return templateResolver;
// }
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(context);
resolver.setPrefix("templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
// public ITemplateResolver templateResolver() {
// ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
// templateResolver.setPrefix("templates/");
// templateResolver.setCacheable(false);
// templateResolver.setSuffix(".html");
// templateResolver.setTemplateMode(TemplateMode.HTML);
// templateResolver.setCharacterEncoding("UTF-8");
// return templateResolver;
// }
}
you don't need MvcConfig
just set the static-locations property in src/main/resources/application.properties to spring.resources.static-locations=classpath:/webapp/
map all possible request to your index-page like:
#GetMapping({"/index","/","/home"})
public String reactApp()
{
return "/index.html";
}
optional you could redirect the requests to your index-page
#GetMapping({"/index","/","/home"})
public String reactApp()
{
return "redirect:/index.html";
}
You don't need the MvcConfig class, Spring boot auto-configure the view resolver. All you have to do is to put your templates to the src/main/resources/templates/ directory and make a simple controller mapping like this
#RequestMapping(value = "/")
public String index() {
return "index";
}
in your home controller.
You can also check out this React.js and Spring example
you will need to configure your resource handler, that way:
#Configuration
public class StaticResourceConfiguration implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("file:/path/to/dir/webapp/");
}
}
Notice, that in unix you will use file:/ and for windows
file:///C:/path/to/dir/webapp/
And then redirect the/ to index.html
#RequestMapping
#RestController
public class IndexEndpoint {
#GetMapping("/")
public String reactApp() {
return "/index.html";
}
}
I have tested it on a MacOS and it worked prefect.
So, I was using JDK 8 with Sprint Boot 2.0.3 before and now I moved to JDK 11 and Spring Boot 2.1.1. And I had the TemplateEngine Bean which processed the thymeleaf templates for the email and it was working fine with the below configuration of ReloadableResourceBundleMessageSource
ReloadableResourceBundleMessageSource messageSourceThymeleafConfiguration = new ReloadableResourceBundleMessageSource();
messageSourceThymeleafConfiguration.setBasename("classpath*:i18n/messages");
messageSourceThymeleafConfiguration.setDefaultEncoding("UTF-8");
And now that I have moved to JDK 11 and Spring Boot 2.1.1 it has stopped working all of a sudden and in the emails instead of the locale based translations from the properties file the property keys are being displayed. For example:
??email.greeting_en??
??email.text1_en??
I even tried to replace the ReloadableResourceBundleMessageSource bean with ResourceBundleMessageSource and try it and it still doesn't work. I tried to meddle with the base name string by using classpath:i18n/messages, i18n/messages still doesn't seem to work.
Any help would be highly appreciated.
Edit: This is how my entire Configuration related to this looks
#Configuration
public class MailServiceThymeleafConfiguration {
#Bean
public TemplateEngine templateEngineThymeleafConfiguration() {
SpringTemplateEngine templateEngineThymeleafConfiguration = new SpringTemplateEngine();
templateEngineThymeleafConfiguration.setTemplateResolver(emailTemplateResolver());
templateEngineThymeleafConfiguration.setMessageSource(messageSourceThymeleafConfiguration());
return templateEngineThymeleafConfiguration;
}
#Bean
#Description("Thymeleaf template resolver serving HTML 5 emails")
public ClassLoaderTemplateResolver emailTemplateResolver() {
ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
emailTemplateResolver.setPrefix("mails/");
emailTemplateResolver.setSuffix(".html");
emailTemplateResolver.setTemplateMode("HTML5");
emailTemplateResolver.setCharacterEncoding("UTF-8");
emailTemplateResolver.setOrder(1);
return emailTemplateResolver;
}
#Bean
public MessageSource messageSourceThymeleafConfiguration() {
ReloadableResourceBundleMessageSource messageSourceThymeleafConfiguration = new ReloadableResourceBundleMessageSource();
messageSourceThymeleafConfiguration.setBasename("classpath*:i18n/messages");
messageSourceThymeleafConfiguration.setDefaultEncoding("UTF-8");
return messageSourceThymeleafConfiguration;
}
}
I am buidling application on top of the Spring Boot. At this point I've decided to use Thymeleaf to render some of the messages in this application. The application uses Thymeleaf configuration to render template provided as String's.
This is the Thymeleaf configuration:
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setDialect(templateDialect());
engine.setTemplateResolver(templateResolver());
engine.setEnableSpringELCompiler(true);
return engine;
}
#Bean
public StringTemplateResolver templateResolver() {
final StringTemplateResolver templateResolver = new StringTemplateResolver();
templateResolver.setTemplateMode(TemplateMode.TEXT);
return templateResolver;
}
#Bean
public SpringStandardDialect templateDialect() {
final SpringStandardDialect dialect = new SpringStandardDialect();
dialect.setEnableSpringELCompiler(true);
return dialect;
}
This is code that I use for rendering:
final Context context = new Context();
context.setVariables(/*provide map with "variable" in it*/);
return engine.process("[[${variable}]]", context);
The problem occurs when i try to process a list object. I want to add Formatter for the list type so it can be rendered in a right way. I've tried to do it using WebMvcConfigurer class by overriding method: AddFormatters with no success so far.