If you are looking at generating document report based on templates, RTFTemplate is a good choice. It allows you to use from a list of template engines at the back and allows for good user experience for template creation as well as the final product.

How it works

This is a three-step process where you need to define your application domain in form of an XML document containing all the objects and its fields, use that document along with a macro in MS Word to create your templates and finally use the template, the fields XML document & actual data to generate reports as and when.

RTF Template

Generate Field XML

To generate the fields XML file, you need to extend AbstractRTFUseCase class provided by RTFTemplate and implement the abstract method putContext. In the implementation, you need to put all your domain objects in the context passed into the method as an input parameter e.g.

package in.setia.report.rtf;

import java.util.ArrayList;
import java.util.List;
import net.sourceforge.rtf.template.IContext;

public class RtfModelCreator extends AbstractRTFUseCase {
    
    public RtfModelCreator(String path){
        super(path);
    }

    @Override
    protected void putContext(IContext rtftemplate) {
        rtftemplate.put("test", new TestObject());
        List<TestField> fields = new ArrayList<TestField>();
        fields.add(new TestField());
        rtftemplate.put("fields", fields);
    }
    
    public static void generateModelXml() {
        RtfModelCreator rmc = new RtfModelCreator(PATH);
        try {
            rmc.run(PATH+"/Test.rtf");
            String xmlFieldString = rmc.getXMLFields();
            rmc.saveXmlFields(PATH+"/app.fields.xml", true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        RtfModelCreator.generateModelXml();
    }
    
    private static String PATH ="YOUR_PATH";

}

Invoking run on this object would allow you to get hold of the XML in a String variable or save it to a file. Wherever you wish to access a collection, you need to add appropriate type objects to the collections for its fields to be accessible in the XML and hence, in MS Word later for creating templates. Running would also take an RTF document as input and generate an RTF document as output however, I typically choose to use a blank RTF document for this purpose and I find it of no use beyond this point.

Unless your model keeps changing, you only need to do this once. Here on out, customer would be able to create desired report templates and generate report based on the template as described below.

Creating templates

What is useful at this step is the app.fields.xml generated by the previous step as well as the blank template file (a .dot file) provided by RTFTemplate which also contains the macros you need to move forward from here.

Once you set the path of XML document in the Settings of the macro, the macro box would show you the fields available which can be placed anywhere in the document in any style or format desired like this –

RTF Template

If you are using collections as fields, the List flag would be Yes and you can use the START_LOOP and END_LOOP tags to loop through them anywhere in the body however, placing them in table would auto-loop it.

Once the template has been created, save it as an Rich text format file and put it into your reporting system as a template so it can be used.

Generating reports

Once templates have been created, to create a report using one of the templates and the actual report data is a piece of cake. To begin with, initialize a template object using a template builder, template file and the domain xml generated in the first step.

       RTFTemplateBuilder builder = RTFTemplateBuilder.newRTFTemplateBuilder();
       RTFTemplate rtfTemplate = builder.newRTFTemplate();
       rtfTemplate.setXmlFields(new FileInputStream(FIELDS_XML_FILE));
       rtfTemplate.setTemplate(new File(rtfSource));

Provide all the data objects to the template object just like you’d done in the first step –

        rtftemplate.put("test", new TestObject());
        rtftemplate.put("fields", fields);

and finally call the merge operation on the template object to find the report generated –

        rtfTemplate.merge(rtfTarget);

In my experience, the initialization of template object takes a couple of seconds and for use cases where report generation is a frequent process, I prefer keeping a per-initialized template objects in memory. This requires using transform() operation on the template object and storing the generated RTFDocument object in memory as shown below –

        RTFTemplate rtfTemplate = builder.newRTFTemplate();
        RTFDocument transformedDocument = 
                               (RTFDocument)transformedTemplates.get(templateId); 
        if(transformedDocument == null){
            rtfTemplate.setXmlFields(new FileInputStream(FIELDS_XML_FILE));
            rtfTemplate.setTemplate(new File(FILE_PATH + templateId));  
            transformedDocument = rtfTemplate.transform();
            transformedTemplates.put(templateId, transformedDocument);
        }
        rtfTemplate.setTransformedDocument(transformedDocument);       
        rtftemplate.put("test", new TestObject());
        rtftemplate.put("fields", fields);
        rtfTemplate.merge(rtfTarget);

You could, additionally in an initialize operation, initialize the transformedTemplates map during application startup and take away that overhead from the first request as well.

    public static void initialize(List<String> list, String fieldsXml) 
                     throws IOException, SAXException{
        transformedTemplates.clear();
        for (String template : list) {
            RTFTemplateBuilder builder = RTFTemplateBuilder.newRTFTemplateBuilder();            
            RTFTemplate rtfTemplate = null;
            try {
                rtfTemplate = builder.newRTFTemplate();
                rtfTemplate.setXmlFields(new FileInputStream(fieldsXml));
                rtfTemplate.setTemplate(new File(template));  
                RTFDocument transformedDocument = rtfTemplate.transform();    
                transformedTemplates.put(template, transformedDocument);
            } catch (Exception e) {
                e.printStackTrace();
            }              
        }
    }

Another requirement that I have come across is to generate a report where different pages of the reports are based on different templates. This usually happens when report for a collection of entities in the domain is to be created and each entity type might have its own template. However, a simple solution to this is to generate individual RTF reports from RTFTemplate and subsequently using iText RTF API to merge all RTF documents into a single document.

Hope this helps.