Search code examples
spring-bootthymeleafcompletable-futurespring-thymeleaf

Thymeleaf view not found with CompletableFuture


I have a strange issue. Given this controller code:

return CompletableFuture
                .supplyAsync(() -> this.acknowledgementTemplatingService.prepareHtmlViewForDocument(offer))
                .thenApply(htmlContent -> documentService.generatePdfDocumentFromHtml(htmlContent, ASSETS))

Given this templating code from this.acknowledgementTemplatingService.prepareHtmlViewForDocument(offer)

Using the templating engine from thymeleaf: ITemplateEngine

Context ctx = new Context();
ctx.setVariable(
                ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
                new ThymeleafEvaluationContext(applicationContext, null));

ctx.setVariable("offer", offerDto);
return templateEngine.process("/documents/offer/pdf", ctx);

When this code runs, the template /documents/offer/pdf cannot be found by the templating engine.

When i refactor this code to the following - calling the template rendering AND the pdf generation in one step:

return CompletableFuture
                .supplyAsync(() -> {
String htmlContent = this.serviceDescriptionTemplatingService.prepareHtmlViewForDocument(offerDto);
byte[] pdfContent = documentService.generatePdfDocumentFromHtml(htmlContent, ASSETS);
return pdfContent;
}

The view will be found and will be rendered properly.

What am i doing wrong?


Solution

  • I found a solution for this: supplyAsync() offers as additional parameter an Executor to be used. I have a dedicated Executor in my configuration like this:

    @Bean
        public Executor executor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(6);
            executor.setMaxPoolSize(10);
            executor.setQueueCapacity(100);
            executor.setThreadNamePrefix("DmpkApplication-");
            executor.initialize();
            // the following is necessary because of:
            // https://stackoverflow.com/a/57434013/7320372
            return new DelegatingSecurityContextAsyncTaskExecutor(executor);
        }
    

    I can autowire this bean and use it like so:

    return CompletableFuture
                    .supplyAsync(() -> this.serviceDescriptionTemplatingService.prepareHtmlViewForDocument(offer), executor)
                    .thenApply(html -> documentService.generatePdfDocumentFromHtml(html, ASSETS))
                    .thenApply(bytes -> this.handleDownload(bytes, offer.getIssueKey()));
    

    So the trick seems to use my dedicated executor in the first supplyAsync statement.