Dieser und weitere Beiträge werden sich mit der Template-Engine FreshMarker beschäftigen. Das Besondere an dieser Template-Engine ist, dass sie noch nicht existiert. Die Beiträge werden, parallel zur Entwicklung der Template-Engine, technische Entscheidungen und verwendete Konzepte beleuchten.
FreshMarker wird keine vollständige Neuentwicklung sein, sondern sich an der Template-Engine FreeMarker orientieren. FreeMarker glänzt mit einer langen Liste von Featuren, hat aber leider einige Schwächen. Aus Sicht des Anwenders fehlt insbesondere die Unterstützung der Java Time API, FreeMarker beherrscht leider nur java.util.Date
, java.sql.Date
und java.sql.Time
. Aus Entwicklersicht ist es unschön anzusehen, dass der Quellcode noch immer auf Java 7/Java 8 basiert.
Damit sind schon einige Anforderungen an die neue Template-Engine formuliert. Unterstützung moderner Java Versionen (Minimum Java 11), Unterstützung der Java Time API und Anlehnung an die FreeMarker Syntax.
Die Entwicklung wird erst schrittweise einige Kernkonzepte bereitstellen und diese dann später vervollständigen. Grundsätzlich soll dabei Wert gelegt werden auf eine einfache, testbare, offene, lose gekoppelte und erweiterbare Architektur.
Ein erster Testfall für die neue Template-Engine ist die Auswertung eines simplen Templates. In diesem Fall enthält es nur einen einfachen Text ohne spezielle Ergänzungen.
@Test void generateOnlytext() throws IOException, ParseException { Configuration configuration = new Configuration() TemplateLoader templateLoader = new StringTemplateLoader(); configuration.registerTemplateLoader(templateLoader); templateLoader.putTemplate("test", "the lazy dog jumps over the quick brown fox"); Template template = configuration.getTemplate("test"); assertEquals("the lazy dog jumps over the quick brown fox", template.process(Map.of())); }
Die Anwendung der neuen Template-Engine hält sich eng an ihr Vorbild. Zuerst wird eine Configuration
instanziiert und mit dieser ein Template
erzeugt. Damit die Configuration
ein Template
finden kann, wird ein StringTemplateLoader
verwendet, der die Sourcen für die Templates in einer Map
verwaltet.
public class StringTemplateLoader implements TemplateLoader { private final Map<String, TemplateSource> cache = new HashMap<>(); @Override public Optional<TemplateSource> getTemplate(String name) { return Optional.ofNullable(cache.get(name)); } public void putTemplate(String name, String content) { cache.put(name, new StringTemplateSource(content)); } private static class StringTemplateSource implements TemplateSource { final String content; public StringTemplateSource(String content) { this.content = content; } @Override public Reader getReader(Charset encoding) { return new StringReader(content); } } }
Das Ergebnis der Template Prozessierung ist der Rückgabewert der Methode process
, die als Parameter das Datenmodel als Map
übergeben bekommt.
private final BlockFragment rootFragment = new BlockFragment(); private final Configuration configuration; public void process(Map<String, Object> dataModel, Writer writer) { rootFragment.process(configuration.createEnvironment(dataModel), writer); } public String process(Map<String, Object> dataModel) { StringWriter writer = new StringWriter(); process(dataModel, writer); return writer.toString(); }
Die process
Methode existiert in zwei Varianten, die allgemeiner Version besitzt einen Writer
als Parameter und schreibt das Ergebnis der Prozessierung in den Writer
. Die zweite Variante liefert direkt einen String
zurück und nutzt intern die erste Variante mit einem StringWriter
.
Wie hier zu erkennen ist, werden intern die Klassen BlockFragment
und Configuration
genutzt. BlockFragment
gehört zu den Klassen die das Interface Fragment
implementieren und die statische Struktur des Templates beschreiben. Das Prozessieren eines Templates ist ein Delegieren an das Prozessieren seiner Fragmente. Die Configuration
wird genutzt, um für jeden einzelnen Aufruf der process
Methode eine unabhängige Verarbeitungsumgebung zu schaffen. Das Erzeugen eines Templates ist aufwendig und sollte daher möglichst nur einmal erfolgen. Durch die unabhängigen Verarbeitungsumgebungen kann eine Template
Instanzen einmal erzeugt und beliebig oft verwendet werden.
Noch bleibt die Fragen zu klären, wie ein Template erzeugt wird. Dies wird im nächsten FreshMarker Beitrag erklärt.