Es muss ja nicht immer Apache Velocity sein, Apache FreeMarker™ tuts auch!

Erstellt am 27. September 2018 von It_berater

Es gibt unterschiedliche Template Engine, wie zum Beispiel Apache Velocity aber auch Apache Freemarker.

Bei der Verwendung von Templates, kann man sich einfach auf die Präsentation der Daten konzentrieren. Das läuft nach dem MVC (Model View Controller) Pattern. Die Templats können auch über diesen Online FreeMarker Template Tester ohne Aufwand getestet werden.

Heute mal ein kleines Beispiel, mit Freemarker. Wir wollen Verzeichnisse nach Mindmaps durchsuchen, und alle gefundenen Mindmaps in eine HTML-Seite ausgeben.

Fangen wir mit einem JUnit Test an:

Hier die Testmethode:

@Test
	@DisplayName("Schreibt eine HTML Seite mit FreeMarker Generator")
	public void generateHtmlSeite() {

		List<String> verzeichnise = new ArrayList<String>(Arrays.asList(MINDMAP_INPUT_VERZEICHNISSE));

		Generator generator = new Generator();
		Configuration cfg = generator.getKonfiguration(CONFIG_DIR);

		int anzahlMindmpas = generator.generate(ERGEBNIS_HTML, MINDMAP_TEMPLATE, verzeichnise, cfg);

		assertEquals(654, anzahlMindmpas, "Die Anzahl der Mindmaps hat sich verändert.");
}

Wir brauchen einmalig (Singelton) eine Configuration, hier die Methode:

/**
	 * Liefert die Konfiguration. Dies muss nur einmal (Singelton) aufgerufen
	 * werden.
	 * 
	 * @param configPath der Path zu der Konfiguration
	 * @return die Konfiguration
	 */
	public Configuration getKonfiguration(String configPath) {
		// einmalig die Konfiguration anlegen
		Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
		try {
			cfg.setDirectoryForTemplateLoading(new File(configPath));
			cfg.setDefaultEncoding("UTF-8");
			cfg.setLocale(Locale.GERMANY);
			cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
			cfg.setLogTemplateExceptions(false);
			cfg.setWrapUncheckedExceptions(true);
		} catch (IOException e) {
			System.err.println("Fehler beim laden der Template Konfiguration. " + configPath + " " + e.getMessage());
		}
		return cfg;
	}

Und dann die Methode, die die eigentlich Arbeit macht, die auch mehrfach aufgerufen werden kann:

private void generateHtml(String ausgabeHtmlDateiName, String mindmapTemplate, List inputMindmaps,
			Configuration configuration) {

		// Datenmodell anlegen für die Nutzung im Template
		Map datenModell = new HashMap();
		datenModell.put("user", "Thomas Wenzlaff");
		datenModell.put("mindmaps", inputMindmaps);
		datenModell.put("datum", new Date());

		try {
			// das Mindmap Template holen, es wird gecached
			Template template = configuration.getTemplate(mindmapTemplate);

			// Datenmodel mit dem Template zusammenfügen und in der Konsole und
			// Ergebnis.html ausgeben
			Writer outputWriter = new OutputStreamWriter(System.out);
			template.process(datenModell, outputWriter);

			Writer fileWriter = new FileWriter(new File(ausgabeHtmlDateiName));
			try {
				template.process(datenModell, fileWriter);
			} finally {
				fileWriter.close();
			}
		} catch (TemplateException | IOException e) {
			System.err.println("Fehler im Template. " + mindmapTemplate + " " + e.getMessage());
		}
	}

Damit die obige Methode mehrfach aufgerufen werden kann, verwende ich diese Methode, die aber für die Funktionsweise der Template-Engine nicht nötig ist:

/**
	 * Generiert für die Input Verzeichnise mit dem Freemarker Template eine Ausgabe
	 * Datei im HTML Format.
	 * 
	 * @param ausgabeHtmlDateiName der zu erzeugende HTML Dateiname
	 * @param mindmapTemplate      der Name des Freemarker Template
	 * @param inputVerzeichnise    alle Input Verzeichnisse wo Mindmaps gesucht
	 *                             werden
	 * @param configuration        Konfiguration
	 * @return die Anzahl der bearbeiteten Mindmaps
	 */
	public int generate(String ausgabeHtmlDateiName, String mindmapTemplate, List<String> inputVerzeichnise,
			Configuration configuration) {

		checkParameter(ausgabeHtmlDateiName, mindmapTemplate, inputVerzeichnise, configuration);

		List<Mindmap> mindmaps = new ArrayList<>();

		for (String inputVerzeichnis : inputVerzeichnise) {
			mindmaps.addAll(getMindmaps(inputVerzeichnis));
		}

		// Sortieren der Mindmaps von A-Z
		Collections.sort(mindmaps);

		generateHtml(ausgabeHtmlDateiName, mindmapTemplate, mindmaps, configuration);

		return mindmaps.size();
	}

