Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Framework: PDF'ing a template that uses highcharts JS library via a Job

This is an extension of a previous post I made.

To summarize what is going on:

  • I am using a Job that gets executed hourly that will generate a PDF to send as an attachment in an e-mail
  • The Job doesn't do much but call directly onto a controller to generate the PDF and send the e-mail. I call a controller to do the work since I am using the PDF module which (currently) requires an HTTP request as part of its PDF processing. Here is how I call the controller via the Job:

    WS.url("my/url/that/points/to/the/controller").get();

  • My previous issue with PDF'ing a template that includes a highcharts JS chart is that it generated the chart client-side, which was too late for the PDF generation and hence my PDF got produced minus the chart. To get around this, I am now using highcharts-serverside-export to generate the chart server-side

If I use the same classes above and render the template in the browser (i.e. go via the controller directly and ignore the Job), the chart gets created server side and the view gets rendered correctly in the browser.

I am generating the chart in the template by calling another controller like this:

<img src="@{ChartGenerator.go()}">

The ChartGenerator controller just basically builds the chart server-side as per the highcharts-serverside-export documentation and calls Play's renderBinary method.

As I said, the template renders fine in the browser with the server-side generated chart. However, when going via the Job that executes hourly, the ChartGenerator.go() call doesn't seem to work. The console spits this out:

INFO  ~ /chartgenerator/go is not a URL; may be relative.

Does anyone have any ideas how this can be fixed? I have proved that it works minus the Job and now need to figure out why when going via the Job, it doesn't seem to work.

Edit: As per Pere's suggestion, my template now calls the ChartGenerator class by doing this (note the double @'s):

 <img src="@@{ChartGenerator.go()}">

I think that has gotten me a bit further with now this getting spat out in the logs:

Error during job execution (fun.EmailJob)
Execution exception (In /fun/EmailJob.java around line 19)
RuntimeException occured : java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException: No response received after 60000
...
09:23:54,687 WARN  ~ bad URL given: http://<full url>/chartgenerator/go
java.net.SocketTimeoutException: Read timed out

If I hit the URL of http://< full url >/chartgenerator/go in the browser, the highcharts png file gets rendered in the browser correctly. And as expected, even after this double @ change, if I render the template in the browser (without pdf'ing), the template renders correctly with the server-side generated chart.

Edit #2: With these problems I seem to be having by calling a controller from within a template to render an image (binary), I'm wondering whether it is possible to pass the File object (containing the image) as a parameter to the render(...) method for the template. So for example, let's say the controller that renders the template does this:

File image = ... // PNG chart as built by the highcharts-serverside-export library
...
File emailAttachment = new File("attachment.pdf");
PDF.writePDF(emailAttachment, "myTemplate.html", image); // This calls the PDF module to render the PDF from the given template and write it to the attachment.pdf File object

I'm wondering whether I could somehow render that image in the template directly without having to go via the @@{...} way?

I tried putting ${image} in the template, but that just rendered attachment.pdf on the screen (kinda expected).

Edit #3: Here is what the ChartGenerator class looks like:

public final class ChartGenerator extends Controller {
    public static void go() throws Exception {
        ChartOptions options = SamplesFactory.getSingleton().createColumnBasic();
        HighchartsExporter pngExporter = ExportType.png.createExporter();
        File chart = new File("column-basic.png");
        pngExporter.export(options, null, chart);
        response.setContentTypeIfNotSet("image/png");
        renderBinary(chart);
    }
}

I'm currently just generating a sample chart server-side to prove that it can be pdf'ed. The sample chart generation is done as per the highcharts-serverside-export documentation.

Edit #4: I also tried adding an action method to the controller to allow pdf'ing while in the browser, and the server side generated highchart also doesn't appear in the pdf and the previously mentioned exception still occurs. So I can rule out it being a problem with the workflow of Job to Controller. (of course rendering the template without pdf'ing still works fine)

Edit #5: To help narrow down the possible causes of the problem, I decided to ignore highcharts (along with the highcharts-serverside-export library) and just use the simple server side charting library jfreechart. Again, I can render the template without pdf'ing, but as soon as I try and pdf a template that includes a chart (rendered via the aforementioned @@ call), it ends up failing for the same reason (i.e. bad URL given, java.net.SocketTimeoutException: Read timed out).

like image 492
digiarnie Avatar asked Dec 14 '11 08:12

digiarnie


1 Answers

Okay, I've managed to get it to work (finally). It all stems from the fact that I'm in DEV mode (obviously because I'm still developing this bit of functionality). But while in DEV mode, I (by default) only have access to one thread. So all I had to do was uncomment the execution pool in application.conf:

play.pool=3

and then my highchart started rendering on the server and then getting inserted as part of the PDF. The additional thread was used for the request to render the chart. One thread was not enough for this scenario and therefore the call to the URL to render the image binary hung.

like image 115
digiarnie Avatar answered Oct 28 '22 22:10

digiarnie