How to register a formatter for a type in ThymeLeaf (non-web environment)

57 views Asked by At

I have a SpringBoot based application (SpringBoot 3.2.3). The application is not a web application, i.e. it does not use servlets, views etc.

In the application, I want to create a report in the HTML format and send it via email. For creating the HTML, I want to use ThymeLeaf. I do it like this:

Context context = new Context();

context.setVariable("reportName", reportName);
context.setVariable("someOtherData", data);

return templateEngine.process("report", context);

Some of the values to be displayed in the report are of type java.time.Duration, others are of type java.util.Date (and some others). My goal is that those values are automatically formatted as I like it.

For that purpose, I created Printer classes and registered them in the Spring registry:

@Configuration
public class Config implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addPrinter(new DatePrinter());
        registry.addPrinter(new DurationPrinter());
    }

}

where e.g. the DatePrinter looks like

public class DatePrinter implements Printer<Date> {

    @Override
    public String print(Date date, Locale locale) {
        return new SimpleDateFormat("yyyy-MM-dd").format(date);
    }

}

But that registrations do not seem to have any effect: In the report, all the values are displayed via their toString method.

I also tried to register the formatters as Formatters rather than Printers, but it still didn't work.

In my view, I did everything as described e.g. in the Thymeleaf docs. Could anybody tell me how I can register custom formatters for types?

I've seen some question to this topic here on SO, but they all are for the WebMvc context.

Thank you!

2

There are 2 answers

2
Metroids On

I got it to work by doing this:

Application Configuration:

public static void spring() {
  SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
  AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext("quickthymeleaftest.formatter");
  resolver.setApplicationContext(app);
  resolver.setPrefix("classpath:/html/");
  resolver.setSuffix(".html");
  resolver.setCharacterEncoding("UTF-8");
  resolver.setTemplateMode(TemplateMode.HTML);
  
  SpringTemplateEngine engine = new SpringTemplateEngine();
  engine.setTemplateResolver(resolver);
  ConversionService c = app.getBean(ConversionService.class);
  
  Context context = new Context();
  final ThymeleafEvaluationContext evaluationContext = new ThymeleafEvaluationContext(app, c);
  context.setVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME, evaluationContext);
  context.setVariable("blah", new Blah());
  
  String html = engine.process("index", context);
  System.out.println(html);
}

In the package quickthymeleaftest.formatter:

@Configuration
public class Config {
    @Bean(name="conversionService")
    public ConversionService getConversionService() {
        ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
        Set<Converter> converters = new HashSet<>();
        converters.add(new BlahConverter());
        bean.setConverters(converters); //add converters
        bean.afterPropertiesSet();
        return bean.getObject();
    }
}

And

public class BlahConverter implements Converter<Blah, String> {
    @Override
    public String convert(Blah blah) {
        return "Blah: " + blah.x;
    }
}
0
fml2 On

Just for the record: The eventual implementation looks like this:

public class ReportBuilder {

    @Autowired
    private final TemplateEngine templateEngine;

    @Autowired
    private final ApplicationContext applicationContext;

    @Autowired
    private final ConversionService conversionService;
    
    public String buildReport(String reportNamen, SomeOtherData someOtherData) {
        Context context = new Context();

        context.setVariable("reportName", reportName);
        context.setVariable("someOtherData", data);
        
        context.setVariable(
            ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
            new ThymeleafEvaluationContext(applicationContext, conversionService));
        

        return templateEngine.process("report", context);
    }

}

Other classes from the original question were not changed.

Many thanks to @Metroids for the hint!