Rendering Periodic Reports

The Qute templating engine can also be very useful when rendering periodic reports. This example uses the quarkus-scheduler extension, which was added during project creation.

Creating Samples

The first step is to create an object that represents a point-in-time value (for example, ambient temperature or blood pressure of a patient).  

Start by creating a file at src/main/java/org/acme/Sample.java and adding the following to it:

package org.acme.qute;



public class Sample {

    public boolean valid;

    public String name;

    public String data;



    public Sample(boolean valid, String name, String data) {

        this.valid = valid;

        this.name = name;

        this.data = data;

    }



}
Next, create a mock service whose get() method returns a random list of samples.
Create a file at src/main/java/org/acme/SampleService.java with the following code:

package org.acme;



import java.util.ArrayList;

import java.util.List;

import java.util.Random;



import javax.enterprise.context.ApplicationScoped;



@ApplicationScoped

public class SampleService {



    private static final String[] names = {"James", "Deepak", "Daniel", "Shaaf", "Jeff", "Sally"};



    public List<Sample> get() {

        int count = new Random().nextInt(10);

        List<Sample> result = new ArrayList<>(count);

        for (int i = 0; i < count; i++) {

            boolean valid = false;

            if (Math.random() > 0.5) {

                valid = true;

            }

            result.add(new Sample(valid, names[(int)(Math.random() * names.length)], Math.random() + ""));

        }

        return result;

    }

}

Creating the Template

The next step is to create the report templates. Start by creating the directory to hold them (from the project root):  


mkdir -p src/main/resources/templates/reports/v1

Inside that directory, create a file named `report_01.json.template` with the following contents:  


{

    "time": "{now}",

    "samples": [

      {#for sample in samples}

      \{"name": "{sample.name ?: 'Unknown'}","data": "{#if sample.valid}{sample.data}{#else}--Invalid--{/if}"}{#if count < samples.size },{/if}

      {/for}

    ]

}
Here we are looping over the passed-in samples. You can iterate over Iterable, Map, and Stream objects. Since we are rendering JSON, we also need to escape the first of any pair of JSON-related } or { using \} or \{.

Also, note the use of the elvis operator {sample.name ?: 'Unknown'}; if the name is null the default value Unknown is used.
 

Creating the Periodic Reports
The last step is to write the necessary Java code to generate the reports. Create a new file at src/main/java/org/acme/ReportGenerator.java with the body:  

package org.acme;



import java.io.FileWriter;



import javax.enterprise.context.ApplicationScoped;

import javax.enterprise.event.Observes;

import javax.inject.Inject;



import io.quarkus.qute.Template;

import io.quarkus.qute.api.ResourcePath;

import io.quarkus.runtime.ShutdownEvent;

import io.quarkus.runtime.StartupEvent;

import io.quarkus.scheduler.Scheduled;



@ApplicationScoped

public class ReportGenerator {



    @Inject

    SampleService service;



    private FileWriter fout = null;



    @ResourcePath("reports/v1/report_01.json.template")

    Template report;



    @Scheduled(cron="* * * ? * *")

    void generate() throws Exception {

        String result = report

            .data("samples", service.get())

            .data("now", java.time.LocalDateTime.now())

            .render();

            System.out.println("report: " + result);

        if (fout != null) {

            fout.write(result + "\n");

            fout.flush();

        }



    }



    void onStart(@Observes StartupEvent ev) throws Exception {

        fout = new FileWriter("/tmp/report.json", true);

    }

    void onShutdown(@Observes ShutdownEvent ev) throws Exception {

        fout.close();

        fout = null;

    }

}
A few notes about the implementation:  
  • In this case, the @ResourcePath qualifier is usedto specify the template path templates/reports/v1/report_01.json.
  • Use the @Scheduled annotation to instruct Quarkus to execute this method every second. For more information see the Scheduler guide.
  • The TemplateInstance.render() method triggers rendering. Note that this method blocks the current thread.
  • Quarkus' StartupEvent and ShutdownEvent manage the File I/O on startup and shutdown. Reports will be written to the /tmp directory.
Warning: If you are running this example on Windows, be sure to change the FileWriter in the onStart method to use an appropriate directory.
To trigger report to start generating (by triggering Quarkus Live Reload), access the following endpoint:  

curl http://localhost:8080/hello?name=Jason
Assuming no errors, the reports should generate every second. Tail the file:  

tail -f /tmp/report.json
Warning: If you changed the default report location, be sure to use that file in the tail.
You should see new reports every second. When done, press CTRL+C to stop the tail.
Daniel Oh
Daniel Oh
Senior Principal Developer Advocate
Daniel Oh is a Senior Principal Developer Advocate at Red Hat. He works to evangelize building cloud-native microservices and serverless functions with cloud-native runtimes to developers. He also continues to contribute to various open-source cloud projects and ecosystems as a Cloud Native Computing Foundation (CNCF) ambassador for accelerating DevOps adoption in enterprises. Daniel also speaks at technical seminars, workshops, and meetups to elaborate on new emerging technologies for enterprise developers, SREs, platform engineers, and DevOps teams.