“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.