FreshMarker Bricks

“Architecture starts when you carefully put two bricks together. There it begins.”

Ludwig Mies van der Rohe

In a Reddit forum, a suggestion was made to support template fragments in FreshMarker. Template fragments are parts of a template that can be used individually. The question that naturally arises is why would you want to use parts of a template individually? After all, many template engines offer the option of construct a template from different sources using include instructions. Such an include can then also be used individually.

The charm of template fragments is the overview of the overall picture that results from the individual parts. With includes, it is more difficult to maintain an overview. A small example should illustrate the use of template fragments in FreshMarker. A template was created for an email message about the passing of a relative.

My husband, ${husband}, has passed away
Dear ${employer},
                
I am writing to inform you with great sorrow that my husband, ${husband}, has passed away.
This is a difficult time for our family, and I wanted to ensure you were aware of his passing.
                
Sincerely,
${wife}

The first line is the subject of the email and the rest of the text is the body of the email.

Template template = configuration.builder().getTemplate("death notice", source);
Map<String, Object> model = Map.of("employer", "Mr. Howard Wagner", "husband", "Willy Loman", "wife", "Linda Loman");
String email = template.process(model);

With the values Mr. Howard Wagner, Willy Loman and Linda Loman, the result is as follows.

My husband, Willy Loman, has passed away
Dear Mr. Howard Wagner,
                
I am writing to inform you with great sorrow that my husband, Willy Loman, has passed away.
This is a difficult time for our family, and I wanted to ensure you were aware of his passing.
                
Sincerely,
Linda Loman

The generated text is cut apart before it is sent. Cutting the text apart could be avoided if the subject and body are available as separate templates. But a separate template for the subject seems excessive.

This is where the template fragments in the form of the FreshMarker Brick Directive come in handy.

It may come as a surprise that the fragments in FreshMarker are called bricks, but this is due to the implementation of FreshMarker. All template components in FreshMarker implement the Fragment interface. There should not be a class with the name FragmentFragment.

All parts of a template that are to be used individually must be within a Brick Directive.

<#brick 'subject'>My husband, ${husband}, has passed away</#brick>
<#brick 'body'>
Dear ${employer},
                
I am writing to inform you with great sorrow that my husband, ${husband}, has passed away.
This is a difficult time for our family, and I wanted to ensure you were aware of his passing.
                
Sincerely,
${wife}
</#brick>

In this example, there are two bricks with the names subject and body. There is a new processBrick method for generating the e-mail subject and body.

Template template = configuration.builder().getTemplate("death notice", source);
Map<String, Object> model = Map.of("employer", "Mr. Howard Wagner", "husband", "Willy Loman", "wife", "Linda Loman");
String emailSubject = template.processBrick("subject", model);
String emailBody = template.processBrick("body", model);

The processBrick method receives the name of the brick as the first parameter and only generates its content. In this example, the subject in emailSubject and the body in emailBody.

The implementation of the Brick Directive is very simple thanks to CongoCC and the visitor pattern. First, the Brick Directive is inserted into the CongoCC grammar for FreshMarker.

Brick #BrickInstruction :
    (<FTL_DIRECTIVE_OPEN1>|<FTL_DIRECTIVE_OPEN2>)
    <BRICK>
    <BLANK>
    <STRING_LITERAL>
    <CLOSE_TAG>
    Block
    DirectiveEnd("brick")
;

This defines a simple FreshMarker directive named brick with a string parameter and a single block in the body.

The Brick Directive is then evaluated using the following visit method.

@Override
public List<Fragment> visit(BrickInstruction ftl, List<Fragment> input) {
  String name = ftl.get(3).toString();
  Fragment optimize = Fragments.optimize(ftl.get(5).accept(this, new ArrayList<>()));
  template.addBrick(name.substring(1, name.length() - 1), optimize);
   input.add(optimize);
   return input;
}

The name of the brick is the fourth token in the BrickInstruction and the body of the directive is at position six and is also processed by the visitor. The result of the processing is a Fragment that is registered in the Template instance under its name.

public void processBrick(String brickName, Map<String, Object> dataModel, Writer writer) {
  Fragment brickFragment = bricks.get(brickName);
  if (brickFragment == null) {
    throw new ProcessException("missing brick: " + brickName);
  }
  process(dataModel, writer, brickFragment);
}

The actual processBrick method is very trivial after all the preliminary work. The desired brickFragment is retrieved from the bricks map and then processed using the internal process method.

By this the new FreshMarker feature is already implemented. If you mourn the death of a traveling salesman, you can use the feature right away. All others too, of course.

Leave a Comment