Loading view templates from a database with Thymeleaf

TL;DR This post presents a custom template resolver for Thymeleaf and Spring MVC. It also shows how to compose multiple resolvers so that some fragments of a view are loaded from the classpath location and some others from a dynamically chosen database record.


Some time ago I was working on a web application which purpose was to serve complex forms to the end users. Those forms had to be updatable (without app redeployment) by the administrator. Fortunately, the project used Thymeleaf and Spring MVC, which enabled our team to come up with an elegant solution.

The goal

In the original problem, the forms were generated from Thymeleaf templates. The forms were quite complex, so we needed all of the Thymeleaf features available.

The solution was to put the forms into database records and then load them during the page rendering. Then an admin would be able to add new records with updated templates. Such solution guarantees both flexibility of template managment and full-fledged view processing that Thymeleaf provides.

Of course not all of the content had to be loaded from the DB. Fragments such as page header and footer were still loaded from a classpath location as in the standard Spring MVC setup.

A simple view

An example of a view looks as follows:

<html>  
<head>  
</head>  
<body>  
    <div th:replace="_header"></div>
    <div th:replace="db:1"></div>
    <div th:replace="_footer"></div>
</body>  
</html>  

As you can see, it contains three fragment inclusions. The _header and _footer are supposed to be standard classpath resources. The middle one, db:1 indicates that fragment should be loaded from the database entity with ID = 1.

Of course it could be more complex than referencing template by ID. E.g. you could have multiple types of entities with names, categories etc. For the sake of simplicity, let's stick with the basic solution and a minimal entity:

@Entity
public class Template {

    @Id @GeneratedValue
    private Long id;
    private String content;

    Template() {
    }

    public Template(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

Composing template resolvers

The basic Thymeleaf configuration looks like this:

@Bean
public TemplateResolver springThymeleafTemplateResolver() {  
    SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    resolver.setPrefix("classpath:/templates/");
    resolver.setSuffix(".html");
    resolver.setOrder(1);
    return resolver;
}

@Bean
public DbTemplateResolver dbTemplateResolver() {  
    DbTemplateResolver resolver = new DbTemplateResolver();
    resolver.setOrder(2);
    return resolver;
}

@Bean
public SpringTemplateEngine thymeleafTemplateEngine() {  
    SpringTemplateEngine engine = new SpringTemplateEngine();
    engine.setTemplateResolvers(
            Sets.newHashSet(springThymeleafTemplateResolver(), dbTemplateResolver()));
    return engine;
}

As you can see, it's a pretty standard config except the fact, that the template engine has 2 template resolvers.

SpringThymeleafTemplateResolver is a standard resolver that handles views from the classpath location. It's pretty much the same what Spring Boot would configure by default. The second resolver (dbTemplateResolver) is a custom one. Notice that both resolvers have their order set, so that the standard one will be examined first, and anything that it cannot resolve will be handled by the second one.

DbTemplateResolver

The simplest implementation of DbTemplateResolver looks as follows:

public class DbTemplateResolver extends TemplateResolver {

    private final static String PREFIX = "db:";

    @Autowired
    TemplateRepository templateRepo;

    public DbTemplateResolver() {
        setResourceResolver(new DbResourceResolver());
        setResolvablePatterns(Sets.newHashSet(PREFIX + "*"));
    }

    @Override
    protected String computeResourceName(TemplateProcessingParameters params) {
        String templateName = params.getTemplateName();
        return templateName.substring(PREFIX.length());
    }

    private class DbResourceResolver implements IResourceResolver {

        @Override
        public InputStream getResourceAsStream(TemplateProcessingParameters params, String resourceName) {
            Template template = templateRepo.findOne(Long.valueOf(resourceName));
            if (template != null) {
                return new ByteArrayInputStream(template.getContent().getBytes());
            }
            return null;
        }

        @Override
        public String getName() {
            return "dbResourceResolver";
        }
    }
}

The most important points are:

  1. The class extends TemplateResolver from Thymeleaf. A template resolver is responsible for loading HTML markup. It usually converts template names such as index into resource names like classpath:/templates/index.html or WEB-INF/templates/index.html. Then a resource is loaded by IResourceResolver implementation. TemplateResolver base class also gives some features for free -- e.g. configurable template caching.

  2. DbTemplateResolver takes into consideration only such template names that start with the db: prefix. Then, the computeResourceName method removes the prefix, so the resulting resource name is the ID of the template to be loaded from the database.

  3. The template content resolution is delegated to the nested class DbResourceResolver that implements IResourceResolver interface. The resource resolver uses the resource name (which is template ID) to fetch the Template entity from the repository. And that's basically it! Of course for a production-ready implementation some error handling would be required.

Dynamic template ID resolution

The setup described so far works fine, but we need a more dynamic approach. In the view example above, the ID of the template was hardcoded in the HTML. It doesn't have to be like that. It can be determined on runtime, based on whatever criteria you want (e.g. user input). The following controller presents this approach:

@Controller
public class HelloController {

    @Autowired
    TemplateRepository templateRepo;

    @RequestMapping("/")
    public String hello(@RequestParam String username, Model model) {

      Long templateId = templateRepo.findLatestTemplateId();

      model.addAttribute("templateId", templateId);
      model.addAttribute("username", username);

      return "index";
    }
}

In this example, the template ID is simply determined as the latest one in the repository. As I wrote above, the logic could be more complex if required. The bottom line is that the controller somehow obtains the value and puts it as templateId model attribute.

Finally, you can use that attribute in the index view:

<html>  
<head>  
</head>  
<body>  
    <div th:replace="_header"></div>
    <div th:replace="db:__${templateId}__"></div>
    <div th:replace="_footer"></div>
</body>  
</html>  

As you can see, I used Thymeleaf preprocessing. The ${templateId} expression is surrounded by a double underscore symbol so it will be processed before the fragment template resolution.

The username model attribute could be used in both index view and the fragment loaded from the DB. As I mentioned at the beginning, the whole power of Thymeleaf is available -- only the location of the HTML code has changed.

Summary and the sources

Thymeleaf is a powerful and highly customisable engine, that can be easily extended. As I showed in this article, it's quite straightforward to load templates from multiple locations -- including a database. In my case it was of great help. I believe that delivering the project on time would have been much more of a challenge without it.

I published a working app which uses the described approach. As usual, you can find the sources on Github. It's a Spring Boot application with an embedded database, so running it is as simple as executing a jar. You'll find some info about how to play with it in the README file.


comments powered by Disqus