Konzept
Diese Seite ist ein Teil der IsyFact-Standards. Alle Inhalte der Seite, insbesondere Texte und Grafiken, sind urheberrechtlich geschützt. Alle urheberrechtlichen Nutzungs- und Verwertungsrechte liegen beim Bundesverwaltungsamt.
Die Nutzung ist unter den Lizenzbedingungen der Creative Commons Namensnennung 4.0 International gestattet.
Java Bibliothek / IT-System
Name | Art | Version |
---|---|---|
|
Bibliothek |
6.2 |
1. Einleitung
Im Dokument Softwaretechnische Architektur ist der Aufbau von IT-Systemen in fünf Komponenten beschrieben. Eine davon ist die Komponente GUI (s. Softwaretechnische Architektur eines IT-Systems (GUI hervorgehoben)).
Dieses Konzept baut auf dem Detailkonzept Web-GUI auf und beschreibt die Architektur der GUI-Komponente, umgesetzt mit den Technologien JSF und Spring Web Flow. Es enthält darüber hinaus architektonische und technische Vorgaben, die bei der Umsetzung der GUI-Komponente zu beachten sind. Es streift Aspekte der verwendeten Technologien, wo nötig, setzt in der Breite aber eine gewisse Vorkenntnis voraus.
Ergänzend wird im Dokument Nutzungsvorgaben JSF die Umsetzung der GUI-Komponente mit dem JSF Frontend-Framework beschrieben. Dort finden sich konkrete, technische Vorgaben, die bei der Umsetzung der GUI-Komponente zu beachten sind.
Zielgruppe dieses Dokuments sind Architekten, die sich einen Überblick über den Baustein JSF verschaffen und wissen möchten, welche Architekturvorgaben und Architekturentscheidungen der Baustein mit sich bringt.
Das Dokument ist in zwei Teile gegliedert. Nach einer kurzen Einführung in die verwendeten Technologien (JSF und Spring Web Flow) beschäftigt sich das Konzept mit der Architektur einer JSF-GUI. Dabei erläutert es zunächst den Aufbau der GUI-Komponente sowie die Kommunikation zwischen ihren Bestandteilen. Danach stellt es die Vorgaben an die Verwendung von JSF und JavaScript vor. Schließlich enthält es noch ein Kapitel zum Thema Session Behandlung.
Im Anhang steht eine Checkliste, die als Ausgangspunkt für die Qualitätssicherung der nach diesem Konzept umgesetzten GUIs dienen soll.
Allgemeine Angaben zur Gestaltung von Oberflächen finden sich im Bedienkonzept.
1.1. Zielsetzung des Bausteins JSF
Einfachheit der Verwendung von JSF: Hier werden die Konfiguration und der Einsatz der verwendeten Technologien festgelegt. Dazu zählen:
-
Einsatz von Facelets
-
Einsatz von Tag Libraries
-
Fehlerbehandlung
-
Anbindung von Hilfesystemen
Einfachheit des Einsatzes von Spring Web Flow: Die Definition der Dialogschritte und die Abhängigkeiten zu den zugehörigen Daten für die Darstellung wird über Spring Web Flow festgelegt. Hierzu werden folgende Themenbereiche genauer definiert:
-
Konfiguration der Dialog Abläufe (Flow)
-
Anbindung der Backend-Services (Spring Beans)
Einfachheit des Session-Managements: Hier wird definiert, wie die Behandlung von Session Informationen erfolgen soll und welche wiederverwendbaren Services zur Verfügung gestellt werden.
1.2. Sicherheitsanforderungen
Web-Anwendungen sind besonderen Gefährdungen ausgesetzt. Folgende Anforderungen müssen bei der Entwicklung von Web-Anwendungen berücksichtigt werden:
-
Entwickler müssen sich mit den TOP10 Risiken für Web-Anwendungen gemäß OWASP vertraut machen (siehe OWASP Top 10)
-
Vertrauliche Informationen dürfen nicht als GET-Parameter übermittelt werden. Dies verhindert, dass solche Informationen ungewollt in Log-Dateien, Caches usw. gespeichert werden.
2. Einführung in die Basistechnologien
Die Übersicht der Basistechnologien soll dem Leser einen einfacheren Einstieg in die angewendeten Frameworks und Technologien ermöglichen. Zusätzlich findet sich noch ein Verweis auf die konkret eingesetzten Implementierungen. Hierbei werden auch Architekturprinzipien angesprochen, welche in den Technologien Verwendung finden.
Für detaillierte Informationen über die verwendeten Technologien sei auf entsprechende Literatur verwiesen: JavaServer Faces, Spring Web Flow und Spring.
2.1. JSF / Facelets
Java Server Faces (JSF) ist ein Framework für die Entwicklung von Webanwendungen. Es basiert auf einer MVC-Architektur. Der Schwerpunkt von JSF ist die Bereitstellung grafischer Komponenten wie z.B. Tabellen, Formulare, Kalender, Menüs oder Editoren für die Entwicklung von Webanwendungen. Das zentrale Konzept hinter JSF ist die Erstellung eines Komponentenbaumes aus diesen grafischen Komponenten. Aus diesem Komponentenbaum wird schließlich die auszuliefernde HTML-Seite generiert.
Die IsyFact setzt die Referenzimplementierung von Oracle (JavaServer Faces) ein und erweitert diese im Bereich der Komponenten um die Bibliothek Apache Tomahawk.
Facelets ist eine Template-Engine und die Standard-View-Technologie von JSF. Facelets löst in dieser Funktion JSP ab. Mit Facelets werden kleine, wiederverwendbare GUI-Komponenten erstellt, die durch ein entsprechendes XML-Tag in eine Seite inkludiert werden. Hierbei wird der Templating-Mechanismus der Facelets verwendet. Die Ablage erfolgt in XHTML-Dokumenten. Im Gegensatz zu Taglibs erfolgt ein Include der Komponente und kein Aufruf von Java-Code für die Generierung von gerendertem GUI-Code.
Facelets bieten vielfältige Möglichkeiten, Vorlagenfragmente zu einer Gesamtseite zusammenzusetzen, um z.B. auf jeder Seite einen einheitlichen Seitenrahmen zu realisieren. Weiter können Facelets mit herkömmlichen HTML-Editoren bearbeitet werden und sind somit einfacher zu verstehen und zu editieren. Als Implementierung kommt wiederum die Referenzimplementierung von Oracle (JavaServer Faces) zum Einsatz.
2.1.1. Bearbeitungsmodell einer JSF Anfrage
Die Spezifikation der Java Server Faces definiert einen sogenannten Lebenszyklus JavaServer Faces, den eine JSF-Anwendung mit jedem Aufruf erneut durchläuft. Dieser Lebenszyklus ist in sechs Phasen (englisch Phases) aufgeteilt.
-
Restore View („Sicht wiederherstellen“) wählt anhand der eingehenden Anforderung eine Sicht (View) aus und baut den dazu passenden Komponentenbaum bei Bedarf auf.
-
Apply Request Values („Anforderungsparameter anwenden“) extrahiert Parameter aus der Anforderung (üblicherweise ein HTTP-Post-Request) und weist sie den passenden JSF-Komponenten zu, beispielsweise Eingabefeldern.
-
Process Validations („Validierung ausführen“) überprüft die Gültigkeit der zuvor ermittelten Eingaben. Dazu werden eigene Validator-Objekte verwendet, die den Komponenten in der View-Definition zugewiesen wurden.
-
Update Model Values („Modell aktualisieren“) weist den Modellobjekten die zuvor ermittelten Werte zu.
-
Invoke Application („Anwendung aufrufen“) ruft durch die Anwendung definierte Methoden auf, beispielsweise wenn ein Button betätigt wurde.
-
Render Response („Antwort wiedergeben“) erzeugt schließlich die Antwort auf die ursprüngliche Anfrage, beispielsweise eine HTML-Seite. Hierzu werden sogenannte Renderer aufgerufen, die den View-Komponenten zugeordnet sind.
Treten Fehler auf oder soll als Antwort beispielsweise eine HTML-Seite aufgerufen werden, die keine JSF-Komponenten enthält, so können einzelne Phasen übersprungen werden.
2.1.2. Datenmodell
Die Daten für die Visualisierung in JSF werden in Model Beans gehalten. Hierfür wird nicht auf den durch JSF zur Verfügung gestellten Mechanismus der Managed Beans zurückgegriffen. Das Datenmodell wird über Spring Web Flow direkt aus Model Beans verfügbar gemacht. Damit ist die Verwaltung des Models unter Kontrolle des Dialogflusses, der über Spring Web Flow gesteuert wird. Die einem Flow zugeordneten Model Beans werden durch den Flow instanziiert und unterliegen somit dem Flow-Lebenszyklus.
2.1.3. Facelets
Mit den Facelets werden das visuelle Layout und die Controls für die Ansicht im Browser definiert. Durch die Nähe zu HTML sind schnell die notwendigen Ansichten designt und können getestet werden. Das Mapping der Controls auf die Daten geschieht über die in JSF verwendete Expression Language (EL). Mit der EL werden direkt die Attribute des zugehörigen Beans genutzt.
Ein weiterer Vorteil von Facelets ist die Verwendung von Templates. Durch diese ist es möglich, bereits einen zum Bedienkonzept konformen Rahmen zur Verfügung zu stellen, in welchen die Applikation lediglich durch definierte Einfügungen ihre Inhalte einbetten.
2.1.4. Taglibs
Durch den Einsatz von Facelets ist die direkte Einbettung von Tag Libraries nicht möglich. Vielmehr müssen diese noch separat mit einer Deskription versehen werden, aus welcher die einzelnen Tags ersichtlich sind und ihr Mapping auf die zugehörigen Klassen definiert ist.
Für den Einsatz der myFaces Tomahawk Library wird eine entsprechende Konfiguration zur Verfügung gestellt. Der Einsatz dieser Library unterliegt für den Einsatz in der IsyFact der allgemeinen Einschränkung bei der Verwendung von JavaScript (siehe auch Benötigte Bibliotheken in den Nutzungsvorgaben).
2.2. Spring Web Flow
Spring Web Flow ist ein Framework für die Ablaufsteuerung von Anwendungsfällen innerhalb von Web-Anwendungen. Ein solcher „Flow“ innerhalb von Spring Web Flow ist eine Abfolge zusammenhängender Masken, wie z.B. das Durchlaufen der Schritte zur Registrierung eines neuen Benutzers in einer Web-Anwendung.
Ein erklärtes Ziel von Spring Web Flow ist die Unterstützung der Browser-Navigation, die in der Web-Entwicklung immer wieder zu Problemen führt. Das Framework übernimmt dabei die Navigation zwischen den einzelnen Views und stellt darüber hinaus einen eigenen Scope Container für Model Beans zur Verfügung. Dieser erweitert die Web-Anwendung um die folgenden Scopes:
-
Flash: Gültig, solange der Flow aktiv ist, jedoch werden Flash Scope Beans nach jedem View-State geleert und dienen somit dazu, Daten zwischen zwei User Events zu transferieren.
-
Flow: Steht über die gesamte Laufzeit des Flows zur Verfügung.
-
Conversation: Die Lebensdauer ist mit dem Flow Scope identisch, nur stehen Conversation Scope Beans auch in den zugehörigen Subflows zur Verfügung.
Spring Web Flow implementiert im Kern einen finiten Zustandsautomaten, der auf definierte Anfangs- und Endzustände angewiesen ist.
Der Ablauf der logisch zusammenhängenden Views wird in einem sogenannten Flow definiert. Innerhalb eines Flows stehen verschiedene States zur Verfügung. Zunächst muss jeder Flow einen Start State und einen End State besitzen. Der Start State definiert den Einstiegspunkt und aktiviert den jeweiligen Flow. Dieser bleibt so lange aktiv, bis ein End State erreicht wird.
Wie bereits erwähnt, setzt sich Spring Web Flow das Ziel, die Browser-Navigation mit „Back-/Forward-Button“ zu unterstützen. Um diese Funktionalität zu gewährleisten, muss die Möglichkeit bestehen, den Zustand einer View zu speichern und wieder abzurufen. Hierfür steht ein sogenanntes Repository zur Verfügung, welches die Zustände der einzelnen Views innerhalb eines Flows zwischenspeichert. Dadurch kann man den Zustand jeder View innerhalb eines Flows zu einem beliebigen Zeitpunkt reproduzieren.
2.2.1. Flows / Subflows
Ein Flow kann wahlweise als XML-Datei oder mittels der Java-API realisiert werden. Ein Flow besteht in der Regel aus mehreren Zuständen (innerhalb von Web Flow als States bezeichnet), die nacheinander und in Abhängigkeit von der jeweiligen Benutzerinteraktion durchlaufen werden.
Auch die Modularisierung von Flows in kleine Einheiten ist durch sogenannte Subflows bzw. Inline-Flows ohne weiteres möglich. Ein Subflow wird wie jede andere Flow-Definition erstellt. Der Unterschied zu einem normalen Flow liegt lediglich darin, dass der Subflow innerhalb eines Flows aufgerufen wird. Eine Flow-Definition kann beliebig viele Subflows enthalten, welche wiederum weitere Subflows aufrufen können.
2.2.2. Back-Button Handling
Während der Ausführung von Flows werden die Variablen mit einer Zwischenspeicherung in das Repository geschrieben. Hierbei wird immer, wenn ein Flow durch eine User-Interaktion unterbrochen wird, der aktuelle Status gespeichert. Das Repository liefert diesen bei der Fortsetzung des Flows zurück. Der dafür notwendige „Flow-Execution-Key“, der Schlüssel, der zur Identifikation des aktuellen Flow-Status dient, wird hierbei von Spring Web Flow erzeugt.
Dieses Speichern des Status von vorhergehenden Schritten im Flow unterstützt so in Kombination mit einem „Post Redirect Get“ Mechanismus (PRG-Pattern) die Nutzung des Back-Buttons im Browser. Da jeder Request im Flow einen eindeutigen Execution Key an den Server sendet, kann, wenn man im Flow zurückgeht, auch der alte Status zum Zeitpunkt dieses Request angezeigt werden, selbst wenn der Server von dem Click auf den Back-Button selbst nichts mitbekommt.
2.3. Transaktionsbehandlung
Die GUI-Komponente und der Anwendungskern sind Teil derselben Web-Applikation und werden per Spring-Konfiguration miteinander verbunden.
Oft gibt es den Fall, dass über die GUI eine Aktion in einer anderen Anwendung ausgelöst werden soll. Ein Beispiel dafür ist die GUI einer Geschäftsanwendung zur Datenerfassung, wobei die Speicherung der Daten über einen Service einer anderen Fachanwendung implementiert ist. In diesem Fall enthält der Anwendungskern der Fachanwendung zur Datenerfassung nur wenig Funktionalität: in ihm werden die Daten für den Serviceaufruf der nachgelagerten Fachanwendung aufbereitet und der Serviceaufruf selbst durchgeführt. Wichtig in diesem Fall ist, dass es nach Zielarchitektur keine Transaktionen über Serviceaufrufe hinweg gibt.
In diesem Abschnitt wird die Behandlung von Transaktionen innerhalb einer Anwendung beschrieben. Grundregel dabei ist, dass der Anwendungskern die Transaktionssteuerung übernimmt. Aus Sicht der GUI-Komponente bedeutet dies, dass jeder Aufruf des Anwendungskerns unmittelbar eine Änderung der Daten zufolge hat. Dabei muss die GUI-Komponente die Brücke schlagen zwischen der fachlichen Transaktion, die dem Nutzer dargestellt wird und der technischen Transaktion, die in der Datenbank abgebildet wird.
Die fachliche Transaktion entspricht einem Dialogablauf. Ein Beispiel dafür: Der Nutzer kann in der Regel über mehrere Masken hinweg Daten eingeben. Abschließend drückt er in einem Dialog den „OK“- bzw. den „Abbrechen“-Button. Für den Nutzer ist klar, dass alle die von ihm eingegebenen Daten im Sinne einer Transaktion behandelt werden müssen, d.h. sie werden entweder vom System komplett übernommen oder komplett verworfen.
Aus technischer Sicht ist die Behandlung dieses Ablaufs etwas komplizierter: Die Daten, die der Nutzer in den verschiedenen Dialogen eingibt, müssen zunächst zwischengespeichert werden, bevor dann bei Betätigung eines Buttons die technische Transaktion in der Datenbank erfolgt. Das Zwischenspeichern der Werte benötigt allerdings ebenfalls technische Transaktionen. Da der Prozess der Web-Anwendung zustandslos ist, muss das Zwischenspeichern ebenfalls in der Datenbank erfolgen. Hier muss die GUI zusätzliche Transaktionen durchführen.
Bei der Spring Web Flow Integration wurde ein Mechanismus verwendet, um die Zwischenwerte und Informationen zum Dialogablauf in der Datenbank abzulegen. Das Zwischenspeichern erfolgt grundsätzlich in einer separaten Transaktion. Somit beeinflussen sich die fachliche Transaktion und die technischen Transaktionen nicht.
Mit den technischen Transaktionen ist es jetzt möglich, „Sitzungen“ abzubilden. Eine Sitzung ist letztendlich die Summe aller Zwischendaten, die der Nutzer eingegeben hat oder die das System selbst erzeugt hat (z.B. interne Zustände, Nutzerinformationen, …). Innerhalb einer Sitzung werden mehrere fachliche Transaktionen durchgeführt.
Das technische Mittel zur Repräsentation einer Sitzung ist zunächst einmal die Session des Servers. Diese Session ist transient. Da der Serverprozess zustandslos ist, muss sie in der Datenbank persistiert werden. Dazu gibt es zwei Alternativen:
-
Serialisierung der Session nach Beendigung des Request und Wiederherstellung bei neuerlichem Aufruf
-
Speichern des Spring Web Flow State an den durch Spring Web Flow vorgesehenen Hooks
Die Variante der Session Serialisierung ist zwar einfacher, beinhaltet aber auch eine wesentliche Gefahr. Die Session des Servers wird zum Speichern von verschiedensten Daten genutzt, der Zugriff auf sie ist frei möglich. Dies führt in der Praxis dazu, dass unkontrolliert große Datenmengen in der Session abgelegt werden. Diese großen Datenmengen lassen sich dann nicht mehr effizient persistieren. Daher wurde diese Option in der Referenzarchitektur ausgeschlossen. Die Details dazu, wie in Spring Web Flow die zu speichernden Daten einer Session ermittelt werden, finden sich in Kapitel Session Behandlung.
2.4. JQuery
JQuery ist ein JavaScript-Framework, das auf einfache Weise JavaScript-Funktionen bereitstellt, die insbesondere für die grafische Gestaltung einer Oberfläche benötigt werden. Erklärtes Ziel ist es, die Oberfläche durch den Einsatz von JavaScript eleganter nutzbar zu machen. Besonderer Fokus liegt dabei auf den Sicherheitsaspekten, die eine Aktivierung von JavaScript mit sich bringt. Die Oberfläche muss jedoch auch mit deaktiviertem JavaScript mit Komforteinschränkungen nutzbar sein.
3. Architektur einer JSF-GUI
Die Architektur von GUIs mit JSF und Spring Web Flow hält sich an die Vorgaben aus der Referenzarchitektur sowie die architektonischen Vorgaben aus dem Detailkonzept Web-GUI. Sie besitzt, aufgrund der Vorgaben und der eingesetzten Technologien, folgende Eigenschaften:
-
Nutzung des MVC-Patterns
-
Trennung des Dialogs in Dialogsteuerung und Präsentation
-
Dialogsteuerung über Spring Web Flow
-
Bildung von gekapselten GUI-Komponenten über Facelets
-
Präsentation über JSF und Facelets
-
Verwaltung von Sessions mit Spring Web Flow
-
Interaktive Oberflächenelemente mit JQuery
Aufbau einer Web-GUI mit JSF und Spring Web Flow zeigt den Aufbau einer Web-GUI mit JSF und Spring Web Flow und verdeutlicht die Integration der Technologien in die Architektur.
Im Rahmen der Anwendungsentwicklung sind die gelb hervorgehobenen Teile bereitzustellen:
-
Definition der Dialogabläufe als Flow
-
Model und Controller-Beans zur Bereitstellung der UI-Models und der UI-Logik
-
Facelets zur Visualisierung der Masken
Die grau hinterlegten Teile werden durch die IsyFact konfiguriert und bereitgestellt.
3.1. Aufbau der GUI-Komponente
Wie im Detailkonzept Web-GUI beschrieben, besteht die GUI-Komponente aus Dialog-Komponenten und einem gemeinsamen AWK-Wrapper. Der Schnitt der Komponenten ist fachlich motiviert und in der Systemspezifikation beschrieben.
Die Dialog-Komponenten einer GUI-Komponente können einen gemeinsamen AWK-Wrapper und in ihren Modellen gemeinsame Klassen verwenden. Trotzdem sind die Dialog-Komponenten zu kapseln, d.h. Controller und UI-Models dürfen nicht gemeinsam verwendet werden (siehe Innensicht einer GUI-Komponente mit ihren Dialog-Komponenten).
Zentral ist also die Forderung, dass die Elemente jeder Dialog-Komponente (Flow, Controller, Model und View) in definierter Weise ausschließlich untereinander kommunizieren und Zugriffe auf Elemente anderer Dialog-Komponenten unterbleiben. Kommunikation innerhalb einer Dialog-Komponente zeigt die Kommunikation innerhalb einer Dialog-Komponente.
3.1.1. Flows
Jede GUI-Komponente wird durch einen Flow beschrieben. Dieser definiert das Zustandsmodell der Komponente und hat die Funktion des zentralen Controllers für diese Komponente. Er erfüllt die folgenden Aufgaben:
-
Erzeugung und Verwaltung eines (ggf. auch mehrerer) Model Beans
-
Definition des Flow-Ablaufs in Form eines Zustandsautomaten mit Zuständen und Zustandsübergängen (Flow, Subflows, Decision-States, Action-States, Event-Handlers)
-
Anbinden des Views
-
Steuerung der Verarbeitung im Rahmen von Zustandsübergängen
Der Flow-Aufbau wird so gestaltet, dass im Flow alle Zustände, Zustandsübergänge sowie Aufrufe von Verarbeitungslogik zentral gebündelt werden und Ablauf und Verhalten des Flows für den Entwickler klar nachvollziehbar sind.
Der Flow wird als XML-Datei im Ordner der Komponente hinterlegt.
3.1.2. Controller
Das Controller-Bean ist ein vom Komponenten-Flow aufzurufendes zustandsloses Spring Bean, welches Änderungen an den Daten des Models vornimmt oder diese aufbereitet bzw. Services des Anwendungskern-Wrappers aufruft. Das Model Bean wird dem Controller mit jedem Aufruf übergeben.
Die Implementierung des Controllers ist zustandslos und stellt nur Methoden bereit.
Das Controller-Bean wird im Spring IoC-Container mit Singleton Scope erzeugt und konfiguriert.
Das Controller-Bean wird vom Flow per Expression-Language aufgerufen. In bestimmten Fällen (siehe Abschnitt Views) wird ein Controller-Bean auch in einer Action (oder ActionListener) des Komponenten-View aufgerufen.
3.1.3. Models
Das Model Bean ist ein Datenobjekt (einfaches POJO) und hält die Daten einer GUI-Komponente. Es hat keine Abhängigkeiten zu View, Controller oder Anwendungskern und enthält im Regelfall keine Logik. Das Model Bean wird durch den Flow erzeugt (durch Definition einer Web-Flow-Variablen) und ist somit automatisch im View sichtbar.
<flow>
<!-- Erzeuge das Model zur Benutzung durch diesen Flow. -->
<var name="erstellenModel"
class="de.msg.terminfindung.gui.terminfindung.erstellen.ErstellenModel" />
</flow>
Der View liest die Daten zur Präsentation der Webseite aus dem Model Bean. Dies können Informationen zur Ansicht aber auch änderbare Formularinhalte sein. Werden Formularinhalte in Form eines Post-Requests auf den Server gesendet, so sorgt JSF eigenständig dafür, dass die Formularinhalte in das Model Bean rückübertragen werden.
Da das Model Bean durch den Flow erzeugt wird und Flow Scope besitzt, wird es automatisch mit in die Session-Persistierung einbezogen. Dazu muss das Model das Interface Serializable implementieren. Die Daten des Models werden bei den Dialogschritten eines Flows zwischen Client (Browser) und Server transparent für den Entwickler abgeglichen.
Das Model Bean ist nicht mit den JPA-Datenobjekten verbunden. Das Schreiben in das Model bewirkt also zunächst keine Änderung in der Datenbank. Die Persistenz fachlicher Datenobjekte wird über das Controller-Bean ausgelöst, welches über Methodenaufrufe des Anwendungskern-Wrappers fachliche Daten persistiert.
Der Abgleich von Model Beans mit einer View und das Abspeichern der Daten einer Model Bean wird im Kapitel "Erstellung der Models" in Nutzungsvorgaben JSF genauer beschrieben.
3.1.4. Views
Der Komponenten-View präsentiert die Daten der Anwendung in Form von generierten HTML-Seiten.
Dazu werden ein oder mehrere Facelets verwendet, die mittels JSF-HTML-Tags auf das Model Bean der Komponente
zugreifen, um die Daten in den View einzubinden.
Da das Model Bean seine Datenzugriffsmethoden nach dem Bean-Standard (get
/set
/is
) anbietet, kann mittels
Value-Expressions (z.B. #{teilnehmenModel.terminfindung.tage}
) direkt auf Eigenschaften des Model Beans und
enthaltener Objekte zugegriffen werden.
Ein View kann auch auf mehrere zum Flow gehörende Model Beans zugreifen.
Im View können Actions definiert sein (z.B. Submit durch einen Command-Button). Dabei werden nur Action-Tokens (String, der die Aktion benennt) übergeben, die dann im Flow entgegengenommen werden und dann Methodenaufrufe auf dem Controller auslösen.
Aus einer Aktion des Views sollte i.d.R. immer ein Zustandstoken zur Steuerung von Transitionen im Flow erzeugt werden.
Dies ist vor allem bei Maskenübergängen und fachlichen Aktionen zu verwenden.
Beispiel: Suche in einem Formular, Öffnen der Detailansicht.
Aktionen, welche zur Steuerung der Darstellung innerhalb einer Maske verwendet werden, müssen nicht zwingend eine Transition auslösen.
In diesen Fällen darf der Controller direkt aufgerufen werden.
Beispiel: Selektion eines Elements und darauf basierende Anpassung der Maske.
Die View-Erstellung wird im Kapitel "Erstellung der Views" in Nutzungsvorgaben JSF genauer beschrieben.
3.1.5. Schnittstellen zwischen Dialog-Komponenten
Wie in Aufbau der GUI-Komponente beschrieben, müssen die Dialog-Komponenten voneinander gekapselt sein. Wenn zwei Dialog-Komponenten Informationen austauschen müssen, darf dies nicht über ein gemeinsam genutztes UI-Model geschehen. Der Austausch von Informationen erfolgt stattdessen über Input/Output-Elemente im Flow, die aus dem Model einer Dialog-Komponente gelesen oder geschrieben werden.
Ist ein Subflow B mit Daten aus dem aufrufenden Flow A zu versorgen, so bekommt dieser nicht das Model A, sondern eine Kopie eines einzelnen Objekts aus Model A übergeben. Dies kann auch eine Datenstruktur, darf aber niemals das gesamte Model A sein. Es ist wichtig, dass eine Kopie übergeben wird, damit Flow B nicht Teile des Models A absichtlich oder versehentlich ändert.
Besteht Bedarf, dass ein Subflow B an den aufrufenden Flow A Daten zurückgibt, so erfolgt dies über ein Output-Element. Hier gilt analog, dass nicht das gesamte Model B, sondern lediglich Kopien eines Teils des Models übergeben werden.
Das folgende Beispiel zeigt wie ein Flow an einen Subflow Parameter übergibt und von diesem einen Ausgabewert empfängt.
<subflow-state id="loeschenViewState" subflow="loeschenFlow">
<input name="terminfindung"
value="verwaltenController.kopiereTerminfindungModel()"/>
<output name="loeschenTerminfindung"/>
<transition on="finished" to="verwaltenViewState">
<evaluate expression="verwaltenModel.setTerminfindung(loeschenTerminfindung)"/>
</transition>
</subflow-state>
Innerhalb des Subflows werden übergebene Parameter entgegengenommen und verarbeitet. Im Endzustand wird ein Rückgabewert zurückgegeben.
<flow>
<input name="terminfindung" type="de.msg.terminfindung.gui.terminfindung.model.TerminfindungModel"/>
<on-start>
<evaluate expression="loeschenModel.setTerminfindung(terminfindung)"/>
</on-start>
<view-state id="loeschenViewState">
<on-entry>
<evaluate expression="loeschenController.setzeAuswahlZurueck(loeschenModel)"/>
</on-entry>
<transition on="cancel" to="finished"/>
<transition on="delete" to="loeschenViewState">
<evaluate expression="loeschenController.loescheZeitraeume(loeschenModel)"/>
</transition>
</view-state>
<end-state id="finished">
<output name="loeschenTerminfindung"
value="loeschenModel.getTerminfindung()"/>
</end-state>
</flow>
Zur Datenübergabe können auch mehrere Input- und mehrere Output-Elemente verwendet werden.
3.1.6. Zugriff auf querschnittliche Controller
Für die Steuerung des Vorgabelayouts (z.B. Menüleiste, Linksnavigation, Hilfezugriff) sowie der Nutzung von vorgegebenen Funktionen (z.B. Validierung) werden querschnittliche Controller mit zugehörigen Models verwendet. Die Instanziierung übernimmt dabei ein übergeordneter Parent-Flow. So kann z.B. die Seitentoolbar konfiguriert oder ein Quicklink hinzugefügt werden.
Der Aufruf dieser Controller ist generell erlaubt. Die Controller sind als Spring Beans global verfügbar. Welche Controller im Detail (LinksnavigationController, QuicklinksController, ValidierungsController, …) für das Vorgabelayout verfügbar sind, wird im Dokument Nutzungsvorgaben JSF beschrieben.
3.2. Vorgaben zur Verwendung von JSF
Die folgenden Abschnitte beschreiben allgemeine Vorgaben bei der Verwendung JSF-eigener Funktionalität.
3.2.1. Verwendung von JSF-Widgets
Für die Arbeit mit JSF werden gemäß dem Bedienkonzept sowohl Seitenelemente als auch Bedienelemente bereitgestellt. Seitenelemente helfen, den Seitenrahmen aufzubauen und erleichtern so einen zum Bedienkonzept konformen Aufbau der Masken. Bedienelemente helfen, die Masken selbst zu strukturieren und bieten vorgefertigte Widgets zur Anzeige und Eingabe von Daten. Daneben gibt es noch einige spezielle Komponenten, z.B. um den Fokus beim Laden der Maske zu setzen oder eine Druckansicht bereitzustellen.
Die Bedienelemente sind alle als JSF Composite Components realisiert. Dadurch ist eine einfachere Wartung möglich, da die Komponenten vollständig in XHTML definiert sind und ein Grundverständnis von JSF genügt, um Anpassungen vorzunehmen. Spezielle Renderer oder Java-Klassen werden nicht benötigt.
Die Nutzung der durch das Bedienkonzept definierten Elemente und durch den Baustein bereitgestellten JSF-Komponenten ist ein zentrales Thema der Nutzungsvorgaben JSF.
3.2.2. Absicherung von JSF-GUIs
Die Absicherung von Masken erfolgt auf Ebene des Dialogablaufs über Spring Web Flow. Die Berechtigungsprüfung verwendet dabei den Baustein Security.
Für die Anbindung von Spring Web Flow an den Baustein Security wird das Spring Security-Interface AccessDecisionManager
durch die Klasse WebDelegatingAccessDecisionManager
implementiert.
In der Implementierung wird mittels einer Abfrage an Berechtigungsmanager
aus dem Security-Baustein geprüft, ob ein Anwender die notwendigen Berechtigungen besitzt (siehe dazu Nutzungsvorgaben Security).
3.2.3. Verwendung des CSRF-Schutzes
Bei einem Cross-Site Request Forgery (CSRF) Angriff versuchen die Angreifer einem angemeldeten Benutzer einen HTTP-basierten Request unterzuschieben. Mithilfe der Session des Benutzers kann der Angreifer so Änderungen am System ausführen.
Schutzbedarf | CSRF-Schutz | Beschreibung |
---|---|---|
normal und hoch |
Token per Session |
Am Anfang der Benutzer-Session wird ein Token erzeugt und in der Session gespeichert. Danach wird bei jedem Request das Token mitgeschickt und mit dem in der Session gespeicherten Token abgeglichen. |
sehr hoch |
Token per Request |
Für jeden Request wird ein neues Token erstellt und versendet. Das erhöht nochmals die Sicherheit, vermindert aber gleichzeitig die Benutzerfreundlichkeit, da beispielsweise ein Rücksprung zu einem Formular verhindert wird. |
Weitere Details zur Konfiguration des CSRF-Schutzes stehen in Nutzungsvorgaben JSF.
3.2.4. Parameter mit Button/Link übergeben
JSF bietet mehrere Möglichkeiten, einen Parameter in Abhängigkeit eines geklickten Buttons oder Links an die Web-Anwendung zu übergeben. Dies ist beispielsweise dann notwendig, wenn auf einer Maske mehrere Elemente angezeigt werden, zu denen jeweils ein eigener Button zum Bearbeiten existiert. In diesem Fall muss es möglich sein zu erkennen, welcher Button zu welchem Element geklickt worden ist.
Die hierfür in JSF 2.x vorgesehenen Lösung mit f:param
erfordert den Einsatz von JavaScript und kann daher Probleme in der Abwärtskompatibilität hervorbringen (z.B. wenn kein JavaScript aktiviert ist).
Die Umsetzung sollte daher in der Regel mit einem Action Listener stattfinden (siehe Verwendung eines Action Listeners (View)):
<h:commandLink id="bearbeite_SV_#{sachverhalt.id}"
value="#{msg.MEL_Bearbeiten}"
actionListener="#{listener.waehleSachverhalt}">
<f:attribute name="sachverhaltId" value="#{sachverhalt.id}" />
...
</h:commandLink>
Dabei kann in der Methode waehleSachverhalt
der Wert des Attributs aus der RequestParameterMap
des FacesContext
gelesen werden (siehe Auswertung von Request Attributen).
FacesContext.getCurrentInstance().getExternalContext() .getRequestParameterMap().get("sachverhaltId");
Als Alternative zum Einsatz eines Action Listeners kann die ID des Buttons/Links parametrisiert und im
Controller ausgewertet werden.
Die Parametrisierung der ID wird ebenfalls in Verwendung eines Action Listeners (View) dargestellt.
Die Auswertung ist in diesem Fall aufwendiger, da alle Attribute der RequestParameterMap
durchlaufen werden müssen, bis ein Parameter gefunden wurde, dessen ID mit bearbeite_SV_
beginnt.
Vorteil der Lösung ist jedoch, dass Probleme mit dem Einsatz von Action Listenern damit umgangen werden.
3.2.5. Einsatz von Action Listenern
Aufgrund einer Eigenart von JSF in Zusammenhang mit dem Partial-State-Saving muss unbedingt darauf geachtet werden, dass die Komponente (Button/Link), an die der Action Listener gebunden ist, nicht durch den Klick ausgeblendet wird. Andernfalls führt dies zu Problemen mit dem Loadbalancing. Hintergrund ist, dass JSF durch das Partial-State-Saving den Zustand der Maske teilweise in der Session ablegt. Werden die Anfragen an die Web-Anwendung durch den Loadbalancer an verschiedene Server verteilt, kann dies daher dazu führen, dass JSF einen Fehler anzeigt, weil der Action Listener der ausgeblendeten Komponente nicht gefunden werden konnte.
3.2.6. Konvertierung von Daten zur Darstellung und Eingabe
JSF-Konverter dürfen sowohl zur Konvertierung aus dem Model zur View (Darstellung) als auch aus der View zum Model (Eingabe) verwendet werden. Standard-JSF-Konverter bieten sich jedoch nur bedingt an, da diese bei der Konvertierung „freier Eingaben“ nicht mit Fehleingaben umgehen können.
<h:inputText id="datum" value="#{erstellenModel.newDate}">
<f:convertDateTime type="date" />
</h:inputText>
Wenn die Validierung in einem Standard-JSF-Konverter stattfindet, werden die Daten in einem Fehlerfall nicht ins Modell geschrieben. Dies führt dazu, dass das Formular zurückgesetzt wird, weil die Seite wegen des Post/Redirect/Get-Patterns mit einem GET-Request mit dem alten Modell neu geladen wird. Die ungültigen Eingaben gehen also zusammen mit allen anderen Änderungen im Modell verloren. Standard-JSF-Konverter sind in diesen Fällen faktisch nicht nutzbar.
JSF-Konverter müssen, um geeignet zu sein, auch ungültige Daten ins Model schreiben können. Wenn dies aufgrund der Nutzung spezieller Datentypen (wie z.B. Datums- und Zeittypen) nicht möglich ist, muss im Model der Datentyp der Eingabe (in der Regel Zeichenketten) verwendet werden. Die Konvertierung findet in diesem Fall nicht durch einen Konverter statt, sondern erst während oder nach der Validierung an der Schnittstelle zum Anwendungskern.
3.2.7. Serverseitige Validierung von Eingaben
Die Validierung und Prüfung der in der GUI erfassten Daten soll entweder vollständig durch die GUI oder aber vollständig im Anwendungskern durchgeführt werden. Die Validierung in der GUI ist dabei bevorzugt. In diesem Falle wird der Validierungsmechanismus von Spring Web Flow verwendet (s. Abschnitt 5.10 in der Dokumentation des JavaServer Faces, Spring Web Flows). Standard-JSF-Validatoren oder -Konverter sollten für die Validierung aus den im Abschnitt Konvertierung von Daten zur Darstellung und Eingabe genannten Gründen nicht verwendet werden.
3.3. Vorgaben zur Verwendung von JavaScript
Bei der Verwendung von JavaScript sind grundsätzlich die folgenden Vorgaben einzuhalten:
eval()
darf nicht verwendet werden
Begründung: Die Verwendung von eval()
stellt ein Sicherheitsrisiko dar. Es besteht z.B. die Gefahr, dass Werte aus Request-Parametern ohne ausreichende Prüfung als Code ausgeführt werden.
Beispiel: Der Inhalt des Strings requestValue
wird ohne ausreichende Prüfung ausgeführt.
var requestValue = getParameterValue(“searchString“); eval(requestValue)
Dies ermöglicht es, jeden beliebigen JavaScript-Code per Injektion auf einem Client ausführen zu lassen:
www.mySite.de?searchString=alert(‚hallo‘)
Lösung: Anstatt Request-Parametern an die eval()
-Funktion zu übergeben, müssen diese über eine eigene Parser-Funktion ausgewertet werden.
Die Funktion muss sicherstellen, dass kein Angriff möglich ist.
setTimeout()
darf keine Funktion als Zeichenkette übergeben werden
Begründung: Die Verwendung einer Zeichenkette ermöglicht Manipulationen, falls Parameter ungeprüft übergeben und somit als Code ausgeführt werden.
Beispiel: Hier wird der Code unzulässigerweise als Zeichenfolge übergeben.
setTimeout(“callFunction(searchString)“, 100);
Ein Angreifer könnte nun eine URL wie folgt aufrufen:
www.mySite.de?searchString=5);alert(‚hallo‘
Der Inhalt der Variable searchString
wird ersetzt, sodass folgender Code ausgeführt wird:
setTimeout(“callFunction(5);alert(‚hallo‘)", 100);
Lösung: Anstatt den Code in einer Zeichenfolge zu übergeben, muss eine Funktion als Parameter übergeben werden:
setTimeout(function() { ... }, 100)
Hierdurch wird der Angriff abgewehrt.
Nutzung von anonymen Funktionen anstatt benannter Funktionen
Begründung: Definierte JavaScript-Funktionen sind in der Regel im globalen Variablen-Kontext gültig. Würde insbesondere für jede Callback-Funktion (z.B. beim Event Binding) eine eigene Funktion definiert, würde das den Speicher unnötig belasten. Weiterhin verschlechtert sich die Lesbarkeit. Gerade bei Callbacks ist es nützlich, wenn direkt ersichtlich ist, was passiert, wenn der Callback aufgerufen wird. Sofern keine Wiederverwendung möglich ist, ist daher von der Definition benannter Funktionen abzusehen.
Anonyme Funktionen können außerdem auf Variablen der umgebenden Funktion zugreifen, was die Implementierung vereinfacht:
var einWert = 5; setTimeout(function() { alert(4 + einWert); }, 100);
Benannte Funktionen sollten in Namespaces deklariert werden
Begründung: Wie bereits beschrieben, gelten Funktionen häufig im globalen Kontext. Funktionen können, wie Variablen, durch redundante Deklaration leicht überschrieben werden. Dann gilt immer die letzte Definition. Die Verwendung von z.B. des View-Names als „Namespace“ vermeidet, Funktionen aus einem anderen View versehentlich zu überschreiben:
var ns_<view> = { foo : function() { ... } }
Nutzung einer anonymen Funktion zur Kapselung
Begründung: Jede JavaScript-Datei beginnt mit einer function()
-Definition.
Mit diesem Konstrukt wird verhindert, dass die Definition neuer Funktionen und Variablen (versehentlich) Elemente aus dem globalen Kontext überschreiben.
Die Deklaration von wiederverwendbaren Funktion- bzw. Namespace-Definitionen muss außerhalb dieser function()
erfolgen.
Jede JavaScript-Datei beginnt demnach mit:
(function(){
und endet vor der Deklaration von wiederverwendbaren Funktionen bzw. Namespaces mit:
})()
Inline-JavaScript ist zu vermeiden
Begründung: Es gibt Fälle, in denen JavaScript „inline“ technisch bedingt direkt in der XHTML-View-Definition implementiert werden muss. Hier besteht die Gefahr, dass der JavaScript-Code schlecht strukturiert und auf zu viele Dateien verteilt wird. Zudem ist JavaScript-Code in XHTML-Dateien unerwartet und wird bei der Analyse der Anwendung schnell übersehen. Insgesamt wird hierdurch die Verständlichkeit und Wartbarkeit der Anwendung verschlechtert.
Eine Implikation dieser Regel ist, dass Event-Binding stets im Code selbst und nicht in den on<Event>
-Attributen der HTML-Elemente geschehen muss.
DOM-Zugriff mit der $-Funktion nur über IDs
Begründung: Der DOM-Zugriff mit der $-Funktion sollte stets über die ID oder Klasse eines DOM-Knotens erfolgen, nicht über die Knotenhierarchie des DOMs. Die Gefahr beim Negativ-Beispiel ist die mangelnde Robustheit bzgl. Änderungen der DOM-Struktur. Wird z.B. ein weiterer Knoten eingefügt, greift die Funktion ggf. nicht und die Anwendung arbeitet fehlerhaft.
Beispiel:
$(„#eineBildID“); // GUT $(„div span a img“); // SCHLECHT
Alle Parameter müssen korrekt encodiert und escaped werden
Begründung: Im JavaScript-Code dürfen Request- oder URL-Parameter nur nach ausreichendem Encodieren und Escapen verwendet werden. Gleiches gilt für den Einsatz von Server-Parametern bzw. Model-Attributen. Geschieht dies nicht, besteht das Sicherheitsrisiko von Ausführung von beliebigem Code (z.B. durch Request-Strings übergeben) auf dem Server.
Negativ-Beispiel: Wird beispielsweise ein Suchstring wie folgt in die Seite eingebunden:
<title>${searchString}</title>
dann könnte ein Angreifer folgende URL aufrufen:
www.mySite.de?searchString=<script>alert(‚halloWelt‘)</script>
Der übergebene JavaScript-Block würde dann auf dem Server ausgeführt. Das Escapen „zerstört“ die spitzen Klammern und Hochkommata, sodass kein Code ausgeführt wird.
3.3.1. Clientseitige Validierung von Eingaben
Clientseitige Validierung ist erlaubt und darf zur Verbesserung der Benutzbarkeit verwendet werden. Da JavaScript deaktivierbar und manipulierbar ist, müssen grundsätzlich alle clientseitigen Validierungen auf dem Server erneut erfolgen.
3.3.2. Vermeidung von Sicherheitslücken bei aktiviertem JavaScript
Ist JavaScript in einem Browser aktiviert, eröffnet dies gewisse Risiken bei der Verarbeitung schützenswerter Informationen. Folgende Maßnahmen reduzieren jedoch das Risiko möglicher Attacken für Cross-site-Scripting (XSS):
- Verwendung von Standardbrowsern
-
Die gängigen Browser befolgen festgelegte Sicherheitsrichtlinien, die nur schwer und vorsätzlich deaktiviert werden können. Diese Standardeinstellungen erschweren XSS und sind gerade für den folgenden Punkt unerlässlich.
- Übertragung aller Inhalte per HTTPS zum Browser
-
Werden Webinhalte per HTTPS zum Client übertragen, ist ein unerwünschtes Datenauslesen per JavaScript-Injection oder IFrame-Injection verhindert, da Browser JavaScript-Code nur dann auf einen domain-fremden DOM zugreifen lassen, sofern dieser nicht sicher übertragen wurde.
- Keine Verwendung von Request-Variablen in offenem JS
-
Werden Request-Parameter, z.B. als Teile eines Formulars, direkt in offenem JavaScript (
eval([var])
odersetTimeout([var])
) weiterverwendet, so können Angreifer manipulierte Parameter für DOM-based-XSS nutzen, d.h. es werden JavaScript Befehle als Parameter übergeben, die Inhalte verändern, auslesen oder in einen falschen Kontext setzen. - Encodierung von Request-Variablen im DOM
-
Werden Request-Variablen auf einer Seite dargestellt, so sind diese XML-encodiert einzubinden (siehe Element
outputText
in Kapitel Konvertierung von Daten zur Darstellung und Eingabe). Somit wird verhindert, dass ein Angreifer ein unerwünschtes Script-Tag übergibt.
3.4. Session Behandlung
Die nachfolgenden Kapitel beschäftigen sich mit der Behandlung der Session Informationen, welche in Spring Web Flow anfallen. Hierunter fallen alle Daten, die für die Dialogabläufe benötigt werden:
-
Komponentenbaum der Dialogansicht, dieser beinhaltet die Dialogelemente und die Information über die Bindung an die Backing Beans.
-
Den Flow Container, in welchem die Backing Beans während dem Dialogfluss vorgehalten werden.
-
Die Conversation, welche eine Benutzerinteraktion beinhaltet, bündelt die beiden vorangegangenen Angaben.
Standardmäßig wird diese Information in der HTTP-Session abgelegt und wieder hergestellt. Nach IsyFact-Zielarchitektur erfolgt diese Speicherung in der Datenbank.
3.4.1. Session Zugriff
Für die Arbeit mit Spring Web Flow ist es notwendig, die in der Session notwendigen Daten der Conversation für jeden Schritt bereitzustellen und nach jedem Schritt abzulegen. Hierfür werden die Daten in der Datenbank persistiert.
Dies erfolgt über einen eigenen Session-Manager.
Als Session-Manager wird die Bibliothek isy-session
verwendet, die eine vereinfachte
Konfiguration von Spring Session
(der eigentlichen Implementierung des Session-Managers)
ermöglicht. isy-session
kapselt und vereinfacht die Nutzung von Spring-Session
.
Weitere Erläuterungen zum Session-Manager finden sich im dazugehörigen Konzept Session Management
. Die Einbindung des Session-Managements ist in den Nutzungsvorgaben isy-session
beschrieben. Konkrete Anweisungen zum Aufbau des Session-Speichers finden sich in den Nutzungsvorgaben Redis
.
Wenn isy-session
als Erweiterung der IsyFact nicht eingesetzt wird, muss die Session-Persistierung auf andere Weise durchgeführt
werden, wobei sicherzustellen ist, dass die Requests eines Benutzers immer auf die gleiche
Instanz gehen (Sticky Sessions). Die Session-Daten müssen dabei möglichst klein gehalten werden, um
die Performance der Anwendung nicht zu verschlechtern.
Die Größe der Session wird maßgeblich durch die „im Webflow“ gespeicherten Model-Daten bestimmt. Daher muss darauf geachtet werden, nur unbedingt notwendige Daten im Model zu halten.