17

I would like to write something like :

    @Autowired
    private SpringTemplateEngine engine;
....
  // Thymeleaf Context
  WebContext thymeleafContext = new WebContext(request, response, request.getServletContext(), locale);

    // cached html of a thymeleaf template file
    String cachedHtml=....

    // process the cached html
  String html=engine.process(cachedHtml, thymeleafContext);

By default, the [process] method can't do that. I can understand from the docs that I need a special Template Resolver :

In order to execute templates, the process(String, IContext) method will be used: final String result = templateEngine.process("mytemplate", ctx); The "mytemplate" String argument is the template name, and it will relate to the physical/logical location of the template itself in a way configured at the template resolver/s.

Does anyone know how to solve my problem ?

The goal is to cache the Thymeleaf templates (files) in strings and then process theses strings rather than the files.

Serge Tahé
  • 1,869
  • 2
  • 19
  • 20
  • 2
    Why? Thymeleaf already caches parsed templates by default. – Tom Verelst Apr 03 '14 at 13:50
  • 1
    Fine. I didn't know. What is cached exactly ? the bare template before processing or the template once processed ? It is not the same for me. I want to cache the bare template. – Serge Tahé Apr 04 '14 at 06:27

4 Answers4

12

The solution we ended up using consisted of a new IResourceResolver with a custom Context rather than a custom TemplateResolver. We chose this because we still wanted to use classpath scanning in most cases, but occasionally had dynamic content.

The following shows how we did it:

public class StringAndClassLoaderResourceResolver implements IResourceResolver {


    public StringAndClassLoaderResourceResolver() {
        super();
    }


    public String getName() {
        return getClass().getName().toUpperCase();
    }


    public InputStream getResourceAsStream(final TemplateProcessingParameters params, final String resourceName) {
        Validate.notNull(resourceName, "Resource name cannot be null");
        if( StringContext.class.isAssignableFrom( params.getContext().getClass() ) ){
            String content = ((StringContext)params.getContext()).getContent();
            return IOUtils.toInputStream(content);
        }
        return ClassLoaderUtils.getClassLoader(ClassLoaderResourceResolver.class).getResourceAsStream(resourceName);
    }

    public static class StringContext extends Context{

        private final String content;

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

        public StringContext(String content, Locale locale) {
            super(locale);
            this.content = content;
        }

        public StringContext(String content, Locale locale, Map<String, ?> variables) {
            super(locale, variables);
            this.content = content;
        }

        public String getContent() {
            return content;
        }
    }

Test Case

public class StringAndClassLoaderResourceResolverTest {

    private static SpringTemplateEngine templateEngine;

    @BeforeClass
    public static void setup(){
        TemplateResolver resolver = new TemplateResolver();
        resolver.setResourceResolver(new StringAndClassLoaderResourceResolver());
        resolver.setPrefix("mail/"); // src/test/resources/mail
        resolver.setSuffix(".html");
        resolver.setTemplateMode("LEGACYHTML5");
        resolver.setCharacterEncoding(CharEncoding.UTF_8);
        resolver.setOrder(1);

        templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(resolver);
    }

    @Test
    public void testStringResolution() {
        String expected = "<div>dave</div>";
        String input = "<div th:text=\"${userName}\">Some Username Here!</div>";
        IContext context = new StringAndClassLoaderResourceResolver.StringContext(input);
        context.getVariables().put("userName", "dave");
        String actual = templateEngine.process("redundant", context);
        assertEquals(expected, actual);
    }

    @Test
    public void testClasspathResolution(){
        IContext context = new Context();
        context.getVariables().put("message", "Hello Thymeleaf!");
        String actual = templateEngine.process("dummy", context);
        String expected = "<h1>Hello Thymeleaf!</h1>";
        assertEquals(expected, actual);
    }
}

Dummy template file at src/main/resources/mail/dummy.html

<h1 th:text="${message}">A message will go here!</h1>

Note: We used Apache CommonsIO's IOUtils for converting the String to an InputStream

David Welch
  • 1,941
  • 3
  • 26
  • 34
7

You can implement your own TemplateResolver and IResourceResolver to work with String.

michal.kreuzman
  • 12,170
  • 10
  • 58
  • 70
5

for simple unit tests:

static class TestResourceResolver implements IResourceResolver {
    public String content = "";

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

    @Override
    public InputStream getResourceAsStream(TemplateProcessingParameters templateProcessingParameters,
            String resourceName) {
        return new ByteArrayInputStream(content.getBytes());
    }
}

or just use org.thymeleaf.templateresolver.StringTemplateResolver in Thymeleaf 3

Stefan K.
  • 7,701
  • 6
  • 52
  • 64
3

Yep StringTemplateResolver is the way to go.

public class ReportTemplateEngine {

    private static TemplateEngine instance;

    private ReportTemplateEngine() {}

    public static TemplateEngine getInstance() {
        if(instance == null){
            synchronized (ReportTemplateEngine.class) {
                if(instance == null) {
                    instance = new TemplateEngine();
                    StringTemplateResolver templateResolver = new StringTemplateResolver();
                    templateResolver.setTemplateMode(TemplateMode.HTML);
                    instance.setTemplateResolver(templateResolver);
                }
            }
        }
        return instance;
    }
}
Mahozad
  • 18,032
  • 13
  • 118
  • 133
Bill Comer
  • 1,033
  • 1
  • 14
  • 24
  • 1
    Ugh, Singleton... He uses Spring so defining a `StringTemplateResolver` Bean, which is Singleton by default, would be better. – Dormouse Jan 11 '19 at 13:11