1

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?

Thomas Lang
  • 1,285
  • 17
  • 35

2 Answers2

1

I think It looks like the issue is related to the relative path of the template file. When you call templateEngine.process("/documents/offer/pdf", ctx) in this.acknowledgementTemplatingService. prepareHtmlViewForDocument(offer), the path /documents/offer/pdf is resolved relative to the classpath root.

Anyway, when you combine the rendering and PDF generation code together and call this.serviceDescriptionTemplatingService.prepareHtmlViewForDocument(offerDto), it seems that the path is being resolved correctly, possibly because the current working directory or classpath root is different.

In my opinion, you can try using an absolute path instead of a relative path when calling templateEngine.process() to fix the problem. For example, if your pdf template file is located in the resources/templates/documents/offer/pdf.html directory, you can use absolute path:

ctx.setVariable("offer", offerDto);
return templateEngine.process("classpath:/templates/documents/offer/pdf", ctx);
Soheil Babadi
  • 562
  • 2
  • 4
  • 15
0

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.

Thomas Lang
  • 1,285
  • 17
  • 35