Xplate ist ein nahe an den XML-Standards implementierter XML-Prozessor. Hauptzweck ist das Verarbeiten von Web-Requests und Generieren von dynamischen Web-Inhalten, wobei Xplate nicht darauf eingeschränkt ist.
Das historisch erste XML-basierte System war wohl XSLT, das heute nur noch in speziellen Fällen eine Rolle spielt. Die Anwendung für Web-Seiten und HTML hat bis fast vollständig an Bedeutung verloren. Es wurde hauptsächlich durch JSP und deren Nachfolgersysteme, sowie rein Java-basierte Systeme verdrängt.
Diese Frameworks weisen inzwischen eine enorme Komplexität auf und sind selbst für langjährige Experten schwerlich einsehbar. Die Systeme sind gewachsen, mit der Anforderung auf Abwärtskompatibilität. Einige Teile, wie zum Beispiel die Tag-Unit ist in JSF derart tief verbaut, das auf dieser Ebene der Eingriff so gut wie nicht mehr möglich ist.
Das Verständnis, wann ein Tag aufgebaut wird, in welchen Phasen welche Variablen zur Verfügung stehen, wie weit andere Tags instantiiert sind, ist kaum mehr nachvollziehbar. Ein Effekt ist, dass das Verhalten von <c:forEach>, <ui:repeat> oder anderen dynamischen Elementen gerade in komplizierten Situationen nicht verstanden wird. Kleine Änderungen können ein unvoraussagbares Verhalten der Anwendung bewirken.
Auch andere Neuerungen, die vom Ansatz her eigentlich sehr nützlich sind, gestalten sich in der praktischen Anwendung als seht kompliziert. Als Beispiel seien hier Behaviors erwähnt, die nur bei speziellen Attributen wirken und explizit unterstützt werden müssen. Wenn dann auch noch sogenannte Composite Components im Spiel sind, bekommen selbst Hersteller großer Komponentenbibliotheken Probleme.
Auch das Einsatzgebiet ist stark eingeschränkt. Factories und viele statische Elemente sind so mit dem Web-Request verbunden, dass selbst das einfache Laden oder Rendern einer XHTML-Datei außerhalb des vorgesehenen Servlets zum Verhängnis wird.
Der Umgang mit EL-Expressions ist momentan eine Herausforderung für die wirklichen Profis. Die Kontexte sind mit der Tag-Erzeugung (Facelet-Context), Include-Anweisungen und Composite-Tags verbunden. Wobei Composite-Tags höchst ineffizient sind, da viele Tags durch den Speicher- und CPU-Intensiven Tag-Mechanismus geleitet werden müssen.
Der Umgang mit sogenannten View-States ist ein überladenes Thema, da die Komponentenbäume unmittelbar mit den gespeicherten Daten verbunden sind. Dadurch muss bei jedem Request der komplette Komponentenbaum rekonstruiert werden, auch wenn beispielsweise nur ein einziges Häkchen geändert wird.
Für eine Tag-Library inklusive Content-Assist und Hilfe-Texte ist nichts weiter nötig, als eine XSD-Datei zu generieren. Die Frameworks zum Einlesen von XML-Dateien liegen auch bereits lange vor, diese prüfen sogar auf Verstöße gegen diese Schema-Dateien.
Entsprechende Java-Klassen können anhand des XML-Namespace gefunden werden, dies können durch Konfiguration oder Annotationen zur Verfügung gestellt werden. Für das Abwickeln von Web-Requests ist dann nur noch ein kleines System erforderlich, das einen Kontext bereit hält, mit dem eine derart gestaltete XHTML-Seite abgearbeitet und das Ergebnis ausgegeben werden kann.
Die Komponentenbäume werden zustandsfrei gespeichert, der aktuelle Zustand in einfachen Kontekten gespeichert. Die Komponentenbäume können daher Request-, Session- und Applikationsübergreifend wiederverwerdet werden. Es fällt keinerlei Berechnungsaufwand für das Erzeugen, Serialisieren und Wiederherstellen an. Der Zustand wird durche den XplateContext transportiert und braucht sehr wenig Speicher.
Xplate läd die Seiten beim ersten Zugriff. Und weil die Seiten statisch sind, werden sie für konsequente Aufrufe gecacht:
INFO 2014-04-20 16:46:40,373 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 1110 ms INFO 2014-04-20 19:12:37,290 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 11 ms INFO 2014-04-20 19:12:39,209 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 10 ms INFO 2014-04-20 19:12:40,105 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 17 ms INFO 2014-04-20 19:12:41,074 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 17 ms INFO 2014-04-20 19:12:41,887 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 12 ms INFO 2014-04-20 19:12:42,677 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 8 ms INFO 2014-04-20 19:12:43,368 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 8 ms INFO 2014-04-20 19:12:44,036 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 8 ms INFO 2014-04-20 19:12:44,662 com.intersult.xplate.servlet.XplateServlet processing of '/index.xhtml' took 7 ms
Die Konfiguration enthält unter anderem auch eine XmlConfig, die über das XML-Element xmlConfig erreichbar ist.
Die Konfiguration kann so aussehen:
<?xml version="1.0" encoding="UTF-8"?> <xplate-config> <xmlConfig> <pretty>false</pretty> </xmlConfig> <resolver>com.intersult.xplate.spring.SpringBeanElResolver</resolver> </xplate-config>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" <meta:include viewId="/fragment.xhtml"/> </html>
Ein derartiges Fragment kann so aussehen:
<?xml version="1.0" encoding="UTF-8"?> <meta:tag xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" xmlns:flow="http://intersult.com/xplate/flow" xmlns:h="http://intersult.com/xplate/html"> <p> <h:text value="Included Tag"/> </p> </meta:tag>
Hinweis: der Tag <meta:tag> stellt zum einen die Möglichkeit zur Verfügung, Namespaces anzugeben (xmlns:...), zum anderen sorgt er für ein einheitliches Interface.
Der Include-Tag kann auch Tags includen, die per EL-Expression referenziert werden. Auf diese Weise können HTML-Snippets als Parameter an Composite-Tags übergeben werden:
<flow:for id="row" value="#{container.elements}" var="element"> <p> <meta:include tag="#{element}"/> </p> </flow:for>
Der For-Tag iteriert die Child-Elemente des Container-Tags, sodass die Variable "element" jeweils ein Element enthält. Der Include-Tag fügt jeden Tag aus der Iteration ein. Der Aufruf kann dann so aussehen:
<app:table> <h:text value="Container Tag 1"/> <h:text value="Container Tag 2"/> <h:text value="Container Tag 3"/> </app:table>
Möchte man nur eine Tag-Liste einfügen, geht dies auch mit dem Attribut "tags":
<meta:include tags="#{container.elements}"/>
<h:text> <meta:attribute name="value" value="Attribute value"/> </h:text>
Hinweis: Attribut Tags sind generisch, das heißt sie können ohne extra Code oder Aufwand überall verwendet werden.
Hier ein Beispiel für die Übergabe von Request-Parametern bei einer Navigation:
<h:submit value="Test" action="#{testBean.action}"> <flow:navigate viewId="/other.xhtml"> <meta:param name="test" value="test param"/> </flow:navigate> </h:submit>
Dies ist gleichbedeutend mit dem Java-Code:
Navigation navigation = XplateContext.instance().getNavigation(); navigation.getParams().put("test", "test param"); navigation.setViewId("/other.xhtml");
Mit <meta:param> können auch Tags (also im Endeffekt ein XHTML-Snippet) übergeben werden. Dies ist nützlich, wenn man einen Composite-Tag (oder native) erstellt und bestimmte Bereiche für den Container flexibel halten möchte:
<app:table id="table"> <meta:param name="someTags"> <h:text value="Tag Param 1"/> <h:text value="Tag Param 2"/> <h:text value="Tag Param 3"/> </meta:param> <h:text value="Container Tag 1"/> <h:text value="Container Tag 2"/> <h:text value="Container Tag 3"/> </app:table>
Die Implementierung sieht dann so aus:
<?xml version="1.0" encoding="UTF-8"?> <meta:tag xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" xmlns:flow="http://intersult.com/xplate/flow" xmlns:h="http://intersult.com/xplate/html"> <table> <tr> <flow:for id="header" value="#{container.paramMap.someTags.elements}" var="element"> <th> <meta:include tag="#{element}"/> </th> </flow:for> </tr> <tr> <flow:for id="row" value="#{container.elements}" var="element"> <td> <meta:include tag="#{element}"/> </td> </flow:for> </tr> </table> </meta:tag>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="X-UA-Compatible" content="IE=9; IE=8"/> <title>test</title> </head> <body> <h1>Hello World</h1> </body> </html>
Darüber hinaus bietet Xplate für bestimmte HTML-Elemente eine erweiterte Unterstützung an. Diese sind unter dem Namespace http://intersult.com/xplate/html mit dem Prefix h verfügbar.
<h:text value="#{testBean.text}"/>
Hinweis: Wenn XML-Text inline geschrieben wird, entsteht intern ein Tag vom Typ <h:text>.
<h:form id="form"> ... </h:form>
<h:form id="form"> <h:submit id="test" value="Test" action="#{testBean.action}"/> </h:form>
Erklärung: Das Attribut action kann eine Method-Expression enthalten, die beim Abschicken des Formulars durch den Knopf aufgerufen wird.
<flow:if test="#{true}"> <h1>Hello World!</h1> </flow:if>
<html> ... <flow:for id="loop" value="#{testBean.list}" var="element"> <h:text value="#{tag.clientId} -> #{element}"/> </flow:for> ... </html>
Ergebnis:
0:1:loop:0:0 -> eins 0:1:loop:1:0 -> zwei 0:1:loop:2:0 -> drei
Der Tag <flow:for> unterstützt auch das Attribut "test", mit dem die iterierten Elemente gefiltert werden. Im Gegensatz zu einem verschachtelten <flow:if> wird für die ausgefilterten Elemente kein Index erzeugt. Je nach Situation kann das eine oder andere von Vorteil sein.
<h:submit value="Test" action="#{testBean.action(testBean.text)}"> <meta:set value="#{testBean.text}" target="#{testBean.target}"/> <flow:navigate viewId="/other.xhtml"/> </h:submit>
Dem Navigation Tag können auch Parameter mit dem Tag <meta:param> übergeben werden:
<flow:navigate viewId="/other.xhtml"> <meta:param name="test" value="test param"/> </flow:navigate>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" xmlns:flow="http://intersult.com/xplate/flow" xmlns:h="http://intersult.com/xplate/html" xmlns:app="http://intersult.com/xplate/tag/app"> ... <app:composite/> ... </html>
Der Composite Tag befindet sich dann unter /tag/app/composite.xhtml:
<?xml version="1.0" encoding="UTF-8"?> <meta:tag xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" xmlns:flow="http://intersult.com/xplate/flow" xmlns:h="http://intersult.com/xplate/html"> <p> <h:text value="Composite Tag"/> </p> </meta:tag>
<?xml version="1.0" encoding="UTF-8"?> <meta:tag xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" xmlns:flow="http://intersult.com/xplate/flow" xmlns:h="http://intersult.com/xplate/html"> <meta:param name="message" value="No message specified"/> <h:text value="#{message}/> </meta:tag>
Der Tag wird dann so aufgerufen:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:meta="http://intersult.com/xplate/meta" xmlns:flow="http://intersult.com/xplate/flow" xmlns:h="http://intersult.com/xplate/html" xmlns:app="http://intersult.com/xplate/tag/app"> ... <app:composite message="Hello tag!"/>
Hinweis: Da auf den Container mit #{container} zugegriffen werden kann, könnten die Attributwerte auch mit #{container.attributes.<name>.evaluated} erreicht werden. Definitionen mit <meta:param> sind null, wenn der Container das Attribut nicht angibt und erlauben einen Default-Wert. Des Weiteren beschreiben die Param-Angaben das Interface des Composite-Tags.
@XmlNamespace(value = "http://intersult.com/xplate/test", localPart = "hello", prefix = "test") public class TextTag extends Tag { public TextTag(TagConfig config) { super(config); } public void renderBegin() { XplateContext.instance().getWriter().writeText("Hello World!"); } }
Hinweis: Ein Native-Tag braucht nirgends registriert zu werden. Es existiert keine Tag-Lib, die Annotation ist völlig ausreichend, damit Xplate den Tag findet. Der Namespace http://intersult.com/xplate/test legt den Xml-Namespace fest, unter welchem der Tag später in die XHTML-Datei eingebunden wird. Ein Namespace kann dabei viele Tags enthalten, sodass Tags gruppiert werden können und die Page übersichtlich bleibt. Der Wert "prefix" gibt an, unter welchen Prefix der Tag später eingefügt wird und localPart den eigentlichen Tag-Namen.
Der Tag wird dann so benutzt:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:test="http://intersult.com/xplate/test"> ... <test:hello/> ... </html>
Erklärung: Im äußeren Tag <html> der Seite wird der Namespace http://intersult.com/xplate/test unter dem Präfix "test" eingebunden. Der Tag <test:hello> steht nun auf der Seite zur Verfügung, sowie andere Tags die für den gleichen Namespace vorhanden sind.
Die Navigation kann direkt durch im Java-Code einer aufgerufenen Action-Methode genutzt werden:
public void action(String message) { Navigation navigation = XplateContext.instance().getNavigation(); navigation.getParams().put("message", message); navigation.setViewId("/other.xhtml"); }
Hinweis: Die Navigation wird meist bequemer durch den Navigation-Tag durchgeführt.
Variable | Beschreibung |
---|---|
this | Der aktuelle Tag, in dem sich die EL-Expression befindet |
context | Der XplateContext, ein Short-Cut für context.tagContext.tagFrame.tag |
composite | Der aktuelle Composite- oder Include Tag. Tatsächlich wird einfach der oberste Tag der aktuellen Page referenziert. Es ist ein Short-Cut für context.tagContext.compositeTagFrame.tag |
container | Der aktuelle Composite- oder Include-Parent Tag. Es ist ein Short-Cut für context.tagContext.containerTagFrame.tag |