Ein einfaches Mindmap BE brauchen wir auch noch:

package de.wenzlaff.mindmap.be;

import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Objects;

/**
 * Eine Mindmap BE.
 * 
 * @author Thomas Wenzlaff
 *
 */
public class Mindmap implements Comparable<Mindmap> {

	private Path path;

	public Path getPath() {
		return path;
	}

	public void setPath(Path path) {
		this.path = path;
	}

	public String getErstelldatum() {
		return new Date(getPath().toFile().lastModified()).toGMTString();
	}

	/**
	 * LIefert den Namen der Mindmap Datei.
	 * 
	 * @return der Name der Datei.
	 */
	public String getName() {
		return getPath().getFileName().toString();
	}

	/**
	 * Liefert die Größe der Mindmap Datei in kB (1000).
	 * 
	 * Im Template Aufruf z.B.: (${mindmap.size} kB)
	 * 
	 * @return die Größe der Datei in kB.
	 */
	public String getSize() {
		return "" + getPath().toFile().length() / 1000;
	}

	@Override
	public int hashCode() {
		return Objects.hash(path);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Mindmap other = (Mindmap) obj;
		return Objects.equals(path, other.path);
	}

	/**
	 * Der Name der Mindmap ohne Prefix.
	 */
	@Override
	public String toString() {
		return getName().substring(0, getName().length() - 5);
	}

	/**
	 * Zum sortieren von A-Z nach Namen.
	 */
	@Override
	public int compareTo(Mindmap mindmap) {
		return getName().compareTo(mindmap.getName());
	}

}

Nun brauchen wir noch ein Apache Freemarker Template:

  Mindmaps von ${user}


Mindmaps von ${user}!


<#list mindmaps as mindmap> ${mindmap},

Anzahl Mindmaps ${mindmaps?size}

Generiert mit TWMindmapGernerator am ${datum?datetime} Uhr

Dies Template, kann man leicht ändern, um Anpassungen an der Ausgabe zu erhalten.

Dann brauchen wir eine pom.xml mit den Freemarker und JUnit Abhängigkeiten:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>de.wenzlaff.twmindmapgenerator</groupId>
	<artifactId>de.wenzlaff.twmindmapgenerator</artifactId>
	<version>0.0.1-SNAPSHOT</version>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>

		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<junit.jupiter.version>5.0.0-M2</junit.jupiter.version>
		<junit.vintage.version>4.12.0-M2</junit.vintage.version>
		<junit.platform.version>1.0.0-M2</junit.platform.version>
	</properties>


	<build>
		<plugins>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.7.0</version>
				<configuration>
					<source>${maven.compiler.source}</source>
					<target>${maven.compiler.target}</target>
					<encoding>${project.build.sourceEncoding}</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.freemarker</groupId>
			<artifactId>freemarker</artifactId>
			<version>2.3.28</version>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${junit.jupiter.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.vintage</groupId>
			<artifactId>junit-vintage-engine</artifactId>
			<version>${junit.vintage.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

Das ist eigentlich alles für das generieren. Jetzt nur noch die Verzeichnisse einlesen und das Ergebnis ausgeben, durch das Starten des JUnit Tests:

So …

Jetzt kann man durch eine kleine Änderung des Templates, z.B. eine Tabelle ausgeben:

<table> 
<#list mindmaps as mindmap>
<tr><td>${mindmap}</td> <td>(${mindmap.size} kB)</td> <td>${mindmap.erstelldatum}</td></tr> 
</#list>
</table>

Die dann so aussieht:

Genauso könnte man leicht CSV oder auch sonstige Dateiformate ausgeben.

Das ganze Projekt lade ich dann noch auf GitHub hoch, aber nicht mehr heute …

Ähnliche Artikel:

  1. Standalone RESTful Server und Client mit dem Restlet Framework
  2. Rest Test Programm: Java Migration von JUnit 4 nach JUnit 5 (1.0.0.-M6)
  3. Kaum ist man im Urlaub, erscheint nicht der Raspberry Pi 4 oder 5 sondern das GA Release von JUnit 5