Hat man eine Idee implementiert, dann ist die nächste oft nicht weit. Die Telephone Bibliothek kann nun schon mit Telefonnummern und Rufnummernblöcken umgehen und bietet Unterstützung für Bean Validation und Jackson. In diesem Beitrag kommt der Support der Template-Engine FreshMarker hinzu.
Die Integration von Erweiterungen ist ein wichtiges Feature der Template-Engine FreshMarker. Ein einfaches Plug-In System erlaubt es, eigene Datentypen und Funktionen hinzuzufügen. Zentrales Element ist dabei der eigene PluginProvider
, der automatisch über den ServiceLoader
geladen wird. Damit der PluginProvider
der Telephone Bibliothek erkannt wird, wird eine Datei META-INF/services/org.freshmarker.core.plugin.PluginProvider
benötigt, in der die zu ladene Klasse eingetragen wird.
Der PhoneNumberPluginProvider
der Telephone Bibliothek registriert die zu unterstützenden Klassen InternationalPhoneNumber
, NationalPhoneNumber
und PhoneNumberBlock
.
public class PhoneNumberPluginProvider implements PluginProvider { @Override public void registerMapper(Map<Class<?>, Function<Object, TemplateObject>> mapper) { mapper.put(InternationalPhoneNumber.class, o -> new TemplatePhoneNumber((PhoneNumber) o, true)); mapper.put(NationalPhoneNumber.class, o -> new TemplatePhoneNumber((PhoneNumber) o, false)); mapper.put(PhoneNumberBlock.class, o -> new TemplatePhoneNumberBlock((PhoneNumberBlock) o)); } // ... }
Für jede Klasse wird eine Funktion angegeben, wie deren Instanzen in TemplateObject
Instanzen eingefügt werden. PhoneNumber
Instanzen werden in TemplatePhoneNumber
Instanzen eingefügt und PhoneNumberBlock
Instanzen in TemplatePhoneNumberBlock
Instanzen.
public class TemplatePhoneNumber extends TemplatePrimitive<PhoneNumber> { private final boolean international; public TemplatePhoneNumber(PhoneNumber phoneNumber, boolean international) { super(phoneNumber); this.international = international; } public PhoneNumber getNational() { return international ? ((InternationalPhoneNumber)getValue()).toNational() : getValue(); } }
Diese TemplateObject
Subklassen sind nicht besonders komplex, weil alles Notwendige schon in der Klasse TemplatePrimitive
implementiert wurde. Bei der Implementierung der FreshMarker Unterstützung fiel auf, dass die toString
Methode der beiden PhoneNumber
Klassen unterschiedlich implementiert wurden. Dies wurde bei der Gelegenheit korrigiert. Die Methode getNational
wird für den nächsten Schritt bei der FreshMarker Unterstützung benötigt.
Wie das große Vorbild FreeMarker, kennt FreshMarker sogenannte Built-Ins. Diese Built-Ins sind Funktionen, die das Ergebnis einer Interpolation verändern. Die Interpolation "${firstName}"
liefert beispielsweise den Text "Jens"
. Das Built-In upper_case
verändert Text-Interpolationen, indem der Text in Großbuchstaben umgewandelt wird. Daher liefert die Interpolation "${firstName?upper_case}"
den Text "JENS"
.
Für die TemplatePhoneNumber
Klasse sollen die Bult-Ins national
, ndc
, sn
und ext
zur Verfügung gestellt werden. Für die TemplatePhoneNumberBlock
Klasse gibt es die Built-Ins first
, last
und list
.
public class PhoneNumberPluginProvider implements PluginProvider { private static final BuiltInKeyBuilder<TemplatePhoneNumber> PHONE_NUMBER_BUILDER = new BuiltInKeyBuilder<>(TemplatePhoneNumber.class); private static final BuiltInKeyBuilder<TemplatePhoneNumberBlock> PHONE_NUMBER_BLOCK_BUILDER = new BuiltInKeyBuilder<>(TemplatePhoneNumberBlock.class); @Override public void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns) { builtIns.put(PHONE_NUMBER_BUILDER.of("national"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> new TemplatePhoneNumber( ((TemplatePhoneNumber) x).getNational(), false))); builtIns.put(PHONE_NUMBER_BUILDER.of("ndc"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> new TemplateString( ((TemplatePhoneNumber) x).getValue().getNationalDestinationCode()))); builtIns.put(PHONE_NUMBER_BUILDER.of("sn"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> new TemplateString( ((TemplatePhoneNumber) x).getValue().getSubscriberNumber()))); builtIns.put(PHONE_NUMBER_BUILDER.of("ext"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> new TemplateString( ((TemplatePhoneNumber) x).getValue().getExtension().orElse("")))); builtIns.put(PHONE_NUMBER_BLOCK_BUILDER.of("first"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> ((TemplatePhoneNumberBlock) x).getFirst())); builtIns.put(PHONE_NUMBER_BLOCK_BUILDER.of("last"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> ((TemplatePhoneNumberBlock) x).getLast())); builtIns.put(PHONE_NUMBER_BLOCK_BUILDER.of("list"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> getSequence((TemplatePhoneNumberBlock) x))); } private static TemplateObject getSequence(TemplatePhoneNumberBlock x) { List<Object> templatePhoneNumbers = x.getValue().phoneNumbers().map(y -> new TemplatePhoneNumber(y, true)).map(Object.class::cast).toList(); return new TemplateListSequence(templatePhoneNumbers); } }
Die Built-Ins ndc
, sn
und ext
liefern die einzelnen Komponenten der Telefonnummer als TemplateString
Instanzen.
builtIns.put(PHONE_NUMBER_BUILDER.of("ndc"), new FunctionalBuiltIn((TemplateObject x, List<TemplateObject> y, ProcessContext e) -> new TemplateString(((TemplatePhoneNumber) x).getValue().getNationalDestinationCode())));
Die Built-Ins werden, wie hier ndc
, als FunctionalBuiltIn
in eine Map eingefügt. Bei der Auswertung des Built-Ins wird die PhoneNumber
aus der TemplatePhoneNumber
Instanz geholt, der NationalDestinationCode extrahiert und als neue TemplateString
Instanz zurückgegeben.
Das Built-In national
liefert für eine internationale Telefonnummer die entsprechende nationale Variante und die Built-Ins first
und last
liefern für Rufnummernblöcke den ersten und letzen Eintrag als internationale Telefonnummer. Das Built-In list
liefert alle Telefonnummern eines Rufnummernblocks als Liste zurück. Dazu nutzt es die neue Methode phoneNumbers
der PhoneNumberBlock
Klasse, die einen Stream von InternationalPhoneNumber
Instanzen liefert.
Das folgende Template liefert für den Rufnummernblock eine HTML Liste mit Einträgen für die ersten 10 enthaltene Telefonnummer.
<ul> <#list numberBlock?list[0..10] as s, l> <li class="${l?item_parity}">${s}</li> </#list> </ul>
Für die Rufnummernblock "+49 173 1122 XXX"
also die folgende Ausgabe.
<ul> <li class="odd">+49 173 112200</li> <li class="even">+49 173 112201</li> <li class="odd">+49 173 112202</li> <li class="even">+49 173 112203</li> <li class="odd">+49 173 112204</li> <li class="even">+49 173 112205</li> <li class="odd">+49 173 112206</li> <li class="even">+49 173 112207</li> <li class="odd">+49 173 112208</li> <li class="even">+49 173 112209</li> </ul>
Damit ist die Unterstützung für die FreshMarker Template-Engine fertiggestellt und kann in eigenen Projekten genutzt werden.