Class FreeMarkerExtension

java.lang.Object
org.faceless.publisher.ext.FreeMarkerExtension
All Implemented Interfaces:
ReportFactoryExtension

public class FreeMarkerExtension extends Object

An extension to use the Apache FreeMarker template library as a pre-processor to generate the HTML/XML parsed by BFO Publisher.

This extension is invoked by including a <?freemarker href="template.ftl"?> processing instruction at the start of the document - the href attribute refers to the template, so the document being parsed is an XML representation of the data. The example from the FreeMarker documentation could be created as


 <?freemarker href="path/to/template.ftl"?>
 <data>
  <user>Big Joe</user>
  <latestProduct>
   <url>products/greenmouse.html</user>
   <name>green mouse</user>
  </latestProduct>
 </data>
 

The processing instruction doesn't have to be added to the XML; it can be added via the API:


 Report report = reportFactory.createReport();
 ProcessingInstruction pi = new ProcessingInstruction().setType("freemarker").put("href", "path/to/template.ftl"");
 report.getProcessingInstructions().add(pi);
 report.load(new File("data.xml"));
 report.parse();
 PDF pdf = output.getPDF();
 pdf.render(new FileOutputStream("out.pdf"));
 

XML isn't the only way to represent data; the technique of manually adding a processing instruction can also be applied to data stored as JSON, CBOR or in a FreeMarker TemplateModel. For JSON or CBOR it is parsed the same way as XML; any stream, file or URL with a media-type of application/json or application/cbor will be passed through FreeMarker, so long as the freemarker processing-instruction has been added via the API. So to convert JSON, the code sample directly above would be modified only to change the file to JSON, eg report.load(new File("data.json"));

To load from a TemplateModel, just pass it in to the Report.load() method:


 Report report = reportFactory.createReport();
 ProcessingInstruction pi = new ProcessingInstruction("freemarker", "href=\"path/to/template.ftl\"");
 report.getProcessingInstructions().add(pi);
 Map<String,String> data = loadDataModel();     // Your method
 FreeMarkerExtension ext = reportFactory.getReportFactoryExtension(FreeMarkerExtension.class);
 Configuration cfg = ext.getConfiguration();
 report.load(cfg.getObjectWrapper().wrap(data));
 report.parse();
 PDF pdf = output.getPDF();
 pdf.render(new FileOutputStream("out.pdf"));
 

Template format and relative paths

By default the output of any Template is assumed to be HTML (even when the file containing the data is XML, as shown in the first example above). If the template outputs XML instead, set the type attribute on the processing instruction to text/xml. In XML:


 <?freemarker type="text/xml" href="path/to/template.ftl?>
 
or in Java, either of these

 new ProcessingInstruction("freemarker", "type=\"text/html\" href=\"path/to/template.ftl\"");
 new ProcessingInstruction().setType("freemarker").put("type", "text/html").put("href", "path/to/template.ftl");
 

Relative paths in the template will be resolved relative to the template file, not relative to the input.

Configuration

Any properties in the map returned from Report.getEnvironment() and ReportFactory.getEnvironment() that begin with freemarker. will be passed to the Configuration (for ReportFactory.getEnvironment()) or Environment (for Report.getEnvironment()), minus the freemarker. prefix.

Security

BFO Publisher has no control over Freemarker Template processing, and as Freemarker currently has no "runaway" checks, a careless or malicious template could easily create an OutOfMemoryError. To mitigate this, FreeMarker Templates must always be loaded from a trusted URL - see URLConnectionFactory.isTrusted(org.faceless.publisher.type.URL2). By default this means the resolved Template URL must have a scheme of classpath, file or jar, or if loading from a MemoryURLConnectionFactory the AbstractBlob.isTrusted() method must return true.

Since:
1.3
See Also:
  • MustacheExtension
  • Constructor Details

    • FreeMarkerExtension

      public FreeMarkerExtension()
      Create a new FreeMarkerExtension
    • FreeMarkerExtension

      public FreeMarkerExtension(Configuration conf)
      Create a new FreeMarkerExtension
      Parameters:
      conf - the FreeMarker Configuration to pass to setConfiguration(Configuration)
  • Method Details

    • setConfiguration

      public void setConfiguration(Configuration conf)
      Set the FreeMarker Configuration. If this is not specified by the time the first FreeMarker document is converted, a default Configuration will be used. This method can only be called once.
      Parameters:
      conf - the FreeMarker Configuration to pass to setConfiguration(Configuration)
    • getConfiguration

      public Configuration getConfiguration()
      Return the FreeMarker Configuration. If one has not been set previously, this will call setConfiguration() with a default configuration.
    • configure

      public void configure(Json json)

      Configure the FreeMarker extension.

      Currently only the the boolean parameter threads can be set to call setThreads(boolean)

      Specified by:
      configure in interface ReportFactoryExtension
      Parameters:
      json - the json configuration
    • load

      public boolean load(Object object, Report report) throws IOException
      Attempt to load the specified object into the specified Report. If object is a TemplateModel. this method will use the Template specified by the freemarker ProcessingInstruction to load the input. Otherwise it will return false.
      Specified by:
      load in interface ReportFactoryExtension
      Parameters:
      object - the object to load, which could be a TemplateModel
      report - the report
      Returns:
      true if this object can be loaded by this extension, false otherwise.
      Throws:
      IOException
    • unregister

      public void unregister(ReportFactory factory)
      Description copied from interface: ReportFactoryExtension
      Notify this object it has been removed from a ReportFactory. Will be called when this object is removed from the list returned by ReportFactory.getReportFactoryExtensions(). The default implementation is a no-op.
      Specified by:
      unregister in interface ReportFactoryExtension
    • register

      public void register(ReportFactory factory)
      Description copied from interface: ReportFactoryExtension
      Notify this object it has beem added to a ReportFactory. Will be called when this object is added to the list returned by ReportFactory.getReportFactoryExtensions(). The default implementation is a no-op.
      Specified by:
      register in interface ReportFactoryExtension
    • register

      public void register(Report report)
      Description copied from interface: ReportFactoryExtension
      Notify this object it has beem added to a Report. Will be called when this object is added to the list returned by ReportFactory.getReportFactoryExtensions(). The default implementation is a no-op.
      Specified by:
      register in interface ReportFactoryExtension
    • setThreads

      public void setThreads(boolean threads)
      Set whether to use two threads when converting the Template.

      By default only a single thread is used; the output from the template+object is written to a String, and after it completes that String is fed into the XML parser to create the Report.

      Setting threads to true will convert the template+object in a new Thread, with the output from that Thread piped into the main Thread to convert to a Report.

      Depending on the implementation of the template library, using two threads should prevent the entire XML document from being stored in memory, which will be beneficial when small or mid-size templates are combined with large models to create extremely large output.

      Parameters:
      threads - whether to use two Threads when applying the Template
    • isThreads

      public boolean isThreads()
      Return the value set by setThreads(boolean)