"
 
 1 Einführung in JavaServer Faces
"
 
 2 Die Konzepte von JavaServer Faces
"
 
 3 Standard-JSF-Komponenten
"
 
 4 Advanced JSF
"
 
 5 Verwaltung von Ressourcen
"
 
 6 Die eigene JSF-Komponente
"
 
 7 Ajax und JSF
"
 
 8 JSF und HTML5
"
 
 9 JSF und CDI
"
 
 10 PrimeFaces -- JSF und mehr
"
 
 11 Faces-Flows
"
 
 12 MyGourmet Fullstack -- JSF, CDI und JPA mit CODI kombiniert
"
 
 13 JSF und Spring
"
 
 14 MyGourmet Fullstack Spring -- JSF, Spring, Orchestra und JPA kombiniert
"
 
 15 Tobago -- JSF und mehr
"
 
 16 Eine kurze Einführung in Maven
"
 
 17 Eclipse
"
 
 18 Autoren
"
 
 19 Änderungshistorie

2 Die Konzepte von JavaServer Faces

Auf der Basis des im letzten Kapitel erarbeiteten kleinen Beispiels wenden wir uns jetzt dem zu, was an Theorie hinter dem Quellcode steht. Dazu werden wir uns die einzelnen Bestandteile der JavaServer Faces -Technologie ansehen und die Unterstützung von JSF für den Aufbau einer modernen Webapplikation analysieren.

2.1 Aufgaben der JSF-Technologie

Fassen wir noch einmal zusammen, was die JSF-Spezifikation bedeutet - sie definiert ein Framework für die Entwicklung der Benutzerschnittstelle in Java-Webapplikationen. Die Spezifikation dient dazu, den Entwickler in folgenden Bereichen zu unterstützen:
Durch die strikte Trennung der Schichten der Applikation im Sinne der MVC-Architektur können die einzelnen an der Applikation beteiligten Personen (z.B. Webdesigner, Komponentenentwickler und Applikationsentwickler) unabhängig voneinander arbeiten.

2.2 JavaServer Faces in Schlagworten

Wir werden im Verlauf dieses Buches sehr viele Begriffe aus der JSF-Technologie verwenden. Um einen Überblick zu geben, werden wir uns die Definition der wichtigsten Begriffe vorab kurz ansehen.
Komponente (auch Component, UIComponent oder Control)
Eine Komponente ist ein eigenständiger und wiederverwendbarer Baustein, der zusammen mit anderen Komponenten zum Aufbau einer Seite in einer JSF-Anwendung eingesetzt wird. JSF bietet eine gute Auswahl an vordefinierten Komponenten. Die Palette reicht von einfachen Ausgabekomponenten für Texte oder Bilder über Komponenten zum Erfassen von Benutzereingaben bis hin zu komplexen Komponenten zur Darstellung von Tabellen.
Ansicht und Komponentenbaum
Alle Komponenten einer Seite werden zusammen als Ansicht bezeichnet und sind in Form eines Komponentenbaums miteinander verknüpft. Die Wurzel dieses Baums bildet die UIViewRoot -Komponente , alle anderen Komponenten hängen als Kinder und Kinder dieser Kinder unter diesem Element. Alle JSF-bezogenen Vorgänge im Ablauf einer Anfrage an die Anwendung starten mit dem Aufruf einer Methode auf diesem UIViewRoot -Element, das den Aufruf rekursiv an seine Kinder weiterreicht.
Renderer
Die eigentliche Ausgabe der Komponente und ihrer Daten und das Entnehmen der vom Benutzer am Client geänderten Daten wird vom Renderer erledigt. Eine Komponente kann dabei mit vielen Renderern verbunden werden - je nach Ausgabetechnologie wird ein bestimmter Renderer ausgewählt und so das Aussehen der Komponente verändert. Renderer sind optional - die Komponente kann auch selbst ihre Darstellung bestimmen und führt dann den Ausgabeprozess "selbstständig" durch.
Seitendeklarationssprache (auch View Declaration Language, VDL)
Eine Seitendeklarationssprache (VDL) ist eine Syntax, um Ansichten beziehungsweise Seiten für JSF zu deklarieren. Dieses Konzept wurde in JSF 2.0 im Zuge der Integration von Facelets eingeführt, um von der eingesetzten Technologie zu abstrahieren. Der Standard unterstützt in Version 2.0 mit Facelets und JSP zwei konkrete Implementierungen einer VDL. JSP wird allerdings nur mehr aus Kompatibilitätsgründen unterstützt und bietet lediglich einen Teil der neuen Features.
Validator
Die vom Benutzer eingegebenen Werte müssen nicht immer korrekt sein - beispielsweise könnte der Benutzer einen Wert in einem notwendigen Feld nicht eingegeben oder einen zu langen Wert in einem von der Länge her beschränkten Textfeld eingetragen haben. Für solche Fälle gibt es einfach zu verwendende Validatoren in JSF. Diese Validatoren überprüfen die Gültigkeit der eingegebenen Werte und unterbinden das Zurückschreiben von ungültigen Werten ins Modell.
Konverter
Für Webanwendungen ist es notwendig, die von der Applikationslogik gelieferten Datentypen in eine Zeichenkette zu konvertieren, da der Browser nur Zeichenketten verarbeiten und anzeigen kann. Auch dafür gibt es eine Hilfestellung in der JSF-Technologie, die sich Konverter nennt. Sie konvertiert die von der Geschäftslogik verwendeten Datentypen in Zeichenketten und diese Zeichenketten nach der Veränderung durch den Benutzer wieder zurück in Java-kompatible Datentypen. Wenn ein Fehler in der Konvertierung auftritt, wird wie bei den Validatoren ein Zurückschreiben der Werte ins Modell verhindert.
Managed-Beans (auch Backing-Beans genannt)
Hinter den Komponenten liegen Managed-Beans, die die eigentlichen Werte zum Befüllen der Komponenten liefern. Sie werden - wie bereits im ersten Beispiel gezeigt - zentral definiert und können entweder für jeden Benutzer getrennt oder zentral für die gesamte Applikation gültig sein. Managed-Beans sind simple Java-Klassen, auch POJOs (Plain Old Java Objects) genannt, die dem JavaBeans -Standard genügen müssen.
Unified Expression Language (auch Unified-EL)
Die Unified Expression Language ist das Bindeglied zwischen den Komponenten einer Ansicht und den dahinterliegenden Managed-Beans. Mit Value-Expressions werden Eigenschaften von Managed-Beans mit Attributen von Komponenten verbunden - und das nicht nur zum Auslesen der Werte einer Bean, sondern auch zum Zurückschreiben von Benutzereingaben. Method-Expressions erlauben das Verknüpfen von Komponenten mit Methoden. Ein Konzept, das zum Beispiel bei der Validierung von Benutzereingaben und der Ereignisbehandlung eingesetzt wird.
Ereignisse und Ereignisbehandlung
Ereignisse sind eines der zentralen Elemente der JavaServer Faces -Technologie. Ein Ereignis tritt in einer JSF -Anwendung beispielsweise auf, wenn eine Schaltfläche betätigt oder ein Wert geändert wird. Jede Komponente kann Ereignisse auslösen und jede Managed-Bean kann als Interessent für solche Ereignisse registriert werden. Neben den bereits für die Standardkomponenten definierten Ereignissen können neue Ereignisse (für angepasste Komponenten) in die Ereignisbehandlung von JSF aufgenommen werden.
Navigation und Aktionen (navigation-rules, action)
Die Navigation in einer JSF-Applikation wurde vor JSF 2.0 ausschließlich über sogenannte Navigationsregeln in der faces-config.xml definiert. JSF verwendet den Rückgabewert spezieller Methoden, nämlich der "Action"-Methoden (die bei der Behandlung eines Action -Ereignisses aufgerufen werden), um eine Weiterleitung von einer auf die nächste Seite zu veranlassen. Dieser Rückgabewert kann der Name einer Navigationsregel oder ab JSF 2.0 direkt der Name der nächsten Seite sein.
Nachrichten
Sollten bei der Abarbeitung von Methoden oder beim Validieren und Konvertieren von Werten Fehler auftreten, müssen diese Fehler dem Benutzer angezeigt werden. Validierungs- und Konvertierungsfehler werden in JSF in Nachrichten umgewandelt, die dann auf der Seite angezeigt werden können.
Das zeitliche Zusammenspiel dieser einzelnen Objekte in der JSF-Technologie ist genau geregelt, und zwar im "Request Processing Lifecycle" genannten Lebenszyklus einer HTTP-Anfrage. Diesen "Lifecycle" werden wir in Abschnitt [Sektion:  Lebenszyklus einer HTTP-Anfrage in JSF] näher betrachten. Zunächst werden wir aber im folgenden Abschnitt den Zusammenhang zwischen einigen dieser Grundbegriffe anhand des Beispiels MyGourmet 1 demonstrieren.

2.3 MyGourmet 1: Schlagworte im Einsatz

Im letzten Abschnitt wurden kurz die wichtigsten Grundbegriffe von JavaServer Faces erläutert. Nach dieser eher theoretischen Betrachtung wollen wir hier versuchen, diese Schlagworte mit dem bereits bekannten Beispiel MyGourmet 1 in Verbindung zu bringen. Als Ausgangspunkt ist in Listing MyGourmet 1: Die Seite editCustomer.xhtml nochmals der Code der Seitendeklaration editCustomer.xhtml abgebildet.
<!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:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html">
<head>
  <title>MyGourmet - Edit Customer</title>
</head>
<body>
  <h1><h:outputText value="MyGourmet"/></h1>
  <h2><h:outputText value="Edit Customer"/></h2>
  <h:form id="form">
    <h:panelGrid id="grid" columns="2">
      <h:outputLabel value="First Name:" for="firstName"/>
      <h:inputText id="firstName"
          value="#{customer.firstName}"/>
      <h:outputLabel value="Last Name:" for="lastName"/>
      <h:inputText id="lastName"
          value="#{customer.lastName}"/>
      <h:commandButton id="save" action="#{customer.save}"
        value="Save"/>
    </h:panelGrid>
  </h:form>
</body>
</html>
Wenn sich ein Benutzer diese Seite im Browser ansehen will, muss er editCustomer.jsf in die Adressleiste tippen. Wir haben aber bis jetzt immer XHTML-Dateien mit der Endung .xhtml erstellt. Woher kommt dieser Unterschied und warum erscheint im Browserfenster überhaupt eine Ausgabe? Diese Frage ist einfach beantwortet. Nach außen ist nur die JSF-Ansicht editCustomer.jsf sichtbar, die intern auf einer sogenannten Seitendeklaration aufgebaut wird. In "Standard"-JSF ist das eine XHTML-Datei für Facelets oder eine JSP-Datei mit dem gleichen Namen wie die entsprechende JSF-Ansicht. Die JSF-Implementierung weiß, wie der Pfad der zugehörigen Seitendeklaration relativ zum Kontext der Webapplikation aussieht - in unserem Fall /editCustomer.xhtml . Dieser Pfad der Seitendeklaration wird auch View-Identifier genannt.
Die Seitendeklaration bestimmt den Inhalt und die Struktur des Komponentenbaums und somit auch die Ansicht. Die Tags der XHTML-Seite werden von Facelets in Komponenten umgesetzt und im Komponentenbaum angeordnet. Abbildung MyGourmet 1: Von der Seitendeklaration zur Komponente zeigt diese Umsetzung exemplarisch am Tag der Eingabekomponente für den Vornamen.
Abbildung:MyGourmet 1: Von der Seitendeklaration zur Komponente
Auf die gleiche Weise werden auch alle anderen Tags der Seite in Komponenten umgesetzt und in den Baum eingefügt. Der fertige Komponentenbaum der Ansicht editCustomer.jsf sieht dann in etwa wie in Abbildung MyGourmet 1: Komponentenbaum aus. Die Namen der Knoten in der Abbildung sind die Namen der eingesetzten Komponentenklassen.
Abbildung:MyGourmet 1: Komponentenbaum
Der fertig aufgebaute Komponentenbaum kann jetzt von einem Renderer in eine Ausgabesprache umgesetzt und dem Benutzer angezeigt werden. In den meisten Fällen wird es sich bei der Ausgabe um HTML-Seiten handeln, durch das Austauschen des Renderers kann aber fast jede beliebige Ausgabetechnologie eingesetzt werden. Wir wollen uns allerdings für unser Beispiel momentan auf HTML beschränken. Der Renderer nimmt also die in der Komponenteninstanz gespeicherten Daten und gibt den entsprechenden HTML-Code für die jeweilige Komponente aus. Abbildung MyGourmet 1: Von der Komponente zur HTML-Ausgabe zeigt zum Beispiel, wie die Eingabekomponente für den Vornamen in HTML umgesetzt wird.
Abbildung:MyGourmet 1: Von der Komponente zur HTML-Ausgabe
Die Darstellung der komplett gerenderten Seite im Browser ist in Abbildung MyGourmet 1: editCustomer.xhtml im Browser zu sehen.
Abbildung:MyGourmet 1: editCustomer.xhtml im Browser
Die Zusammenhänge zwischen Seitendeklaration, Komponentenbaum und gerenderter Ausgabe dürften somit geklärt sein. Den konkreten Ablauf des gesamten Prozesses in Form des JSF-Lebenszyklus haben wir allerdings noch außen vor gelassen. Diese zeitlichen Zusammenhänge und Abläufe werden in Abschnitt [Sektion:  Lebenszyklus einer HTTP-Anfrage in JSF] behandelt. Zuvor werfen wir jedoch in Abschnitt [Sektion:  Managed-Beans] noch einen genaueren Blick auf Managed-Beans und in Abschnitt [Sektion:  Die Unified Expression Language] auf die Verbindung zwischen Modell und Ansicht mit der Unified Expression Language .

2.4 Managed-Beans

Die Managed-Beans sind ein zentraler Bestandteil der JavaServer Faces . Sie bilden in einer Anwendung das Modell beziehungsweise die Verbindung zum Modell und der Geschäftslogik. Im Hinblick auf eine strikte Trennung von Präsentation und Logik fällt ihnen damit eine sehr wichtige Rolle zu. In der Praxis sind die Aufrufe der Geschäftslogik in einer Anwendung komplett in den Managed-Beans gekapselt. Die Verbindung zu den Eigenschaften und Methoden einer Managed-Bean wird mit Unified-Expression -Ausdrücken realisiert.
Der Rest dieses Abschnitts geht auf die Grundlagen und Details von Managed-Beans ein. Detailliertere Informationen zur Unified Expression Language , dem Bindeglied zwischen Ansicht und Modell in JSF, finden sich in Abschnitt [Sektion:  Die Unified Expression Language] .

2.4.1 Managed-Beans -- die Grundlagen

Wie muss eine Managed-Bean in JSF aussehen, damit sie eingesetzt werden kann? Die Anforderungen sind minimal: Managed-Beans sind simple Java-Klassen, auch POJOs (Plain Old Java Objects) genannt, die dem JavaBeans -Standard genügen müssen. Für die Klasse an sich bedeutet das nur, dass sie einen Konstruktor ohne Parameter mit Sichtbarkeit public haben muss.
Wie bereits erwähnt, wird in JSF auf die Eigenschaften der Managed-Beans zugegriffen, um Daten zu lesen und zu schreiben. Eine Eigenschaft hat einen Namen, einen Typ und Methoden zum Lesen und Schreiben des Werts. Die Namen dieser Methoden müssen folgender Konvention entsprechen: getEigenschaftsName ist der Name der Methode für den lesenden Zugriff und setEigenschaftsName der Name der Methode für den schreibenden Zugriff. Ob in der Methode auf eine private Variable der Klasse zugegriffen wird oder ob eine komplexe Operation der Geschäftslogik dahinter liegt, ist transparent und letztendlich egal. Nach außen ist in beiden Fällen nur die Eigenschaft der Bean sichtbar.
Listing Managed-Bean-Klasse Customer aus MyGourmet 1 zeigt noch einmal die Klasse Customer aus unserem Beispiel MyGourmet 1 . Diese Bean hat die Eigenschaften firstName und lastName vom Typ String , die durch die jeweiligen Getter- und Setter-Methoden definiert sind. Der Zugriff in der Ansicht erfolgt mit den Ausdrücken #{customer.firstName} und #{customer.lastName} . Je nachdem, ob der Wert in der zugreifenden Komponente gelesen oder geschrieben wird, kommt der entsprechende Getter oder Setter zum Einsatz. Die Namen der Eigenschaften leiten sich von den Namen der Getter- und Setter-Methoden ab. Die privaten Felder im Hintergrund haben keinerlei Einfluss auf den Namen - sie nehmen nur den Wert der Eigenschaft auf.
Die Methode save erfüllt einen anderen Zweck. Sie wird zur Behandlung von Ereignissen benutzt und ist keiner Eigenschaft zugeordnet - detailliertere Informationen zu diesem Thema finden sich in Abschnitt [Sektion:  Ereignisse und Ereignisbehandlung] .
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class Customer {
  private String firstName;
  private String lastName;
  public String getFirstName() {
    return firstName;
  }
  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }
  public String getLastName() {
    return lastName;
  }
  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
  public String save() {
    return "/showCustomer.xhtml";
  }
}
Eigenschaften von Beans müssen nicht unbedingt sowohl eine Getter- als auch eine Setter-Methode haben. Je nachdem, welche der beiden vorhanden ist, handelt es sich dann um eine Eigenschaft, die nur gelesen oder nur geschrieben werden kann.
Genau genommen haben wir in den letzten Absätzen lediglich von JavaBeans gesprochen. Zu Managed-Beans werden sie erst, wenn sie tatsächlich von JSF verwaltet werden. Wie das funktioniert, zeigt der nächste Abschnitt.

2.4.2 Konfiguration von Managed-Beans

Einer der Eckpfeiler von JSF ist die zentrale Stelle für die Behandlung von Managed-Beans - die Managed Bean Creation Facility . Mit diesem Instrument werden folgende Aufgaben durchgeführt:
Damit Managed-Beans in der Ansicht verwendet werden können, muss die JSF-Umgebung wissen, unter welchem Namen und unter welcher Klasse die jeweilige JavaBean zu finden ist. Seit JSF 2.0 gibt es zwei Varianten, um diese Registrierung durchzuführen. Im Einführungsbeispiel haben wir ja bereits gesehen, wie eine Managed-Bean über Annotationen deklariert wird. Listing Konfiguration der Bean customer aus MyGourmet 1 über Annotationen zeigt nochmals den relevanten Teil der Klasse. Alternativ können Managed-Beans auch in der faces-config.xml deklariert werden - in JSF-Versionen vor 2.0 war das noch die einzige Möglichkeit.
@ManagedBean
@SessionScoped
public class Customer {
  ...
}
Listing Konfiguration der Bean customer aus MyGourmet 1 in der faces-config.xml zeigt, wie die Konfiguration der Bean customer aus MyGourmet 1 in der Datei faces-config.xml aussieht.
<managed-bean>
  <managed-bean-name>customer</managed-bean-name>
  <managed-bean-class>
    at.irian.jsfatwork.gui.page.Customer
  </managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Die beiden in den Listings Konfiguration der Bean customer aus MyGourmet 1 über Annotationen und Konfiguration der Bean customer aus MyGourmet 1 in der faces-config.xml vorgestellten Varianten führen zu demselben Ergebnis: Die Managed-Bean mit dem Namen customer wird definiert. Welche der beiden Sie in Ihrem Projekt einsetzen, ist zum Teil auch eine Geschmacksfrage. Wir haben uns in MyGourmet für Annotationen entschieden, um aufgeblähte Konfigurationsdateien zu vermeiden.
In der faces-config.xml erfolgt die Deklaration einer Bean in einem Element namens managed-bean . Darin verschachtelt folgt im Element managed-bean-name zuerst der Name, unter dem die Bean in EL-Ausdrücken referenziert wird - in unserem Beispiel customer . Die Klasse der Bean wird im Element managed-bean-class festgelegt. Zuletzt folgt im Element managed-bean-scope mit session die Angabe der Lebensdauer der Bean.
Bei der Deklaration mit der Annotation @ManagedBean entspricht der Name der Managed-Bean laut Konvention dem Klassennamen mit einem kleinen Anfangsbuchstaben. In unserem Fall wird zum Beispiel aus der Klasse Customer die Bean customer . Wollen Sie einen anderen Namen verwenden, können Sie diesen im Element name der Annotation setzen. Listing Konfiguration der Bean customer mit alternativem Namen zeigt die bereits bekannte Bean mit dem expliziten Namen customerBean . Der Zugriff in der Ansicht erfolgt jetzt mit dem Ausdruck #{customerBean.firstName} .
@ManagedBean(name = "customerBean")
@SessionScoped
public class Customer {
  ...
}
In Standard-JSF sind folgende Gültigkeitsbereiche (Scopes) für Beans definiert (in Klammer steht der Wert für die Konfiguration in der faces-config.xml und die entsprechende Annotation):
Abbildung Vergleich der Lebensdauer unterschiedlicher Gültigkeitsbereiche vergleicht die Lebensdauer der von JSF standardmäßig zur Verfügung gestellten Gültigkeitsbereiche.
Abbildung:Vergleich der Lebensdauer unterschiedlicher Gültigkeitsbereiche
Nachdem Sie jetzt wissen, wie Managed-Beans deklariert werden, wollen wir Ihnen nicht vorenthalten, wie JSF diese verwaltet. Der interne Ablauf beim Zugriff auf eine Bean sieht wie folgt aus:
  1. Beim ersten Zugriff auf die Bean wird diese automatisch durch die Managed Bean Creation Facility instanziiert. Ist die Instanz der Bean bereits vorhanden, wird sie zurückgegeben. Die Instanziierung kann nur erfolgen, wenn ein Konstruktor ohne Argumente verfügbar ist.
  2. Nach dem Erzeugen der Bean werden alle Managed-Properties ini-tialisiert. Genaueres dazu finden Sie in Abschnitt [Sektion:  Managed-Properties] .
  3. Zu guter Letzt wird die Managed-Bean unter der spezifizierten Lebensdauer gespeichert.
Das erfolgt in unserem Beispiel im Session-Scope, also solange eine logische Verbindung zwischen Benutzer und Applikation in Form einer Sitzung besteht. Verwenden Sie den Session-Scope nur, wenn unbedingt notwendig. Mit dem neuen View-Scope ist es jetzt relativ einfach möglich, Daten über mehrere Requests mitzunehmen. Zumindest so lange, bis auf eine neue Seite navigiert wird.

2.4.3 Managed-Properties

Die Managed Bean Creation Facility bietet die Möglichkeit, Eigenschaften von Managed-Beans nach dem Erstellen zu initialisieren (sogenannte Managed-Properties). Neben fixen Werten besteht über Dependency-Injection auch die Möglichkeit, Abhängigkeiten auf andere Beans für die Initialisierung zu verwenden.
Wie bei der Konfiguration der Beans selbst existieren auch für die Deklaration der Managed-Properties zwei Varianten. Mit der Annotation @ManagedProperty werden direkt die Felder der Eigenschaft in der Bean annotiert. Der initiale Wert steht bei dieser Methode im Element value . Alternativ kann dieselbe Deklaration auch in der faces-config.xml gemacht werden. Dazu kommt das Element managed-property zum Einsatz, wobei der Name der Eigenschaft und der zu setzende Wert in den Elementen property-name und value stehen.
Listing Managed-Properties mittels Annotationen zeigt den Java-Code einer annotierten Managed-Bean mit Managed-Properties. In Listing Managed-Properties in der Konfiguration ist die äquivalente Konfiguration in der faces-config.xml zu sehen. Die Annotationen in der Klasse Login sind in diesem Fall natürlich nicht notwendig.
@ManagedBean
@SessionScoped
public class Login {
  @ManagedProperty(value = "3")
  private int loginRetries;
  @ManagedProperty(
    value = "#{roleResolver.defaultResolver}")
  private RoleResolver roleResolver;
  ...
}
<faces-config>
  ...
  <managed-bean>
    <managed-bean-name>login</managed-bean-name>
    <managed-bean-class>
        at.company.webapp.model.Login
    </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
      <property-name>loginRetries</property-name>
      <value>3</value>
    </managed-property>
    <managed-property>
      <property-name>roleResolver</property-name>
      <value>#{roleResolver.defaultResolver}</value>
    </managed-property>
  </managed-bean>
  ...
</faces-config>
Nach der Initialisierung der Bean login besitzt deren Attribut loginRetries den Wert 3. Das Attribut roleResolver zeigt, dass als Wert auch eine Referenz auf eine andere Managed-Bean angeführt sein kann - über eine Value-Expression. Alle anderen Attribute besitzen ihre Standardwerte.
Der Einsatz von Managed-Beans zum Initialisieren von Managed-Properties unterliegt in JSF einer Einschränkung. Eine Managed-Property darf nicht mit einer Managed-Bean mit kürzerer Lebensdauer initialisiert werden. Es ist zum Beispiel nicht erlaubt, eine Bean im Request-Scope in eine Bean im Session-Scope zu injizieren. Die Erklärung dafür ist einfach: Da eine Session länger läuft als eine Anfrage, ist die injizierte Bean nach der ersten Anfrage nicht mehr aktuell. Beans im None-Scope können dagegen immer verwendet werden, da sie in keinem Gültigkeitsbereich abgelegt sind.
Die Initialisierung mit Referenzen über Dependency-Injection bietet einige Vorteile gegenüber der Auflösung von Beans im Code. Zum einen lassen sich damit statische Aufrufe im Code vermeiden und zum anderen steigen Wartbarkeit und Übersichtlichkeit durch die zentrale Konfiguration.
Die Managed Bean Creation Facility ist die von JSF zur Verfügung gestellte Möglichkeit zur Erstellung von Beans - aber bei Weitem nicht die einzig mögliche. Mit CDI und Spring gibt es Alternativen, die in Konfigurationsumfang und Erweiterbarkeit deutlich überlegen sind, ohne jedoch die Komplexität der Anwendung unnötig zu erhöhen. In Abschnitt Sektion:  Beans und Dependency-Injection mit CDI zeigen wir, wie Managed-Beans mit CDI verwaltet werden und welche Vorteile sich daraus ergeben.

2.4.4 Die Rolle von Managed-Beans

Bis jetzt sind wir davon ausgegangen, dass die Managed-Beans das Modell der Anwendung laut MVC-Entwurfsmuster bilden. Das muss nicht unbedingt so sein - in den meisten Fällen ist es sogar besser, wenn die Managed-Beans nicht direkt das Modell sind, sondern nur eine Vermittlerrolle zwischen Ansicht und tatsächlichem Modell einnehmen.
Wie kann man sich das vorstellen? Ein einfaches Beispiel, basierend auf MyGourmet 1 , hilft, diesen Sachverhalt zu klären. In MyGourmet 1 bildet die Managed-Bean das Modell der Anwendung, ein Zugriff auf den Vornamen des Kunden sieht folgendermaßen aus: #{customer.firstName} . Neben den Modelleigenschaften des Kunden beinhaltet die Bean hier auch die Action-Methoden zur Ereignisbehandlung. Der Nachteil dieser Variante ist die enge Kopplung von GUI-Logik und Modell, der sich besonders dann negativ auswirkt, wenn es zu Änderungen kommt.
Eine elegantere Lösung ist, die Modellklasse Customer komplett von JSF-Code freizuhalten und eine Managed-Bean customerBean einzuführen, die neben der GUI-Logik eine Eigenschaft vom Typ Customer besitzt. Ein Zugriff auf den Vornamen des Kunden sieht dann folgendermaßen aus: #{customerBean.customer.firstName} . Die Modellklasse kann in dieser Variante unabhängig von der Präsentationsschicht in einer tiefer liegenden Schicht der Anwendung erstellt werden. Die beiden Varianten sind in Abbildung Die Rolle von Managed-Beans dargestellt.
Abbildung:Die Rolle von Managed-Beans
In MyGourmet werden wir aus Gründen der Einfachheit vorerst dabei bleiben, die Klasse Customer direkt als Managed-Bean zu verwenden. Erst bei den etwas umfangreicheren Beispielen ab MyGourmet 5 in Abschnitt [Sektion:  MyGourmet 5: Konvertierung] kommt eine eigene Klasse zum Einsatz.

2.5 Die Unified Expression Language

Ein Basiselement der JSF-Spezifikation ist die Unified Expression Language (kurz Unified-EL) , die es ermöglicht, Komponenten der Benutzerschnittstelle und der Geschäftsdaten dahinter sehr dynamisch zu verbinden. Wir wollen ja Daten aus dem Modell lesen, aber auch Benutzereingaben ins Modell zurückschreiben. Des Weiteren muss definiert werden, welche Methoden welche Ereignisse behandeln. Mithilfe von Value-Expressions werden Komponentenattribute an Managed-Beans und ihre Eigenschaften gebunden und mit Method-Expressions werden Methoden referenziert. Wie das in MyGourmet 1 erfolgt, sehen wir uns in Abschnitt [Sektion:  Unified-EL in MyGourmet 1] etwas genauer an.
Für die Definition eines EL-Ausdrucks ist eine Raute und eine geschwungene Klammer dem Ausdruck voran- und eine geschwungene Klammer dem Ausdruck nachzustellen. Was darf zwischen diesen Begrenzern stehen? Zwischen diesen Begrenzern können der Name von Managed-Beans oder "impliziten" Objekten, Eigenschaften dieser Objekte (von den Elternelementen mit Punkten getrennt) oder auch Operatoren angegeben werden. Dazu zählen sowohl arithmetische Operatoren wie "+" und "-" als auch Vergleichsoperatoren, sogar der "ternäre" Operator ( bedingung ? wenn_wahr : wenn_falsch ) ist erlaubt.
Es lassen sich aber nicht nur Eigenschaften mit der Unified Expression Language auslesen, auch Methoden können mit dieser vereinheitlichten "Ausdruckssprache" aufgerufen werden - über die Angabe von Method-Expressions. Beispielsweise werden mit Konstrukten der Unified Expression Language Ereignisbehandlungsmethoden aufgerufen.

2.5.1 Unified-EL in MyGourmet 1

Im Beispiel MyGourmet 1 haben wir bereits ausführlich von Value-Expressions Gebrauch gemacht, um Kundendaten anzuzeigen und Benutzereingaben abzuspeichern. Nehmen wir den Vornamen des Kunden als Beispiel: Mit dem Ausdruck #{customer.firstName} wird das Attribut value der Eingabekomponente mit der Eigenschaft firstName der Managed-Bean customer verbunden. In Abbildung Value-Expression in Eingabekomponente ist der Zusammenhang zwischen Tag, Komponente und Managed-Bean noch genauer dargestellt.
Abbildung:Value-Expression in Eingabekomponente
Eine Value-Expression wird erst beim tatsächlichen Lesen oder Setzen des Werts aufgelöst. Deswegen wird in der Komponente auch die Value-Expression selbst und nicht der Wert abgelegt. Eine genauere Betrachtung, warum das notwendig ist, folgt in Abschnitt [Sektion:  Lebenszyklus einer HTTP-Anfrage in JSF] über den Lebenszyklus von JSF.
Die Daten werden beim Anzeigen der Seite bereits ordnungsgemäß gelesen und beim Abschicken des Formulars geschrieben. Es fehlt die Möglichkeit, benutzerdefinierten Code beim Bearbeiten der abgeschickten Seite aufzurufen - zum Beispiel um die Eingaben des Benutzers in der Datenbank zu speichern. Hier kommen Method-Expressions ins Spiel. Hat der Benutzer auf der Seite editCustomer.jsf die Schaltfläche zum Speichern betätigt, soll serverseitig die Methode save der Bean aufgerufen werden. Dazu gibt es bei Befehlskomponenten das Attribut action , das eine Method-Expression enthalten kann. In unserem Beispiel ist das der Ausdruck #{customer.save} . Die damit referenzierte Methode wird aufgerufen, wenn alle vom Benutzer eingegebenen Daten validiert wurden und gültig sind. Die zurückgelieferte Zeichenkette wird zur Navigation benutzt und bestimmt, welche Seite angezeigt wird. Abbildung Method-Expression in Befehlskomponente zeigt das Tag der Komponente und den Weg bis zum Aufruf der mit der Komponente verbundenen Methode.
Abbildung:Method-Expression in Befehlskomponente
Ausführlichere Informationen zur Unified-EL finden sich im nächsten Abschnitt. Der Navigationsvorgang wird in Abschnitt [Sektion:  Navigation] näher beleuchtet.

2.5.2 Die Unified-EL im Detail

Im Folgenden finden Sie einen kurzen Überblick über die Möglichkeiten, die die Unified-EL bietet.
Implizite Objekte: Als Basis für die Auflösung der EL -Ausdrücke kann jede Managed-Bean dienen oder eine Liste von impliziten Objekten, die von JSF zur Verfügung gestellt werden. Die folgende Liste enthält die wichtigsten impliziten Objekte:
Beispiel: Ein häufig verwendetes Beispiel für implizite Objekte: Mit dem Ausdruck #{param.myParam} kann auf den Request-Parameter namens myParam zugegriffen werden.

2.5.3 Erweiterungen der Unified-EL in Java EE 6

Java EE 6 bringt eine neue Version der Unified-EL mit einigen lang erwarteten Neuerungen. Mit der neuen Version können endlich in EL-Ausdrücken Methoden mit Parametern verwendet werden. Bislang war das nur für statische Methoden über EL-Funktionen (siehe Abschnitt Sektion:  Definition einer EL-Funktion ) möglich.
Der Einsatz von Method-Expressions mit Parametern eröffnet eine Reihe interessanter Möglichkeiten in JSF. Im Beispiel MyGourmet 9 in Abschnitt Sektion:  MyGourmet 9: UIData und Detailansicht sehen Sie, wie eine Action-Methode mit Parametern zum Löschen einer Adresse aus einer Liste eingesetzt werden kann.
Aber das ist noch nicht alles. Als weitere Neuerung sind Value-Expressions nicht mehr auf Eigenschaften von Beans beschränkt. Die neue Unified-EL erlaubt den Aufruf einer beliebigen Methode, deren Rückgabewert den Wert der Value-Expression bildet. Damit sind folgende Unified-EL -Ausdrücke möglich:
Die neue Version der Unified-EL ist ein Teil von Java EE 6 und kommt automatisch mit allen Servern, die Servlet 3.0 und JSP 2.2 unterstützen (dazu zählen zum Beispiel Tomcat 7 oder Jetty 8). Falls Sie einen älteren Server einsetzen, müssen Sie trotzdem nicht auf die wichtigsten Features der neuen Unified-EL verzichten. Abschnitt subsec Konfiguration der Unified-EL zeigt die Verwendung der alternativen EL-Implementierung von JBoss .

2.6 Lebenszyklus einer HTTP-Anfrage in JSF

Aus einer HTTP-Anfrage heraus müssen in JSF einige Verarbeitungsschritte durchgeführt werden, um den Zustand der Applikation wiederherzustellen und die Vorbereitungen dafür zu treffen, dass die Applikationslogik aufgerufen werden kann. Diese Schritte betreffen einmal die Wiederherstellung des Komponentenbaums, das Auslesen der vom Benutzer veränderten Daten aus der HTTP-Anfrage und die Validierung dieser Daten sowie das Übertragen dieser validierten Daten in die Modell-objekte. Nach dem Aufruf von Aktionen in der Geschäftslogik bleibt dann noch der letzte Schritt - die Ausgabe der Antwort auf die HTTP-Anfrage (das sogenannte "Rendering"). Dieser Ablauf ist in der JavaServer Faces -Spezifikation genau definiert und bildet eine essenzielle Grundlage für jede auf der JSF-Technologie basierende Anwendung. In Abbildung Der Lebenszyklus ("`Lifecycle"') einer HTTP-Anfrage ist der Ablauf dargestellt.
Abbildung:Der Lebenszyklus ("`Lifecycle"') einer HTTP-Anfrage
Wir werden nun die einzelnen Verarbeitungsschritte genauer betrachten. Um das Ganze spannender zu gestalten, werden wir auch gleich auf die praktische Verwertbarkeit der Vorgänge in den einzelnen Phasen eingehen.
Phase 1: Ansicht wiederherstellen (Restore View)
Jede Ansicht einer JSF-Anwendung besteht aus Komponenten, die in Form eines Komponentenbaums organisiert sind. Die Abarbeitung einer Anfrage beginnt in der ersten Phase des Lebenszyklus mit dem Aufbau des Komponentenbaums.
Trifft die erste Anfrage auf eine Ansicht ein, existiert der Komponentenbaum noch nicht und JSF baut ihn aus der Seitendeklaration neu auf. Kommt als Seitendeklarationssprache JSP zum Einsatz, leitet JSF die Anfrage an die hinter der Ansicht liegende JSP-Seite weiter. Diese wird abgearbeitet und bei jedem Antreffen eines neuen, noch nicht zu einer initialisierten Komponente gehörenden Tags wird eine neue Komponente erzeugt und mit den Attributwerten aus der JSP-Seite initialisiert. Facelets verfolgt eine ganz ähnliche Strategie und baut den Baum beim Parsen des zugrunde liegenden XHTML-Dokuments auf.
In einem zweiten Durchlauf durch den Komponentenbaum wird die Seite dann gerendert. Eine initiale Anfrage auf eine Ansicht durchläuft also nur die erste und die letzte Phase des Lebenszyklus. Abbildung Initialzündung des Lebenszyklus zeigt diesen Ablauf.
Abbildung:Initialzündung des Lebenszyklus
Wenn eine Anfrage das zweite Mal dieselbe Seite anfordert, wird in JSF-Versionen vor 2.0 der Komponentenbaum komplett aus dem zuvor am Server oder am Client gespeicherten Zustand wieder hergestellt. Zum Komponentenbaum gehören natürlich nicht nur die Komponenten selbst, sondern auch Validatoren, Konverter und die "alten" Werte sowie sämtliche anderen Eigenschaften der Komponenten.
Ab JSF 2.0 gibt es an diesem Punkt einen entscheidenden Unterschied zwischen JSP und Facelets. Mit JSP wird der komplette Komponentenbaum aus dem Seitenzustand rekonstruiert. Beim Einsatz von Facelets baut JSF hingegen zuerst die Ansicht aus der Seitendeklaration neu auf und verarbeitet erst dann den gespeicherten Zustand. Dieser neue Ansatz - auch Partial-State-Saving genannt - bietet einige Vorteile bezüglich Performance und Größe des Seitenzustands. Erste Tests zeigen, dass sich bei aktiviertem Partial-State-Saving die Größe des Seitenzustands ungefähr um den Faktor 3 reduziert.
Phase 2: Request-Parameter anwenden (Apply Request Values)
In dieser Phase wird der gesamte Komponentenbaum bearbeitet und die vom Benutzer eingetragenen Werte werden den einzelnen Komponenten zugewiesen. Das geschieht, indem am Wurzelknoten die Methode processDecodes() aufgerufen wird - der Wurzelknoten ruft dann die gleiche Methode auf seinen Kindknoten und diese wiederum auf ihren Kindknoten rekursiv auf.
Außerdem sucht sich beim Abarbeiten der Methode jede Komponente (oder genauer gesagt der der Komponente zugeordnete Renderer) aus der HTTP-Anforderung, und zwar aus den Parametern, HTTP-Kopfzeilen (Header) und Cookies, die Werte heraus, die diese Komponente betreffen, und speichert sie als "übermittelter" Wert (Submitted-Value) . Dieser Prozess wird "decodieren" (engl. decoding ) genannt. Der Submitted-Value ist allerdings noch nicht der Wert, der dann später tatsächlich ins Modell geschrieben wird - er muss zunächst noch in ein fürs Datenmodell geeignetes Format konvertiert und validiert werden. Außerdem kann das Zurückschreiben durch das Fehlschlagen der Konvertierung oder der Validierung noch verhindert werden.
Die Konvertierung und Validierung wird dann in der nächste Phase vorgenommen, allerdings kann diese Phase auch vorgezogen werden: Mit dem Setzen des immediate -Attributs wird die Komponente "angewiesen", die Konvertierung und Validierung bereits in Phase 2 durchzuführen. Warum das in manchen Fällen wünschenswert ist und wie das genau funktioniert, wird in Abschnitt [Sektion:  Ändern des Lebenszyklus -- immediate-Attribut] näher erläutert.
Der Submitted-Value ist auch in der Renderphase, speziell für das Schreiben von benutzerdefinierten Komponenten, wichtig. Wenn nämlich die Validierung oder Konvertierung von Komponenten der Seite nicht erfolgreich verläuft, muss beim Rendering der Komponenten der Submitted-Value herangezogen werden - es wäre nicht richtig, hier den "alten" Komponentenwert, der in der value-Eigenschaft gespeichert ist, heranzuziehen, weil sonst Informationen des Benutzers verloren gehen würden.
Phase 3: Konvertierung und Validierung durchführen (Process Validations)
Der aus der HTTP-Anfrage ausgelesene Wert einer Komponente wird in dieser Phase konvertiert und validiert - natürlich wieder am Wurzelknoten startend für den ganzen Komponentenbaum. Die Konvertierung erfolgt vom zeichenkettenbasierten Submitted-Value auf die für das dahinterliegende Datenmodell notwendige Darstellung. Aus der Zeichenkette " 01.01.2012" wird dann zum Beispiel eine Instanz der Klasse java.util.Date .
Standardkonverter: Diese Konvertierung wird standardmäßig durchgeführt, ohne dass der Entwickler aktiv werden muss, dazu wird einfach der im Framework für einen bestimmten Datentyp definierte Standardkonverter herangezogen.
Benutzerdefinierter Konverter: Ist das Verhalten des Standardkonverters nicht ausreichend oder unerwünscht (weil zum Beispiel eine spezielle Datumsklasse für die Geschäftsdaten verwendet werden soll), kann man eigene Konverter erstellen und einzelnen Komponenten zuweisen. Das Erzeugen von benutzerdefinierten Konvertern und deren Einbindung erläutern wir in Abschnitt [Sektion:  Konvertierung] näher.
Sofort nach dem erfolgreichen Abschluss des Konvertierungsvorgangs wird der Wert der Komponente validiert; das erledigen sogenannte Validatoren.
Validatoren: Es gibt im JSF-Standard bereits einige vorgefertigte Validatoren (z.B. ein LengthValidator oder ein DoubleRangeValidator ), Apache MyFaces liefert noch einige Validatoren mehr mit (siehe Abschnitt [Sektion:  Validierung] , beispielsweise gibt es Kreditkarten- und E-Mail-Validatoren). Das Einbinden dieser Validatoren erfolgt über das Hinzufügen von Kindelementen zu Komponenten. Darüber hinaus ist es sehr einfach, selbst Validatoren und auch Methoden, die eine Validierung übernehmen, zu erstellen. Validierungsmethoden können - ähnlich wie Konverter - mit dem Attribut validator an die Komponente gebunden werden. Beim Validieren eines erforderlichen Werts liegt die Sache etwas anders: Hier wird kein Kindelement der Komponente hinzugefügt, sondern das Attribut required der Komponente auf true gesetzt. Weiterführende Informationen zur Validierung finden sich in Abschnitt [Sektion:  Validierung] .
Der nächste Schritt ist nun das Setzen des konvertierten und validierten Werts: Allerdings noch nicht in die Managed-Beans, sondern vorerst nur in die Eigenschaft value innerhalb der Komponente Eines der Grundkonzepte von JSF ist, dass die konvertierten und validierten Werte aller Komponenten gemeinsam in der Phase "Modell aktualisieren" in die Managed-Beans übernommen werden.: . Gleichzeitig wird mit dem Setzen von localValueSet markiert, dass ein Wert in der Komponente selbst gespeichert wurde. Anschließend kommt auch die Ereignisbehandlung von JavaServer Faces ins Spiel: Hat sich der Wert der Komponente geändert, wird ein Value-Change-Event erzeugt und registriert. In der nächsten Ereignisbehandlungsphase werden die Behandlungsroutinen für dieses Ereignis aufgerufen.
Was passiert aber, wenn die Konvertierung und/oder die Validierung fehlschlägt? Ist dies der Fall, werden die entsprechenden Fehlermeldungen generiert und die aktuelle Seite wird inklusive Fehlermeldungen als Antwort gerendert. Das bedeutet, dass alle folgenden Phasen außer der "Antwort rendern"-Phase übersprungen werden - es gibt kein Übertragen der Werte in die Geschäftsdaten und kein Ausführen von Aktionen in der Geschäftslogik. Abbildung Lebenszyklus für fehlgeschlagene Validierung stellt diesen Ablauf grafisch dar.
Abbildung:Lebenszyklus für fehlgeschlagene Validierung
Phase 4: Modell aktualisieren (Update Model Values)
Unter der Voraussetzung, dass die abgesendeten Werte (Submitted-Values) richtig konvertiert, validiert und als Local-Value gespeichert werden konnten, werden diese Werte jetzt auf die von den einzelnen Komponenten referenzierten Eigenschaften der Geschäftsdaten übertragen. Dafür werden die Setter-Methoden aufgerufen, die notwendig sind, um das Modell mit den neuen Daten zu aktualisieren.
Wir haben schon bei der Analyse des Beispiels kurz besprochen, wie eine solche Referenz zwischen Komponente und Geschäftslogik aussehen kann: Üblicherweise wird dafür die value -Eigenschaft der Komponente mit einer Value-Expression an eine Eigenschaft der Geschäftslogik gebunden. Ein Beispiel hierfür ist der bereits bekannte Ausdruck #{customer.firstName} aus MyGourmet , mit dem die Eigenschaft firstName der {Managed-Bean} customer referenziert wird.
Nach dem Ausführen dieser Phase wurde ein konvertierter und validierter Wert in die dahinterliegenden Beans eingetragen - wir mussten dafür noch keine einzige Zeile Applikationslogik schreiben.
Phase 5: Applikation ausführen (Invoke Application)
Der nächste Schritt ist das Ausführen von speziellen Ereignissen, den sogenannten Aktionen. Diese speichern beispielsweise geänderte Geschäftsdaten, lesen Geschäftsdaten auf der Basis geänderter Filterkriterien neu aus oder kommunizieren mit anderen Systemen. Jedenfalls bestimmen sie durch ihren Rückgabewert, wohin die Reise in der Anwendung gehen wird - welche Ansicht also als Nächstes aufgerufen wird.
Die Übergänge zwischen den einzelnen Ansichten haben wir durch die geeignete Definition der Navigation bereits vorher festgelegt. Wir registrieren Aktionen durch das Setzen des action -Attributs der Befehlskomponenten (von UICommand abgeleitete Komponenten).
Zusätzlich zum action -Attribut jeder Befehlskomponente gibt es auch ein Attribut actionListener . Mit diesem Attribut wird eine Verbindung zu Ereignisbehandlungsmethoden geschaffen, die knapp vor den Action-Methoden aufgerufen werden. Wozu benötigen wir solche Action-Listener? Im Gegensatz zur Action-Methode wird beim Aufruf einer mit dem Attribut actionListener gebundenen Methode ein Parameter vom Typ javax.faces.event.ActionEvent mitgegeben, und dieser Parameter enthält wiederum ein Element component . Damit kann also die Komponente, die diese Aktion ausgelöst hat, sehr schnell aufgefunden werden.
Weitere Informationen zu Ereignissen finden sich in Abschnitt [Sektion:  Ereignisse und Ereignisbehandlung] . Die Navigation wird in Abschnitt [Sektion:  Navigation] genauer betrachtet.
Phase 6: Antwort rendern (Render Response)
In der letzten Phase wird der Komponentenbaum gerendert und die Ausgabe wird als Antwort der JSF-Anfrage zum Client geschickt. Des Weiteren speichert JSF den Zustand des Komponentenbaums für nachfolgende Anfragen auf dieselbe Ansicht.
Das Rendern des Komponentenbaums läuft im Prinzip in zwei Schritten ab:
  1. Der Komponentenbaum wird aus der Seitendeklaration aufgebaut. Mit JSP passiert das durch einen Forward auf die JSP-Datei, in Facelets beim Parsen der XHTML-Datei.
  2. Der in Schritt 1 erstellte Komponentenbaum wird durch einen Aufruf der Methode encodeAll auf dem Wurzelknoten gerendert.
In allen JSF-Versionen kommen beim Rendern der Werte der einzelnen Komponenten wieder die bereits erwähnten Konverter ins Spiel: Der Renderer holt den Wert der Komponente, ruft die Methode getAsString() auf dem Konverter auf und rendert das in eine Zeichenkette verwandelte Objekt zurück zum Client. Damit ist der Lebenslauf der Anfrage abgeschlossen.
Ausführlichere Informationen zum Thema Seitendeklarationssprachen finden Sie in Abschnitt [Sektion:  Seitendeklarationssprachen] .

2.6.1 Ändern des Lebenszyklus -- immediate-Attribut

JSF wäre nicht JSF, wenn es nicht auch beim Ablauf des Lebenszyklus Möglichkeiten zur Einflussnahme gäbe. Bei der Beschreibung der Apply-Request-Values-Phase wurde bereits kurz die vorzeitige Konvertierung und Validierung von Eingabekomponenten über das immediate -Attribut erwähnt. Eine detailliertere Erläuterung dieses Themas holen wir jetzt nach.
Mit dem Attribut immediate kann das Verhalten von Eingabe- und Befehlskomponenten beim Ablauf des Lebenszyklus beeinflusst werden. Der Standardwert für immediate ist false , was einem normalen Ablauf des Lebenszyklus entspricht. Wird der Wert auf true gesetzt, ändert sich das Verhalten der Komponenten. Bei Eingabekomponenten bewirkt es die vorgezogene Konvertierung und Validierung des Werts der Komponente in der Apply-Request-Values-Phase. Das Verhalten für Befehlskomponenten wird dahingehend abgeändert, dass Aktionen und Action-Listener nicht nach der Invoke-Application-Phase, sondern bereits nach der Apply-Request-Values-Phase aufgerufen werden. Das gilt wohlgemerkt nur für Komponenten, deren immediate -Attribut tatsächlich den Wert true hat. Die Abarbeitung aller anderen Komponenten der Seite bleibt unverändert.

2.6.1.1 immediate-Attribut für Eingabekomponenten

Wann ist dieses Verhalten bei Eingabekomponenten erwünscht? Sehr häufig benötigt man die Validierung und Konvertierung nur eines Teils des Komponentenbaums und die Änderung der Seite (oder des Verhaltens der Seite) aufgrund dieser kleinen Änderung. Diese Änderung soll dann unabhängig von der Validierung der anderen Komponentenwerte auf jeden Fall durchgeführt werden. Beispielsweise soll bei der Auswahl einer Zahlung mit Kreditkarte ein neues Feld für den Kartentyp und die Kreditkartennummer eingeblendet werden - diese Einblendung soll natürlich auch erfolgen, wenn im E-Mail-Adressfeld noch keine gültige E-Mail-Adresse steht. Genau dieses Verhalten erreichen Sie, indem Sie das Attribut immediate der Komponente zur Auswahl der Zahlungsart auf true setzen.
Sehen wir uns dieses kleine Beispiel in der Praxis an: Dazu erstellen wir in einer Seite ein Eingabefeld für den Namen mit verpflichtender Eingabe, ein Auswahlfeld und ein Eingabefeld für den Kreditkartentyp, das nur bei selektiertem Auswahlfeld angezeigt wird. Listing Value-Change-Listener in der Ansicht zeigt das Fragment.
<h:inputText value="#{customer.lastName}" required="true"/>
<h:selectBooleanCheckbox onclick="this.form.submit()"
    value="#{customer.useCreditCard}" immediate="true"
    valueChangeListener="#{customer.useCreditCardChanged}"/>
<h:inputText value="#{customer.creditCardType}"
    rendered="#{customer.useCreditCard}"/>
Die notwendige Logik zum Ein- und Ausblenden des Eingabefelds für den Kreditkartentyp befindet sich im Value-Change-Listener , der für die Komponente registriert ist. Diese Methode wird im Ablauf des Lebenszyklus aufgerufen, wenn sich der Wert der Komponente geändert hat. In unserem Fall wird in dieser Methode die Eigenschaft useCreditCard in der Managed-Bean auf den neuen Wert gesetzt. Die Eingabekomponente benutzt die gleiche Eigenschaft für das Attribut rendered und wird abhängig vom Wert der Eigenschaft dargestellt oder ausgeblendet.
Würde der Standardlebenslauf einer JSF-Anfrage abgearbeitet werden, könnte das letzte Textfeld nur dann ohne Fehlermeldungen angezeigt werden, wenn der Benutzer bereits einen Namen eingegeben hat. Ist der Name leer, erfolgt zwar die Umschaltung; es wird aber eine Fehlermeldung angezeigt.
Die Lösung ist, das immediate -Attribut des Auswahlfelds auf den Wert true zu setzen. Dadurch wird die Konvertierung und Validierung des Werts vorgezogen und der Value-Change-Listener wird vor der Konvertierung und Validierung der anderen Eingabekomponenten aufgerufen. Im Value-Change-Listener ist der Aufruf der Methode FacesContext.getCurrentInstance().renderResponse() notwendig, um die Konvertierung und die Validierung der anderen Komponenten zu überspringen. Listing Value-Change-Listener in der Bean zeigt den Code der Methode.
public void useCreditCardChanged(ValueChangeEvent ev) {
  Boolean useCreditCard = (Boolean) ev.getNewValue();
  if (useCreditCard != null) {
    this.useCreditCard = useCreditCard;
  }
  FacesContext.getCurrentInstance().renderResponse();
}
Damit funktioniert die Umschaltung ohne die aus der Konvertierung oder Validierung anderer Komponenten resultierenden Fehlermeldungen.
Abbildung Lebenszyklus für immediate-Eingabekomponenten zeigt den Ablauf des Lebenszyklus für unser Beispiel. Durch den Aufruf von renderResponse() im Value-Change-Listener der Komponente wird die Ausführung nach der Apply-Request-Values-Phase direkt bei der Render-Response-Phase fortgesetzt. Alle dazwischenliegenden Phasen werden übersprungen.
Abbildung:Lebenszyklus für immediate-Eingabekomponenten
Eine detailliertere Beschreibung der Funktionsweise von Value-Change-Ereignissen und deren Behandlung beim Ablauf des Lebenszyklus findet sich in Abschnitt [Sektion:  Value-Change-Events] und in Beispiel MyGourmet 3 in Abschnitt [Sektion:  MyGourmet 3: Ereignisse] .

2.6.1.2 immediate-Attribut für Befehlskomponenten

Für Befehlskomponenten bewirkt das Setzen des immediate -Attributs auf true ebenfalls eine vorgezogene Behandlung der Komponente beim Ablauf des Lebenszyklus. Bei der Komponente registrierte Aktionen und Action-Listener werden dann bereits am Ende der Apply-Request-Values-Phase und nicht mehr nach der Invoke-Application-Phase ausgeführt. Die Validierung und das Update des Modells werden dabei übersprungen. Der Grund dafür ist schnell erklärt: Durch die Bearbeitung der Befehlskomponente wird die Navigation angestoßen und der darauf folgende Schritt im Lebenslauf ist immer die Render-Response-Phase.
Ein Beispiel für den Einsatz von immediate -Befehlskomponenten ist eine Abbrechen-Schaltfläche für Formulare. Das Setzen des Attributs immediate auf true verhindert in diesem Fall, dass der Benutzer alle verpflichtenden Felder eingeben muss, um die Bearbeitung des Formulars überhaupt abbrechen zu können. Mit einer normalen Befehlskomponente sieht der Lebenszyklus bei fehlenden verpflichtenden Eingaben wie in Abbildung Lebenszyklus für fehlgeschlagene Validierung aus und die relevante Invoke-Application-Phase wird gar nicht mehr erreicht. Hier die zentrale Codezeile:
<h:commandButton action="/cancelled.xhtml"
    value="Cancel" immediate="true"/>
Wie man sieht, genügt die Attributdefinition immediate=true auf der Komponente, um die gewünschte Funktionalität zu erzielen. Die Codezeile entstammt dem Beispiel MyGourmet 2 , das in Abschnitt [Sektion:  MyGourmet 2: immediate-Attribute] näher beschrieben wird. Abbildung Lebenszyklus für immediate-Befehlskomponenten zeigt den Ablauf des Lebenszyklus für unser Beispiel.
Abbildung:Lebenszyklus für immediate-Befehlskomponenten
Die Bearbeitung der immediate -Schaltfläche löst bereits in der Apply-Request-Values-Phase die Navigation aus und die Ausführung wird direkt mit der Render-Response-Phase fortgesetzt. Alle dazwischenliegenden Phasen werden übersprungen.
Details zu Action-Ereignissen und deren Behandlung beim Ablauf des Lebenszyklus finden Sie in Abschnitt [Sektion:  Action-Events] .

2.6.2 MyGourmet 2: immediate-Attribute

MyGourmet 2 ist eine kleine Erweiterung des Beispiels MyGourmet 1 aus Abschnitt Sektion:  MyGourmet 1: Einführung anhand eines Beispiels . Die Eingabe des Vor- und Nachnamens ist von nun an verpflichtend und das Eingabeformular für die Kundendaten erhält eine zusätzliche Schaltfläche zum Abbrechen der Bearbeitung. Der Zweck der Übung ist die Demonstration des Zusammenhangs zwischen verpflichtenden Eingabekomponenten, deren Validierung und immediate -Befehlskomponenten. Nur wenn bei der Befehlskomponente immediate auf true gesetzt ist, kann die Bearbeitung des Formulars auch im Fall leerer verpflichtender Eingabefelder abgebrochen werden. Andernfalls wäre bei der Ausführung des Lebenszyklus bereits bei der Process-Validations-Phase Schluss und der Benutzer sieht statt einer neuen Seite erneut das Formular - diesmal allerdings mit Fehlermeldungen, wenn die Seite h:messages - oder h:message -Komponenten enthält. Ab JSF 2.0 wird automatisch eine h:messages -Komponente hinzugefügt, wenn die Project-Stage auf Development gesetzt ist (siehe Abschnitt Sektion:  Project-Stage ).
Die notwendigen Änderungen an editCustomer.xhtml sind minimal. Bei den beiden h:inputText -Komponenten wird das Attribut required auf true gesetzt und die neue Schaltfläche wird eingefügt. Das Attribut action der neuen Befehlskomponente referenziert keine Ereignisbehandlungsmethode, sondern enthält mit der Zeichenkette cancelled.xhtml direkt die View-ID der anzuzeigenden Ansicht. Der komplette Sourcecode der modifizierten Seite ist in Listing MyGourmet 2: Die Seite editCustomer.xhtml dargestellt.
<!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:h="http://xmlns.jcp.org/jsf/html">
<head>
  <title>MyGourmet - Edit Customer</title>
</head>
<body>
  <h1><h:outputText value="MyGourmet"/></h1>
  <h2><h:outputText value="Edit Customer"/></h2>
  <h:messages showDetail="true" showSummary="false"/>
  <h:form id="form">
    <h:panelGrid id="grid" columns="2">
      <h:outputLabel value="First Name:" for="firstName"/>
      <h:inputText id="firstName" required="true"
          value="#{customer.firstName}"/>
      <h:outputLabel value="Last Name:" for="lastName"/>
      <h:inputText id="lastName" required="true"
          value="#{customer.lastName}"/>
    </h:panelGrid>
    <h:commandButton id="save" value="Save"
        action="#{customer.save}"/>
    <h:commandButton id="cancel" value="Cancel"
        immediate="true" action="/cancelled.xhtml"/>
  </h:form>
</body>
</html>
An der zweiten Seite showCustomer.xhtml zum Anzeigen der gespeicherten Kundendaten hat sich nichts geändert. Neu ist allerdings die Seite cancelled.xhtml - sie wird dargestellt, wenn der Benutzer die Bearbeitung der Daten abbricht. Auf dieser Seite passiert außer der Ausgabe einer Nachricht nichts Aufregendes, weshalb wir auf eine eigene Abbildung verzichten.

2.7 Navigation

Ein wichtiger Teil jeder JSF-Applikation ist die Definition der Navigation zwischen den einzelnen Ansichten. Damit der Benutzer im Browser überhaupt von einer Ansicht der Anwendung zu einer anderen wechseln kann, muss die Seitendeklaration eine Befehlskomponente enthalten. Darunter versteht man eine Komponente, die das Absenden der aktuellen Seite an den Server veranlässt und somit die Abarbeitung des Lebenszyklus am Server anstößt. Von diesen Befehlskomponenten gibt es in JSF zwei: h:commandButton und h:commandLink . Wie der Name schon verrät, werden sie als Schaltfläche beziehungsweise Link in HTML ausgegeben.
Der ausschlaggebende Faktor für den Einsatz der Navigation ist das Attribut action der Befehlskomponenten. Darüber wird am Ende der Invoke-Application-Phase entschieden, welche Ansicht von JSF gerendert und zum Benutzer zurückgesendet wird.
Ab JSF 2.0 kann direkt die View-ID einer Ansicht im action -Attribut angegeben oder von der Action-Methode zurückgegeben werden - wir bezeichnen das als implizite Navigation. Sehen wir uns die Navigation in MyGourmet dahingehend noch einmal genauer an. Listing Befehlskomponenten aus MyGourmet zeigt die beiden Befehlskomponenten in der Ansicht editCustomer.xhtml . Die erste Schaltfläche löst eine Navigation zur Ansicht showCustomer.xhtml aus, da die Action-Methode save diese View-ID zurückgibt. Beim Betätigen der zweiten Schaltfläche landet der Benutzer auf der im Attribut action angegebenen Ansicht cancelled.xhtml .
<h:commandButton id="save" value="Save"
    action="#{customer.save}"/>
<h:commandButton id="cancel" value="Cancel"
    action="/cancelled.xhtml" immediate="true"/>
Die klassische Variante der Navigation beruht auf Navigationsregeln. Sie legen fest, wann welche Seite der Anwendung aufgerufen wird. JSF erlaubt die Definition einer beliebigen Anzahl von Navigationsregeln in der faces-config.xml . In einer Regel steht vor der Auflistung der einzelnen Navigationsfälle das Element from-view-id . Dieses Element bestimmt, von welcher Seite aus die folgenden Navigationsfälle gültig sind.
Listing Navigationsregel für MyGourmet zeigt eine Navigationsregel für die Seitendeklaration mit der View-ID editCustomer.xhtml , die der aktuell mit impliziter Navigation umgesetzten Navigation in MyGourmet entspricht. Die einzelnen Navigationsfälle können nur aktiv werden, wenn sich der Benutzer auf dieser Ansicht befindet.
<navigation-rule>
  <from-view-id>/editCustomer.xhtml</from-view-id>
  <navigation-case>
    <from-outcome>ok</from-outcome>
    <to-view-id>/showCustomer.xhtml</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>cancel</from-outcome>
    <to-view-id>/cancelled.xhtml</to-view-id>
  </navigation-case>
</navigation-rule>
Im Gegensatz zum vorangegangenen Beispiel ist die Navigationsregel in Listing Globale Navigationsregel global für alle Ansichten der Anwendung gültig. Das Element from-view-id enthält in solchen Fällen einen Ausdruck, um die zutreffenden View-IDs zu definieren. Im einfachsten Fall schließt der Wert * alle Ansichten mit ein. Genauso gut ist es aber beispielsweise möglich, die Regel mit /secure/* auf ein Verzeichnis innerhalb der Anwendung einzuschränken.
<navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
    <from-outcome>logout</from-outcome>
    <to-view-id>/home.xhtml</to-view-id>
  </navigation-case>
</navigation-rule>
Im Detailbereich der Navigationsregeln folgen dann ein oder mehrere Navigationsfälle. Diese Navigationsfälle definieren über Filter- und Zielelemente die Seitenübergänge. Die Zielseite ist über das Element definiert. Die Filterelemente sind from-outcome und das seltener verwendete from-action .
Die Zeichenkette im from-outcome -Element der Navigationsregel muss dann dem Rückgabewert der Action-Methode entsprechen. Als Alternative kann direkt eine Zeichenkette im action -Attribut stehen, die dann dem Element from-outcome entsprechen muss.
Gibt es mehrere gleiche Rückgabewerte kann der Navigationsfall mit dem Element from-action noch weiter auf den Aufruf einer bestimmten Action-Methode eingeschränkt werden. Listing Navigationsfall mit from-action zeigt einen entsprechenden Navigationsfall.
<navigation-case>
  <from-action>#{customer.save}</from-action>
  <from-outcome>ok</from-outcome>
  <to-view-id>/showCustomer.xhtml</to-view-id>
</navigation-case>
Einen Fall haben wir noch nicht berücksichtigt. Was passiert, wenn die Action-Methode null zurückliefert? Die Antwort darauf ist einfach (zumindest vor JSF 2.0): Die aktuelle Seite wird nochmals angezeigt. Ab JSF 2.0 kann dieses Verhalten durch die bedingte Navigation beeinflusst werden, doch dazu später mehr.
In manchen Fällen ist es erforderlich, die Navigation explizit über den Navigation-Handler von JSF anzustoßen, um etwa aus einem Action-Listener heraus auf eine andere Seite zu navigieren. In direkter Form ist das nicht möglich, da der Action-Listener keine Zeichenkette zurückliefert. Dazu muss zuerst eine globale Navigationsregel in der faces-config.xml definiert werden, bei der from-outcome auf eine Zeichenkette und to-view-id auf die gewünschte Zielseite gesetzt sind. Listing Programmatische Navigation zeigt den Aufruf, der die Navigation auf die Zielseite bewirkt. Ab JSF 2.0 ist es natürlich auch möglich, statt der globalen Navigationsregel direkt eine View-ID anzugeben.
FacesContext ctx = FacesContext.getCurrentInstance();
fc.getApplication().getNavigationHandler()
    .handleNavigation(ctx, null, "cancel");
Seit Version 2.0 bietet JSF zusätzlich die bedingte Navigation. Mit dem Element if , das eine Value-Expression aufnimmt, die zu einem booleschen Wert evaluieren muss, können Navigationsfälle abhängig vom Ergebnis dieses Ausdrucks gemacht werden. Das Element if kann in Kombination mit from-outcome oder from-action oder als einzige Bedingung für einen Navigationsfall eingesetzt werden. So oder so müssen alle angegebenen Bedingungen erfüllt sein, damit der Navigationsfall eintreten kann.
Listing Bedingte Navigation im Einsatz zeigt eine Version der bereits bekannten Navigationsregel aus MyGourmet mit bedingter Navigation. Die Navigation nach Betätigen der Abbrechen-Schaltfläche ist in dem Beispiel von der Eigenschaft customer.registered abhängig. Ist der Benutzer bereits registriert und ändert er seine Daten nur, kommt er beim Betätigen der Schaltfläche auf die Übersichtsseite /showCustomer.xhtml zurück. Bricht er ab, bevor die Registrierung abgeschlossen ist, gelangt er auf die Ansicht /cancelled.xhtml .
<navigation-rule>
  <from-view-id>/editCustomer.xhtml</from-view-id>
  <navigation-case>
    <from-outcome>ok</from-outcome>
    <to-view-id>/showCustomer.xhtml</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>cancel</from-outcome>
    <if>#{customer.registered}</if>
    <to-view-id>/showCustomer.xhtml</to-view-id>
  </navigation-case>
  <navigation-case>
    <from-outcome>cancel</from-outcome>
    <if>#{not customer.registered}</if>
    <to-view-id>/cancelled.xhtml</to-view-id>
  </navigation-case>
</navigation-rule>
Die Einführung der bedingten Navigation beeinflusst auch das Verhalten der Navigation, wenn die Action-Methode null zurückliefert. In JSF-Versionen vor 2.0 wurde dadurch immer die aktuelle Seite nochmals angezeigt. Ab Version 2.0 prüft JSF in diesem Fall auch alle Navigationsfälle, die ein if -Element ohne from-outcome oder from-action haben.

2.8 Ereignisse und Ereignisbehandlung

Ein essenzieller Bestandteil jeder Webapplikation ist die Behandlung von Eingaben des Benutzers am Client. Wenn der Benutzer zum Beispiel einen Button anklickt oder einen Eintrag aus einer Liste auswählt, muss eine entsprechende Aktion ausgeführt werden. In JavaServer Faces kommen dazu Ereignisse (Events) zum Einsatz. Diese Ereignisse werden dann von Event-Listenern behandelt, die vorher bei einer Komponente für einen speziellen Ereignistyp registriert wurden. Als Beispiel dient die Schaltfläche zum Speichern aus MyGourmet 2 in Listing MyGourmet 2: Die Seite editCustomer.xhtml .
Für diese Button-Komponente wird über die Method-Expression im Attribut action eine Ereignisbehandlungsmethode registriert. Klickt der Benutzer im Browser auf den Schalter, wird beim Abarbeiten des Lebenszyklus ein Event erzeugt und die registrierte Methode wird aufgerufen.
Die Behandlung der Events und der Aufruf der Event-Listener wird in JSF serverseitig durchgeführt. Beim Durchlaufen des Lebenszyklus können ab der Apply-Request-Values-Phase beim Abarbeiten der Phasen Events generiert und in eine Event-Queue eingefügt werden. Wenn die Phase fertig bearbeitet ist, werden für die Events in der Queue die registrierten Event-Listener aufgerufen. Das darf nicht früher geschehen, damit jeder Event-Listener denselben Status des Komponentenbaums sieht. Abbildung Der Lebenszyklus ("`Lifecycle"') einer HTTP-Anfrage bietet einen Überblick über den Lebenszyklus und die möglichen Stellen zur Behandlung von Ereignissen. Aus der Abbildung ist ersichtlich, dass es beim Abarbeiten der Events die Möglichkeit zur Beeinflussung des Lebenszyklus gibt. Event-Listener können mit der Methode FacesContext.renderResponse() direkt zur Render-Response-Phase springen oder mit der Methode FacesContext.responseComplete() die Ausführung des Lebenszyklus komplett abbrechen. Wie das konkret in einem Beispiel aussieht, zeigt MyGourmet 3 in Abschnitt [Sektion:  MyGourmet 3: Ereignisse] .
JSF definiert mehrere Arten von Events (und dazu passende Event-Listener), die durch Benutzeraktionen oder vom System ausgelöst werden. Folgende Events werden durch Benutzeraktionen ausgelöst:
Folgende Events werden vom System ausgelöst:
Eine detailliertere Beschreibung der einzelnen Ereignistypen folgt in den nächsten Abschnitten.

2.8.1 Value-Change-Events

Value-Change-Events werden von Eingabekomponenten ausgelöst, deren Werte sich beim Senden des Formulars geändert haben. Wählt der Benutzer zum Beispiel die Checkbox "Kreditkarte benutzen" aus, und sie war vorher nicht angewählt, wird beim Abarbeiten des Lebenszyklus nach dem Senden der Seite ein Value-Change-Event gefeuert und registrierte Event-Listener werden aufgerufen. Bleibt der Wert gleich, wird kein Event ausgelöst.
Event-Listener für Value-Change-Events können auf zwei Arten auf Komponenten registriert werden: über eine Method-Expression im Attribut valueChangeListener der Komponente oder mit dem Kindelement f:valueChangeListener . Sehen wir uns zuerst in Listing Registrieren einer Ereignisbehandlungsmethode an, wie das Registrieren eines Value-Change-Listeners über eine Method-Expression im Sourcecode aussieht.
<h:selectBooleanCheckbox onclick="this.form.submit()"
    value="#{customer.useCreditCard}" immediate="true"
    valueChangeListener="#{customer.useCreditCardChanged}"/>
Listing Code der Ereignisbehandlungsmethode zeigt die zugehörige Ereignisbehandlungsmethode in der Managed-Bean. Die Signatur einer solchen Methode muss den Rückgabewert void haben und einen Parameter vom Typ ValueChangeEvent aufnehmen. Der Name spielt keine Rolle.
public void useCreditCardChanged(ValueChangeEvent e) {
  Boolean useCreditCard = (Boolean) e.getNewValue();
  if (useCreditCard != null) {
    this.useCreditCard = useCreditCard;
  }
  FacesContext.getCurrentInstance().renderResponse();
}
Sehen wir uns nun in Listing Registrieren einer Value-Change-Listener-Klasse an, wie das Registrieren eines Value-Change-Listeners über ein Kindelement der Komponente im Sourcecode aussieht.
<h:selectBooleanCheckbox onclick="this.form.submit()"
    value="#{customer.useCreditCard}" immediate="true">
  <f:valueChangeListener
      type="at.irian.CreditCardChangeListener"/>
</h:selectBooleanCheckbox>
Der Unterschied ist offensichtlich: In den Kindelementen wird statt einer Method-Expression der Name einer Klasse verwendet, die das Interface javax.faces.event.ValueChangeListener implementiert. Diese Form der Registrierung bietet den Vorteil, dass mehrere Listener für eine Komponente registriert werden können. Die Reihenfolge der Aufrufe entspricht dabei der Reihenfolge der Kindelemente. Listing Code des Value-Change-Listeners zeigt den Java-Code des Event-Listeners.
public class CreditCardChangeListener
    implements ValueChangeListener {
  public void processValueChange(ValueChangeEvent e) {
    Boolean useCreditCard = (Boolean) e.getNewValue();
    FacesContext fc = FacesContext.getCurrentInstance();
    if (useCreditCard != null) {
      ELContext el = fc.getELContext();
      Customer customer = (Customer) el.getELResolver()
          .getValue(el, null, "customer");
      customer.setUseCreditCard(useCreditCard);
    }
    fc.renderResponse();
  }
}
Auf den ersten Blick sieht dieser Code komplizierter aus, was allerdings täuscht. Wenn der Value-Change-Listener als eigene Klasse implementiert ist, können wir nicht mehr direkt auf die Eigenschaften der Managed-Bean zugreifen. Das ist aber kein Beinbruch, da wir mithilfe des ELResolvers Managed-Beans auflösen können. In unserem Fall wird zuerst die Bean customer geholt und dann der Wert ihrer Eigenschaft useCreditCard auf den Wert aus dem Value-Change-Event gesetzt.
In beiden Varianten bekommt der Event-Listener das Ereignis als Instanz der Klasse javax.faces.event.ValueChangeEvent übergeben. Diese Klasse bietet folgende für die Bearbeitung des Ereignisses relevante Methoden:
Stellt sich noch die Frage, wie JSF Value-Change-Events intern bearbeitet. Das Ereignis wird nur ausgelöst, wenn sich der neue Wert nach der Validierung tatsächlich vom alten Wert in der Managed-Bean unterscheidet. Value-Change-Events werden im Normalfall am Ende der Process-Validations-Phase ausgelöst, nachdem die Werte aller Eingabekomponenten erfolgreich konvertiert und validiert wurden. Ist das immediate -Attribut der Komponente true , wird das Value-Change-Event bereits am Ende der Apply-Request-Values-Phase gefeuert.

2.8.2 Action-Events

Action-Events werden von Befehlskomponenten ausgelöst, wenn der Benutzer sie am Client betätigt. Im JSF-Standard sind zwei Befehlskomponenten vorgesehen: h:commandButton und h:commandLink , doch dazu mehr in Abschnitt Sektion:  Befehlskomponenten . Wichtig ist hier, dass die Betätigung ein Senden der aktuellen Seite bewirkt. Beim dadurch angestoßenen Ablauf des Lebenszyklus wird dann ein Action-Event für die auslösende Komponente gefeuert.
Bei der Abarbeitung von Action-Events muss man zwischen Actions und Action-Listenern unterscheiden. Im Prinzip werden beide eingesetzt, um Action-Events zu bearbeiten, die von Komponenten ausgelöst wurden. Dabei gilt, dass Action-Listener immer kurz vor Actions aufgerufen werden.
Actions sind der ideale Ort für Aufrufe der Geschäftslogik, wohingegen Action-Listener eher für UI-zentrierte Logik vorgesehen sind. Diese Trennung macht durchaus Sinn, um die Businesslogik und die UI-Logik getrennt zu halten. Ein Aspekt, der besonders bei komplexeren Projekten die Wartbarkeit spürbar erhöht. Wie schon bei den Value-Change-Events gibt es auch für Action-Listener zwei Methoden der Registrierung bei Komponenten: Entweder über eine Method-Expression im Attribut actionListener der Komponente oder mit dem Kindelement f:actionListener .
In beiden Varianten bekommt die Ereignisbehandlungsmethode das ausgelöste Ereignis als Instanz der Klasse ActionEvent übergeben. Für die Bearbeitung des Ereignisses ist besonders die Methode UIComponent getComponent() interessant. Sie liefert als Rückgabewert die das Ereignis auslösende Komponente zurück.
Werfen wir auch bei den Action-Events einen kurzen Blick hinter die Kulissen der Bearbeitung durch JSF. Die Bearbeitung beginnt bereits in der Apply-Request-Values-Phase beim Decodieren des Requests. Hat der Benutzer eine Befehlskomponente betätigt, wird ein entsprechendes Event erzeugt und in die Event-Queue eingefügt. Handelt es sich um eine immediate -Befehlskomponente, werden die registrierten Event-Listener am Ende der Apply-Request-Values-Phase und ansonsten am Ende der Invoke-Application-Phase ausgeführt.

2.8.3 MyGourmet 3: Ereignisse

MyGourmet 3 erweitert das Beispiel um einige Angaben zur Kreditkarte des Kunden, genauer gesagt um den Kartentyp und die Nummer der Karte. Der Benutzer kann auf der Seite editCustomer.xhtml über eine h:selectBooleanCheckbox -Komponente auswählen, ob er die Kreditkarte als Zahlungsmittel verwenden will oder nicht. Abhängig von der dortigen Auswahl sind die entsprechenden Eingabefelder auf der Seite ein- beziehungsweise ausgeblendet. Diese Umschaltung erfolgt mittels eines Value-Change-Listeners, der beim Auswahlfeld registriert ist und die Eigenschaft useCreditCard der Managed-Bean customer setzt. Genau diese Eigenschaft wird auch in einer Value-Expression im rendered -Attribut der Eingabefelder referenziert, um diese ein- und auszublenden.
Sehen wir uns diesen Ablauf kurz in der Praxis an, bevor wir uns näher mit dem Sourcecode beschäftigen. Wir nehmen an, dass beim ersten Aufruf der Seite editCustomer.xhtml die Eigenschaft useCreditCard der Managed-Bean den Wert false hat. Die Anwendung präsentiert sich dem Benutzer dann wie in Abbildung MyGourmet 3: Kunde bearbeiten ohne Kreditkartendaten gezeigt. In diesem Fall ist die h:selectBooleanCheckbox -Komponente nicht aktiviert und die h:inputText -Komponenten für Kartentyp und Kartennummer werden nicht gerendert - so weit, so gut.
Abbildung:MyGourmet 3: Kunde bearbeiten ohne Kreditkartendaten
Klickt der Benutzer jetzt auf das Auswahlfeld, erfolgt ein Senden des Formulars durch den JavaScript-Code im onclick -Attribut und der Lebenszyklus wird am Server gestartet. Dabei kommt der Value-Change-Listener ins Spiel, indem er die Eigenschaft auf den neuen Wert setzt. Als Ergebnis wird die Seite mit aktiviertem Auswahlfeld und den beiden Eingabefeldern am Client angezeigt. Die Anwendung sieht dann im Browser wie in Abbildung MyGourmet 3: Kunde bearbeiten mit Kreditkartendaten aus. Der umgekehrte Weg ist genauso möglich, auch wenn der Benutzer noch nicht alle verpflichtenden Felder ausgefüllt hat, da das Auswahlfeld als immediate -Komponente realisiert ist.
Abbildung:MyGourmet 3: Kunde bearbeiten mit Kreditkartendaten
Eine weitere Neuerung auf der Seite ist die Möglichkeit zum Export der Daten. Ein Klick auf die Schaltfläche macht nichts anderes, als die vom Benutzer eingegebenen Daten als reinen Text in einer eigenen Seite auszugeben. Dazu wird in der entsprechenden Action nach der Ausgabe die Ausführung des Lebenszyklus mit der Methode FacesContext.responseComplete() abgebrochen. Doch dazu kommen wir etwas weiter unten.
Listing MyGourmet 3: Änderungen in editCustomer.xhtml zeigt die zusätzlichen Eingabekomponenten für die Kreditkartendaten in der Seitendeklaration editCustomer.xhtml .
<h:outputLabel value="Use Credit Card:" for="useCreditCard"/>
<h:selectBooleanCheckbox id="useCreditCard"
    value="#{customer.useCreditCard}"
    valueChangeListener="#{customer.useCreditCardChanged}"
    immediate="true" onclick="this.form.submit()"/>
<h:outputLabel value="Credit Card Type:" for="ccType"
    rendered="#{customer.useCreditCard}"/>
<h:inputText rendered="#{customer.useCreditCard}"
    id="ccType" value="#{customer.creditCardType}"
    required="#{customer.useCreditCard}"/>
<h:outputLabel rendered="#{customer.useCreditCard}"
    value="Credit Card Number:" for="ccNumber"/>
<h:inputText id="ccNumber"
    value="#{customer.creditCardNumber}"
    rendered="#{customer.useCreditCard}"
    required="#{customer.useCreditCard}"/>
Die Seite showCustomer.xhtml zeigt alle vom Benutzer eingegebenen Daten an. Bei den Kreditkartendaten wird wie bei der Dateneingabe das Attribut rendered verwendet, um sie nach Bedarf ein- oder auszublenden. Listing MyGourmet 3: Änderungen in showCustomer.xhtml zeigt die Änderungen zum Darstellen der Kreditkartendaten in showCustomer.xhtml bezüglich MyGourmet 2 .
<h:outputText value="Credit Card Type:"
    rendered="#{customer.useCreditCard}"/>
<h:outputText value="#{customer.creditCardType}"
    rendered="#{customer.useCreditCard}"/>
<h:outputText value="Credit Card Number:"
    rendered="#{customer.useCreditCard}"/>
<h:outputText value="#{customer.creditCardNumber}"
    rendered="#{customer.useCreditCard}"/>
Die Klasse Customer hat neben den Getter- und Setter-Methoden der neuen Eigenschaften zwei zusätzliche Methoden zur Bearbeitung von Ereignissen erhalten.
Die Ereignisbehandlungsmethode useCreditCardChanged behandelt das Value-Change-Event der Auswahlkomponente. Zum Ein- und Ausblenden der Eingabefelder für die Kreditkartendaten wird der neue Wert der Komponente aus dem Ereignis ausgelesen und in die Eigenschaft useCreditCard gesetzt. Ein darauffolgender Aufruf von FacesContext.renderResponse() erzwingt anschließend eine sofortige Ausgabe der Seite. Die Methode ist in Listing MyGourmet 3: Value-Change-Listener ersichtlich.
public void useCreditCardChanged(ValueChangeEvent e) {
  Boolean useCreditCard = (Boolean) e.getNewValue();
  if (useCreditCard != null) {
    this.useCreditCard = useCreditCard;
  }
  FacesContext.getCurrentInstance().renderResponse();
}
Die Methode export führt den Export der Daten des Kunden durch. Zur Ausgabe wird direkt in die HttpServletResponse geschrieben, auf die man in JSF für Servlet-basierte Anwendungen wie folgt zugreifen kann:
(HttpServletResponse)FacesContext.getCurrentInstance().
    getExternalContext().getResponse();
Nach der Ausgabe der Daten wird der Ablauf des Lebenszyklus mit einem Aufruf von FacesContext.responseComplete() komplett abgebrochen. Die Methode export ist in Listing MyGourmet 3: Ereignisbehandlungsmethode export zu finden.
public String export() {
  FacesContext fc = FacesContext.getCurrentInstance();
  try {
    HttpServletResponse resp = (HttpServletResponse)
        fc.getExternalContext().getResponse();
    resp.setContentType("text/plain");
    PrintWriter writer = resp.getWriter();
    writer.print("First Name: ");
    writer.println(firstName);
    writer.print("Last Name: ");
    writer.println(lastName);
    if (useCreditCard) {
      writer.print("Credit Card Type: ");
      writer.println(creditCardType);
      writer.print("Credit Card Number: ");
      writer.println(creditCardNumber);
    }
    fc.responseComplete();
  } catch (IOException e) {e.printStackTrace();}
  return null;
}
An der Seitendeklaration cancelled.xhtml und der Konfigurationsdatei faces-config.xml hat sich gegenüber dem vorangegangenen Beispiel nichts verändert.

2.8.4 System-Events

System-Events bilden ab JSF 2.0 einen neuen Typ von Ereignissen, die zu bestimmten Zeitpunkten in der JSF-Applikation oder im Lebenszyklus ausgelöst werden. JSF definiert eine ganze Reihe dieser Ereignisse und bietet die Möglichkeit, bei Bedarf dafür Listener zu registrieren. Grundsätzlich unterscheidet JSF zwischen System-Events, die beim Ausführen des Lebenszyklus für eine spezifische Komponenteninstanz ausgelöst werden, und System-Events, die beim Ausführen der Applikation unabhängig von einer Komponenteninstanz ausgelöst werden.
Folgende System-Events werden für eine bestimmte Komponenteninstanz ausgelöst:
Folgende System-Events werden von JSF unabhängig von einer Komponenteninstanz ausgelöst:
Das Registrieren von Ereignisbehandlungsmethoden für System-Events auf Komponenten erfolgt deklarativ mit dem Tag f:event aus der Core-Tag-Library. f:event wird dazu einfach als Kind-Tag in das entsprechende Komponenten-Tag eingebunden. Im Attribut type wird dabei der voll qualifizierte Klassenname der Event-Klasse angegeben und im Attribut listener eine Method-Expression für die Listener-Methode. Listing f:event im Einsatz zeigt, wie ein Listener für das Ereignis PostValidateEvent auf einer h:inputText -Komponente registriert wird.
<h:inputText value="#{bean.name}">
  <f:event type="javax.faces.event.PostValidateEvent"
      listener="#{customerBean.postValidateName}"/>
</h:inputText>
Für häufig verwendete System-Events gibt es Kurznamen, die im Attribut type statt des Klassennamens verwendet werden können. Tabelle tab:sysevent-short zeigt eine Übersicht der verfügbaren Kurznamen.
KurznameEvent-Klasse
preRenderComponent javax.faces.event.PreRenderComponentEvent
preRenderView javax.faces.event.PreRenderViewEvent
postAddToView javax.faces.event.PostAddToViewEvent
preValidate javax.faces.event.PreValidateEvent
postValidate javax.faces.event.PostValidateEvent
Listing Listener-Methode für PostValidateEvent zeigt die in Listing f:event im Einsatz referenzierte Ereignisbehandlungsmethode für das System-Event PostValidateEvent . Listener-Methoden für System-Events, die einer Komponenteninstanz zugeordnet sind, müssen immer einen Parameter vom Typ Component-System-Event aufweisen. Im Beispiel wird aus der übergebenen Event-Instanz mit der Methode getComponent() die auslösende Komponente ermittelt und in der Bean abgelegt.
public void postValidateName(ComponentSystemEvent ev) {
  inputComponent = ev.getComponent();
}
Listener für System-Events, die keiner Komponenteninstanz zugeordnet sind (wie etwa PostConstructApplicationEvent ), lassen sich nicht über f:event registrieren. Solche Listener müssen als eigene Klassen umgesetzt werden, die das Interface SystemEventListener implementieren. Die Registrierung der Listener-Klasse erfolgt dann in der faces-config.xml . Dazu wird im Element application für jede Kombination von Event-Klasse und Listener-Klasse ein system-event-listener -Element hinzugefügt. Listing System-Event-Listener in der faces-config.xml zeigt, wie das zum Beispiel für die Listener-Klasse at.irian.jsfatwork.MyListener und das System-Event Post-Construct-ApplicationEvent aussieht.
<faces-config>
  <application>
    <system-event-listener>
      <system-event-listener-class>
        at.irian.jsfatwork.MyListener
      </system-event-listener-class>
      <system-event-class>
        javax.faces.event.PostConstructApplicationEvent
      </system-event-class>
    </system-event-listener>
  </application>
</faces-config>
In Listing System-Event-Listener als Klasse finden Sie die Listener-Klasse MyListener . Das Interface SystemEventListener definiert zwei zu implementierende Methoden: processEvent wird immer dann aufgerufen, wenn das in der faces-config.xml angegebene Ereignis ausgelöst wird und ein Aufruf von isListenerForSource den Wert true zurückliefert. Mit isListenerForSource kann eine Listener-Klasse die zu bearbeitenden Ereignisse anhand des auslösenden Objekts einschränken. In unserem Listener ist das zum Beispiel die JSF-Klasse Application .
public class MyListener implements SystemEventListener {
  public void processEvent(SystemEvent event) {
    // Ereignis behandeln
  }
  public boolean isListenerForSource(Object source) {
    return source instanceof Application;
  }
}
Weitere Beispiele zur Verwendung von System-Events finden Sie in MyGourmet 6 (siehe Abschnitt [Sektion:  MyGourmet 6: Validierung] ), in Abschnitt Sektion:  View-Actions und in Abschnitt Sektion:  Komponentenklasse schreiben .

2.8.5 Phase-Events

Phase-Events werden routinemäßig bei der Ausführung des Lebenszyklus vor und nach jeder einzelnen Phase ausgelöst. Wie schon bei den bisher besprochenen Ereignistypen können auch für Phase-Events Listener registriert werden, wobei der Fokus allerdings ein etwas anderer ist. Da Phase-Events von JSF als Teil der Ausführung des Lebenszyklus ausgelöst werden, liegt ihr Haupteinsatzgebiet bei JSF-naher Funktionalität oder beim Debugging.
Für die Behandlung von Value-Change-Events und Action-Events werden Listener für Ereignisse bei einzelnen Komponenten registriert. Die Einbindung von Listenern für Phase-Events unterscheidet sich davon etwas - zumindest vor JSF-Version 1.2. Sie werden direkt in der faces-config.xml für den Lebenszyklus registriert. Wie das beispielsweise mit der Klasse DebugPhaseListener aussieht, zeigt Listing Phase-Listener konfigurieren .
<lifecycle>
  <phase-listener>at.irian.DebugPhaseListener</phase-listener>
</lifecycle>
Wie sieht eine solche Klasse aus? Jeder Phase-Listener muss das Interface javax.faces.event.PhaseListener implementieren. Dieses Interface definiert drei Methoden:
Die Klasse at.irian.DebugPhaseListener dient als einfaches Beispiel, um die Einsatzmöglichkeiten von Phase-Listenern zu demonstrieren. Der Zweck des Beispiels ist, vor und nach jeder Phase eine Logmeldung auszugeben. Der Sourcecode der Klasse findet sich in Listing Ein Phase-Listener zum Debuggen .
public class DebugPhaseListener implements PhaseListener {
  static Log log = LogFactory.getLog(DebugPhaseListener.class);

  public void afterPhase(PhaseEvent event) {
    log.debug("After phase: " + event.getPhaseId());
  }
  public void beforePhase(PhaseEvent event) {
    log.debug("Before phase: " + event.getPhaseId());
  }
  public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
  }
}
Die Anzahl der Listener ist natürlich auch bei Phase-Events nicht begrenzt. Die Reihenfolge der Aufrufe bei der Ausführung des Lebenszyklus entspricht der Reihenfolge der Definition.
In JSF 1.2 wurde eine weitere Möglichkeit zum Registrieren von Phase-Listenern eingeführt. Mit dem Tag f:phaseListener kann für einzelne Seiten ein Phase-Listener eingebunden werden. Die Definition der zu verwendenden Klasse erfolgt analog zu den bereits vorgestellten Tags über das Attribut type . In unserem kleinen Beispiel sieht das dann so aus:
<f:view>
  <f:phaseListener type="at.irian.DebugPhaseListener"/>
Phase-Listener werden gerne von externen Frameworks und Bibliotheken für JSF verwendet, um die Funktionalität zu erweitern. Durch die fix definierten Aufrufe vor und nach einzelnen Phasen bieten sie ideale Einstiegspunkte in die Ausführung des Lebenslaufs. Besonders die Möglichkeit, vor den einzelnen Phasen Code auszuführen, erlaubt es Erweiterungen, eigenen Initialisierungscode einzubinden.

2.8.6 MyGourmet 4: Phase-Listener und System-Events

In MyGourmet 4 fügen wir keine neue Funktionalität zur Anwendung hinzu, sondern erweitern sie um zwei Phase-Listener und einen Listener für System-Events. Der erste Phase-Listener gibt Debuginformationen zu Beginn und am Ende jeder Phase aus und der zweite listet alle Request-Parameter auf. Anhand der Ausgaben dieser Ereignisbehandlungsmethoden werden wir weiter unten nochmals die Ausführung des Lebenszyklus durch JSF genauer unter die Lupe nehmen. Der System-Event-Listener gibt beim Starten und Beenden der Applikation jeweils eine Logmeldung aus.
Sehen wir uns zuerst in Listing MyGourmet 4: Phase-Listener zum Debuggen die Klasse DebugPhaseListener an. Wie jeder Phase-Listener implementiert auch sie das Interface PhaseListener . Die Methode getPhaseId() liefert PhaseId.ANY_PHASE zurück, wodurch die beiden Ereignisbehandlungsmethoden vor beziehungsweise nach jeder Phase aufgerufen werden. In beforePhase und afterPhase werden nur Logmeldungen ausgegeben, mit deren Hilfe wir etwas weiter unten den Ablauf des Lebenszyklus analysieren. Der Listener ist somit einsatzbereit und muss nur noch registriert werden.
public class DebugPhaseListener implements PhaseListener {
  static Log log = LogFactory.getLog(DebugPhaseListener.class);

  public void afterPhase(PhaseEvent event) {
    log.debug("After phase: " + event.getPhaseId());
  }
  public void beforePhase(PhaseEvent event) {
    log.debug("Before phase: " + event.getPhaseId());
  }
  public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
  }
}
Der zweite Listener ParameterPhaseListener gibt vor der Ausführung der Apply-Request-Phase alle Request-Parameter in Form von Logmeldungen aus. Seine Methode getPhaseId() muss daher den Wert Phase-Id.APPLY_REQUEST_VALUES zurückliefern. In beforePhase wird die Map mit allen Request-Parametern über folgenden Code ausgelesen:
FacesContext.getCurrentInstance().getExternalContext()
    .getRequestParameterMap();
Die Implementierung von afterPhase kann leer bleiben. Somit ist auch dieser Listener bereit für die Registrierung. Der Sourcecode von ParameterPhaseListener findet sich in Listing MyGourmet 4: Phase-Listener zum Ausgeben der Request-Parameter .
public class ParameterPhaseListener implements PhaseListener {
  static Log log = LogFactory.getLog(
      ParameterPhaseListener.class);

  public void beforePhase(PhaseEvent event) {
    FacesContext fc = FacesContext.getCurrentInstance();
    Map<String, String> map = fc.getExternalContext().
        getRequestParameterMap();
    for (String key : map.keySet()) {
      StringBuilder param = new StringBuilder();
      param.append("Parameter: ");
      param.append(key);
      param.append(" = ");
      param.append(map.get(key));
      log.debug(param.toString());
    }
  }
  public void afterPhase(PhaseEvent event) {
  }
  public PhaseId getPhaseId() {
    return PhaseId.APPLY_REQUEST_VALUES;
  }
}
Die Registrierung der beiden Phase-Listener erfolgt in der Datei faces-config.xml im WEB-INF -Verzeichnis der Applikation. Diese Datei ist der zentrale Punkt zur Konfiguration verschiedenster Aspekte einer JSF-Anwendung. Wir werden im Laufe des Buches noch auf diverse Einstellungen zu sprechen kommen. In der faces-config.xml fügen wir für jeden der Listener unter dem Element lifecycle ein Kindelement phase-listener mit dem Namen der Klasse hinzu. Hier können beliebig viele Listener registriert werden, wobei die Reihenfolge der Ausführung mit der Reihenfolge ihrer Definition übereinstimmt. Die faces-config.xml von MyGourmet 4 ist in Listing MyGourmet 4: faces-config.xmlmit Registrierung der Phase-Listener zu finden.
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
    version="2.2">
  <lifecycle>
    <phase-listener>
      at.irian.jsfatwork.gui.jsf.DebugPhaseListener
    </phase-listener>
    <phase-listener>
      at.irian.jsfatwork.gui.jsf.ParameterPhaseListener
    </phase-listener>
  </lifecycle>
</faces-config>

2.8.6.1 Analyse des Lebenszyklus

Nachdem die Funktionsweise der Phase-Listener und deren Registrierung jetzt klar sein sollte, folgt jetzt der wirklich interessante Aspekt von MyGourmet 4 . Mit den Ausgaben der Phase-Listener lässt sich der Ablauf des Lebenszyklus sehr schön nachvollziehen.
Beginnen wir im ersten Schritt beim initialen Aufruf der Seite edit-Customer.xhtml . Der Listener gibt während der Ausführung folgende Logmeldungen aus:
DEBUG Before phase: RESTORE_VIEW(1)
DEBUG After phase: RESTORE_VIEW(1)
DEBUG Before phase: RENDER_RESPONSE(6)
DEBUG After phase: RENDER_RESPONSE(6)
Die Logmeldungen bestätigen das erwartete Ergebnis für den erstmaligen Aufruf einer Seite. Nur die Phasen Restore-View zum Erstellen des Komponentenbaums und Render-Response zum Rendern der Seite werden durchlaufen. Der Benutzer sieht als Ergebnis die Seite zum Editieren der Kundendaten in seinem Browser.
Im nächsten Schritt gibt der Benutzer seinen Vor- und Nachnamen ein und klickt auf die Schaltfläche zum Abspeichern. Dadurch wird das Formular der Seite abgeschickt und am Server wird erneut der Lebenszyklus durchlaufen. Der Phase-Listener gibt diesmal folgende Logmeldungen aus:
DEBUG Before phase: RESTORE_VIEW(1)
DEBUG After phase: RESTORE_VIEW(1)
DEBUG Before phase: APPLY_REQUEST_VALUES(2)
DEBUG After phase: APPLY_REQUEST_VALUES(2)
DEBUG Before phase: PROCESS_VALIDATIONS(3)
DEBUG After phase: PROCESS_VALIDATIONS(3)
DEBUG Before phase: UPDATE_MODEL_VALUES(4)
DEBUG After phase: UPDATE_MODEL_VALUES(4)
DEBUG Before phase: INVOKE_APPLICATION(5)
DEBUG After phase: INVOKE_APPLICATION(5)
DEBUG Before phase: RENDER_RESPONSE(6)
DEBUG After phase: RENDER_RESPONSE(6)
Beim Senden der Seite werden alle sechs Phasen des Lebenszyklus durchlaufen. Die Liste der Request-Parameter sieht dabei laut Phase-Listener ParameterPhaseListener wie folgt aus:
DEBUG Parameter: javax.faces.ViewState = ...
DEBUG Parameter: form:lastName = Mustermann
DEBUG Parameter: form_SUBMIT = 1
DEBUG Parameter: form:save = Save
DEBUG Parameter: form:firstName = Max
Was passiert aber jetzt genau im Hintergrund? Nach der Wiederherstellung des Komponentenbaums in der Restore-View-Phase - dazu benutzt JSF den Parameter javax.faces.ViewState - werden in der Apply-Request-Values-Phase beim Decodieren die Submitted-Values der Komponenten gesetzt. Nachdem die wichtigsten Komponenten der Seite editCustomer.xhtml sprechende IDs aufweisen, ist die Aufschlüsselung der Parameter relativ einfach. Der Parameter form_SUBMIT sagt dem Formular, dass es übermittelt wurde, form:firstName ist der vom Benutzer eingegebene Vorname "Max" und form:lastName der Nachname "Mustermann". Durch den Parameter form:save weiß die entsprechende Befehlskomponente beim Decodieren, dass das Senden von ihr ausgelöst wurde und dass sie ein entsprechendes Action-Event in die Event-Queue einfügen muss.
Damit sind alle Submitted-Values in den Komponenten abgelegt und die weitere Verarbeitung kann bei der Konvertierung und Validierung fortgesetzt werden. Hier passiert nichts Besonderes, da Vor- und Nachname gültige Werte haben. Im nächsten Schritt - dem Modell-Update - werden die konvertierten und validierten Werte der Eingabekomponenten in das Modell zurückgeschrieben. In unserem Fall bedeutet das einen Aufruf der Methoden setFirstName und setLastName der Managed-Bean customer .
Damit sind wir auch schon bei der Invoke-Application-Phase angekommen. Dort wird als Reaktion auf das in der Apply-Request-Values-Phase erzeugte Ereignis die bei der Befehlskomponente registrierte Ereignisbehandlungsmethode aufgerufen. Für die Speichern-Schaltfläche ist das die Methode save der Managed-Bean customer . Das Ergebnis dieser Methode bestimmt die nächste anzuzeigende Seite. Bleibt als letzter Schritt nur noch das Rendern dieser Seite - in unserem Fall show-Customer.xhtml - und der Lebenszyklus ist abgeschlossen.
Nachdem wir den Standardfall durchexerziert haben, kommen wir nun zu einigen Spezialfällen. Schauen wir uns zunächst an, was passiert, wenn der Benutzer das Auswahlfeld "Use Credit Card" aktiviert. Der JavaScript-Code im Attribut onclick sorgt dafür, dass bei jedem Klick auf das Auswahlfeld das Formular übermittelt wird - eine Notwendigkeit, damit der registrierte Value-Change-Listener am Server seine Arbeit verrichten kann. Aber sehen wir uns zuerst die Logmeldungen des Phase-Listeners an, bevor wir weitere Details betrachten:
DEBUG Before phase: RESTORE_VIEW(1)
DEBUG After phase: RESTORE_VIEW(1)
DEBUG Before phase: APPLY_REQUEST_VALUES(2)
DEBUG After phase: APPLY_REQUEST_VALUES(2)
DEBUG Before phase: RENDER_RESPONSE(6)
DEBUG After phase: RENDER_RESPONSE(6)
Wie Sie sehen können, springt die Ausführung des Lebenszyklus nach der Apply-Request-Values-Phase direkt zur Render-Response-Phase. Der Grund dafür ist schnell erklärt. Die Eingabekomponente ist immediate und wird somit bereits in der Apply-Request-Values-Phase konvertiert und validiert. Das bedeutet weiterhin, dass auch der Value-Change-Listener aufgerufen wird, wenn sich der Wert der Komponente geändert hat. Genau dieser ruft dann auch nach dem Setzen des neuen Werts in der Eigenschaft useCreditCard die Methode FacesContext.renderResponse() auf, und die Phasen 3 bis 5 werden übersprungen.
Sehen wir uns abschließend noch kurz die Ausgabe des Phase-Listeners bei fehlgeschlagener Validierung an:
DEBUG Before phase: RESTORE_VIEW(1)
DEBUG After phase: RESTORE_VIEW(1)
DEBUG Before phase: APPLY_REQUEST_VALUES(2)
DEBUG After phase: APPLY_REQUEST_VALUES(2)
DEBUG Before phase: PROCESS_VALIDATIONS(3)
DEBUG After phase: PROCESS_VALIDATIONS(3)
DEBUG Before phase: RENDER_RESPONSE(6)
DEBUG After phase: RENDER_RESPONSE(6)
Die Ausgabe ist wie erwartet. Nach der Process-Validations-Phase wird bei einem Validierungsfehler sofort die Render-Response-Phase angesprungen und die gleiche Seite wird nochmals dargestellt - im Idealfall mit sprechenden Fehlermeldungen.
Neben den beiden Phase-Listenern ist in MyGourmet 4 zu Demonstrationszwecken noch der Listener ApplicationListener für die System-Events PostConstructApplicationEvent und PreDestroyApplicationEvent in der faces-config.xml registriert. Die einzige Funktionalität des Listeners ist, beim Hochfahren und Beenden der Applikation eine Logmeldung auszugeben. Listing MyGourmet 4: System-Event-Listener zeigt die Listener-Klasse. Nachdem die Methode processEvent für beide Ereignisse aufgerufen wird, muss anhand der übergebenen Instanz der Klasse SystemEvent eine Unterscheidung getroffen werden.
public class ApplicationListener implements SystemEventListener{
  static Log log = LogFactory.getLog(ApplicationListener.class);

  public void processEvent(SystemEvent event) {
    if (event instanceof PostConstructApplicationEvent) {
      log.debug("application startup ");
    } else if (event instanceof PreDestroyApplicationEvent) {
      log.debug("application shutdown");
    }
  }

  public boolean isListenerForSource(Object source) {
    return source instanceof Application;
  }
}

2.9 Seitendeklarationssprachen

Eine Seitendeklarationssprache ( View Declaration Language, VDL ) ist eine Syntax, um Ansichten beziehungsweise Seiten für JSF zu deklarieren. Dieses Konzept wurde in JSF 2.0 im Zuge der Integration von Facelets eingeführt, um von der eingesetzten Technologie zu abstrahieren. Der Standard unterstützt ab Version 2.0 mit Facelets und JSP zwei konkrete Implementierungen einer VDL.
Vor Version 2.0 von JavaServer Faces war JSP die primäre Seitendeklarationssprache der Spezifikation. Entwicklern sollte durch den Einsatz einer standardisierten und weitverbreiteten Technologie der Umstieg auf JSF so einfach wie möglich gemacht werden. Ein nobler Ansatz - doch leider ist das Gespann von JSF und JSP nur eine suboptimale Lösung, was hauptsächlich daran liegt, dass die beiden Technologien für verschiedene Einsatzzwecke entworfen wurden.
Sehen wir uns diese Diskrepanz zwischen JSP und JSF etwas genauer an. Wie Sie bereits wissen, wird eine Anfrage in JSF in mehreren Phasen abgearbeitet. Der Aufbau des Komponentenbaums aus der Seitendeklaration und die Ausgabe der Ansicht finden dabei in verschiedenen Phasen statt. Und genau hier liegt das Problem: JavaServer Pages wurden für einen viel einfacheren Lebenszyklus entwickelt.
Eine Anfrage wird mit JSPs in einer einzigen Phase bearbeitet - in der wird auch gleich die Antwort ausgegeben Im Hintergrund wird jede JSP-Datei in ein Servlet mit einer mehr oder weniger komplexen Reihe von out.println(" ...")-Anweisungen kompiliert.: . Das ist in Kombination mit JSF besonders dann problematisch, wenn die JSP-Datei neben Komponenten-Tags auch einfache Inhalte wie HTML-Elemente und Text enthält. Diese werden beim Abarbeiten der Datei direkt ausgegeben, während aus den Komponenten-Tags der Komponentenbaum aufgebaut wird, der erst am Ende des Lebenszyklus gerendert wird. Das und andere Gründe machen den Aufbau des Komponentenbaums aus JSP-Seitendeklarationen relativ aufwendig.
Hier kommt Facelets ins Spiel - eine alternative Technologie zur Deklaration von Ansichten, die perfekt in den Lebenszyklus von JSF integriert ist. Die Hauptaufgabe von Facelets besteht im Aufbau von Komponentenbäumen aus XHTML-Dokumenten. Facelets erledigt diese Aufgabe mit Bravour und bietet im Vergleich zu JSP auch noch einige andere Vorteile, auf die wir im nächsten Abschnitt eingehen werden.
In JavaServer Faces 2.0 hat Facelets den Platz von JSP als primäre Seitendeklarationssprache eingenommen. JSP wird im Standard nur mehr aus Kompatibilitätsgründen unterstützt. Neue Features sind ab JSF 2.0 nur noch mit Facelets verfügbar.

2.9.1 Vorteile von Facelets gegenüber JSP

Der Einsatz von Facelets in einem JSF-Projekt bietet eine Reihe von praktischen Vorteilen gegenüber dem Einsatz von JavaServer Pages . Das ist auch nicht weiter verwunderlich, da Facelets speziell für JSF entwickelt wurde. Durch die daraus resultierende, nahtlose Integration in den Lebenszyklus treten viele JSP-inhärente Probleme beim Aufbau der Ansichten erst gar nicht auf.
Die optimale Abstimmung auf den JSF-Lebenszyklus macht das Erstellen und Ausgeben von Ansichten mit Facelets effizienter. Das wirkt sich einerseits in einer höheren Geschwindigkeit beim Abarbeiten der Seiten gegenüber JSP aus. Andererseits ergeben sich auch für Seitenautoren einige Vorteile. Probleme bei der Kombination von Komponenten und Standard-HTML-Inhalten gehören mit Facelets der Vergangenheit an. Der Einsatz von f:verbatim erübrigt sich damit genauso wie die Notwendigkeit, f:view einzusetzen.
Facelets bietet die Möglichkeit, Seiten und Inhalte auf Templates aufzubauen. Nähere Informationen dazu finden Sie in Abschnitt Sektion:  Templating . Die Wiederverwendung von Inhalten wird generell groß geschrieben, was die Modularisierung Ihrer Projekte erleichtert und dadurch die Wartbarkeit erhöht. Seitenfragmente lassen sich einfach zentral ablegen und in mehrere Seiten integrieren. Das geht sogar so weit, dass Sie Komponenten erstellen können, ohne eine Zeile Java-Code zu verfassen. Wie das funktioniert, erfahren Sie in Kapitel Kapitel:  Die eigene JSF-Komponente .
Nach diesem kurzen Überblick über die Vorteile von Facelets sehen wir uns im nächsten Abschnitt die praktischen Aspekte von Seitendeklarationen etwas genauer an.

2.9.2 Seitendeklarationssprachen im Einsatz

JSF 2.0 war ein großer Schritt vorwärts, was die Unterstützung von Seitendeklarationssprachen betrifft. In älteren Versionen musste noch der View-Handler ausgetauscht werden, um Facelets in eigenen Projekten einzusetzen. Mit Version 2.0 wurde dieser Schritt obsolet. JSP und Facelets funktionieren jetzt auf Anhieb ohne weitere Konfiguration.
Bei der Integration von Facelets in den JSF-Standard wurde besonderer Wert auf die Rückwärtskompatibilität zur letzten Version vor JSF 2.0 gelegt. Nach außen hin hat sich für Entwickler, die Facelets als VDL einsetzen, nichts geändert. Lediglich die Implementierung wurde an JSF 2.0 angepasst. Wenn Sie in Ihrem Projekt also Klassen aus dem Package com.sun.facelets oder einem Subpackage verwenden, müssen Sie Ihr Projekt an die neuen Klassen anpassen. In allen anderen Fällen wird der Einsatz von Facelets sogar einfacher, da das Einbinden der Jar-Dateien und des Facelets-View-Handlers entfällt.
Bis jetzt haben wir in allen Beispielen Facelets eingesetzt. Es wird Zeit, eine solche Deklaration etwas genauer unter die Lupe zu nehmen. Listing Seitendeklaration in Facelets zeigt nochmals showCustomer.xhtml aus MyGourmet 1 in leicht gekürzter Fassung.
<!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:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html">
<head><title>Show Customer</title></head>
<body>
  <h1><h:outputText value="Show Customer"/></h1>
  <h:panelGrid id="grid" columns="2">
    <h:outputText value="First Name:"/>
    <h:outputText value="#{customer.firstName}"/>
    <h:outputText value="Last Name:"/>
    <h:outputText value="#{customer.lastName}"/>
  </h:panelGrid>
  <h:outputText value="Customer saved!"/>
</body>
</html>
Eine Facelets-Deklaration ist technisch gesehen nichts anderes als ein XHTML-Dokument mit dem Doctype XHTML Transitional . Das Einbinden der verwendeten Tag-Bibliotheken wird über Namensräume im Wurzelelement des Dokuments realisiert. Im Beispiel aus Listing Seitendeklaration in Facelets sind alle Tags der Core-Tag-Library unter dem Präfix f: und die Tags der HTML-Tag-Library unter dem Präfix h: verfügbar. Der Rest des Dokuments besteht nur mehr aus Komponenten-Tags und reinem HTML.
JSF entscheidet anhand einiger Regeln, ob eine Seitendeklaration als JSP oder Facelets interpretiert wird. Dieses Verhalten kann über folgende Kontextparameter in der web.xml gesteuert werden:
Prinzipiell spricht nichts dagegen, JSP und Facelets parallel in einer Anwendung einzusetzen. Mit der Standardkonfiguration von JSF ist das auch ohne Probleme möglich. Ob es Sinn macht, einen Teil der Ansichten in JSP und einen anderen mit Facelets zu deklarieren, bleibt fraglich.

2.10 Verwendung des ID-Attributs in JSF

Neben dem value -Attribut wird in JSF vermutlich das id -Attribut am häufigsten verwendet. Dieses Attribut erlaubt dem Entwickler, eine eindeutige ID für die Komponente festzulegen. Die ID muss dabei aber nicht eindeutig für die gesamte Seite sein, sondern nur innerhalb des aktuellen Naming-Containers, in den die Komponente eingebettet ist. Naming-Container sind in JSF einige Komponenten, die große Bereiche der Seite eingrenzen, wie h:form , h:dataTable oder sämtliche Kompositkomponenten .
Um den Entwickler dabei zu unterstützen, die Komponenten-IDs einer Webseite eindeutig zu halten, schreibt JSF nicht direkt die vom Benutzer angegebene ID in die gerenderte Ausgabe. Stattdessen kommt die sogenannte Client-ID der Komponente zum Einsatz. Die Client-ID wird durch Doppelpunkte getrennt aus der ID der Komponente und den IDs aller Elternkomponenten, die Naming-Container sind, zusammengesetzt.
Als Beispiel sind in Abbildung Client-IDs in MyGourmet 3 die Client-IDs aus dem Beispiel MyGourmet 3 zu finden. Alle Eingabekomponenten und die Schaltflächen sind innerhalb des Formulars mit der ID form angeordnet, was auch an den Client-IDs zu erkennen ist.
Abbildung:Client-IDs in MyGourmet 3
Bei einer Änderung der Seitenstruktur, bei der Naming-Container als Eltern hinzukommen oder wegfallen, ändert sich auch die Client-ID der eingebetteten Komponente. Ab JSF 1.2 kann dieses Verhalten für Form-Komponenten beeinflusst werden. Das Attribut prependId des h:form -Tags steuert, ob die ID des Formulars den Bezeichnern ihrer Komponenten vorangestellt wird (Wert ist true ) oder nicht (Wert ist false ). Der Defaultwert ist true . Abbildung Client-IDs in MyGourmet 3 mit prependId auf false zeigt nochmals die Client-IDs aus dem Beispiel MyGourmet 3 - diesmal ist allerdings das Attribut prependId des h:form -Tags auf false gesetzt.
Abbildung:Client-IDs in MyGourmet 3 mit prependId auf false
Natürlich wird die Client-ID auch innerhalb des Quelltextes benötigt - beispielsweise, um die ID einer Komponente zu rendern. Der Zugriff auf die vollständige ID erfolgt über die in UIComponent definierte Methode getClientId(FacesContext fc) . Ist beim ersten Aufruf dieser Methode noch keine ID für die Komponente gesetzt, erhält die Komponente eine automatische ID zugewiesen. Diese automatischen IDs sind über das Präfix j_id von händisch zugewiesenen Bezeichnern unterscheidbar.

2.11 Konvertierung

Die Konvertierung ist ein wichtiger Aspekt im JSF-Lebenszyklus, da Werte am Webclient in Form von Zeichenketten, serverseitig jedoch als Java-Typen dargestellt werden. Den Konvertern fällt dabei die Rolle eines internen Vermittlers zu. Sie kümmern sich um die Umwandlung der vom Benutzer eingegebenen Zeichenketten in Java-Objekte und wandeln Java-Objekte beim Rendern der Ausgabe wieder in Zeichenketten um.
In JSF gibt es für sehr viele Datentypen bereits Standardkonverter, die automatisch zum Einsatz kommen. Wenn wir zum Beispiel in MyGourmet 3 den Typ der Eigenschaft creditCardNumber der Klasse von String auf Integer ändern, müssen wir uns nicht um die Konvertierung kümmern. JSF verwendet, falls kein anderer Konverter definiert ist, automatisch den Standardkonverter für den Datentyp .
Dieser Standardkonverter ist für beide Richtungen der Konvertierung verantwortlich. Zum einen konvertiert er beim Rendern der Ansicht den Integer -Wert der Eigenschaft creditCardNumber in eine Zeichenkette. Beim Absenden eines Formulars konvertiert er außerdem die vom Benutzer eingegebene Zeichenkette wieder in einen Integer .
Jeder Konverter implementiert das in Listing Das Interface Converter dargestellte Interface javax.faces.convert.Converter .
public interface Converter {
  Object getAsObject(FacesContext context,
      UIComponent component,
      String value) throws ConverterException;
  String getAsString(FacesContext context,
      UIComponent component,
      Object value) throws ConverterException;
}
Die zwei Richtungen der Konvertierung sind hier klar zu erkennen: Für die Konvertierung des Java-Datentyps in eine Zeichenkette wird die Methode getAsString() aufgerufen, für die Konvertierung aus der Zeichenkette zurück in den Java-Datentyp die Methode getAsObject() .
Das Auffinden eines Konverters für eine Komponente funktioniert über folgende Schritte:
Die Registrierung von Konvertern in der faces-config.xml behandeln wir weiter unten. Zuerst zeigen wir Ihnen im nächsten Abschnitt, welche Konverter JSF bereits im Standard ausliefert.

2.11.1 Standardkonverter

Von den Standardkonvertern, die über ein Kind-Tag eingebunden werden, sind in der Praxis das Tag f:convertDateTime und das Tag f:convertNumber relevant.
Das Tag f:convertDateTime hat folgende Eigenschaften:
Das Tag f:convertNumber hat folgende Eigenschaften:
Für die einzelnen Attribute der Konverter gilt, dass sie möglichst nahe an die Attribute der Lokalisierungsfunktionen von Java selbst angelehnt sind - in der Java-API-Dokumentation findet sich also mehr über die möglichen Werte für die einzelnen Parameter und ihre Auswirkungen.
Die Behandlung von Fehlern und Fehlermeldungen in Form von Nachrichten beim Konvertieren wird in Abschnitt [Sektion:  Nachrichten] näher erläutert.
Konverter zum Lokalisieren: Konverter dienen auch der Lokalisierung von Werten. Mit dem Attribut locale kann der Lokalisierungscode für die Standardkonverter explizit gesetzt werden - sowohl als Zeichenkette als auch als Instanz der Klasse Locale über eine Value-Expression. Ohne explizite Angabe wird der Standard-Lokalisierungscode verwendet. Im Beispiel in Listing Lokalisierung mit Konvertern wird das Datum 13. Dezember 2012 aus einer Backing-Bean ausgelesen und für Deutsch und Englisch dargestellt, was zu folgender Ausgabe führt:
13.12.12
12/13/12
<h:outputText id="orderDate" value="#{orderBean.orderDate}">
  <f:convertDateTime dateStyle="short" locale="de"/>
</h:outputText>
<h:outputText id="orderDate" value="#{orderBean.orderDate}">
  <f:convertDateTime dateStyle="short" locale="en"/>
</h:outputText>
Konverter-Binding: Ab JSF 1.2 haben die Standardkonverter-Tags ( f:convertNumber , f:convertDateTime und f:converter ) ein zusätzliches Attribut binding , über das die Konverterinstanz aus einer Managed-Bean bezogen werden kann.

2.11.2 Benutzerdefinierte Konverter

Natürlich kann die Funktionalität des JSF-Frameworks durch Erstellen eigener Konverter erweitert werden: Es ist sowohl möglich, bestehende Konverter durch eigene zu ersetzen als auch neue Java-Datentypen zu verarbeiten oder für einzelne Komponenten-Tags eigene Konverter zu entwickeln.
Wir wollen uns als Beispiel einen Konverter ansehen, der in eingegebenen Zeichenketten Gruppen von Whitespace-Zeichen durch einen Unterstrich ersetzt. Listing Konverter zum Ersetzen von Zeichen zeigt die entsprechende Klasse Replace-Converter .
public class ReplaceConverter implements Converter {
  public Object getAsObject(FacesContext ctx, UIComponent c,
      String value) throws ConverterException {
    if (value == null) return null;
    return value.replaceAll("
s+", "_");
  }
  public String getAsString(FacesContext ctx, UIComponent c,
      Object value) throws ConverterException {
    if (value == null) return null;
    return value.toString();
  }
}
Unser Konverter implementiert das Interface Converter mit den beiden Methoden getAsObject() and getAsString() . Nachdem wir vom Benutzer eingegebene Zeichenketten konvertieren wollen, ersetzen wir die Zeichen in der Methode getAsObject() . In der Methode getAsString() , die beim Rendern aufgerufen wird, geben wir einfach die String-Repräsentation des übergebenen Objekts zurück (es sollte sich ohnehin um einen String handeln).
Ein kleiner Hinweis: Dieser Konverter funktioniert in der oben gezeigten Form nur mit Eingabekomponenten, nicht aber mit Ausgabekomponenten. Warum? Aus dem zuvor erwähnten Grund, dass in getAsString() nur die String-Repräsentation des übergebenen Objekts zurückgegeben wird.
Benutzerdefinierte Konverter können auf verschiedene Weise in JSF registriert und eingebunden werden. Eine Möglichkeit ist, einzelne Komponenten einer Ansicht mit Konvertern zu versehen. Das funktioniert bei allen Ein- und Ausgabekomponenten über das Kindelement f:converter oder eine Method-Expression im Attribut converter . Das Tag f:converter wird einfach als Kindelement zum Tag der gewünschten Komponente in der Seitendeklaration hinzugefügt. Dazu muss der Konverter allerdings bereits unter einem eindeutigen Bezeichner im System registriert sein. Dieser Bezeichner wird dann im Attribut converterId von f:converter eingetragen.
JSF bietet ab Version 2.0 die Möglichkeit, Konverter mit der Annotation @FacesConverter im System zu registrieren. Listing Konverter mit @FacesConverterregistrieren zeigt, wie der zuvor erstellte Konverter mit dem in der Annotation angegebenen Bezeichner verbunden wird.
@FacesConverter("at.irian.ReplaceConverter")
public class ReplaceConverter implements Converter {
  ...
}
In JSF vor Version 2.0 erfolgte die Registrierung von Konvertern ausschließlich in der Datei faces-config.xml . In JSF 2.0 und neueren Versionen steht diese Variante natürlich immer noch zur Verfügung. Dazu muss für jeden Konverter ein Element converter mit den Kindelementen converter-id für den Bezeichner und converter-class für die Klasse des Konverters angelegt werden. Listing Konverter in faces-config.xmlregistrieren zeigt, wie die zuvor mit der Annotation durchgeführte Registrierung unseres Konverters in der faces-config.xml aussieht.
<converter>
  <converter-id>at.irian.ReplaceConverter</converter-id>
  <converter-class>
    at.irian.jsfatwork.gui.jsf.ReplaceConverter
  </converter-class>
</converter>
JSF bietet noch eine weitere Variante, um einen Konverter mit einer Komponente zu verbinden. Alle Ein- und Ausgabekomponenten verfügen dazu über das Attribut converter . In diesem Attribut kann über eine Value-Expression eine Managed-Bean-Eigenschaft vom Typ javax.faces.convert.Converter referenziert werden. Die Getter-Methode dieser Eigenschaft muss dann bei jedem Aufruf eine neue Instanz des gewünschten Konverters zurückliefern. Eine Setter-Methode wird hierfür nicht benötigt.
Listing Benutzerdefinierte Konverter in der Seitendeklaration zeigt für beide vorgestellten Varianten, wie der Konverter in der Seitendeklaration mit einer Komponente verbunden wird. Beim Einsatz des Attributs converter muss es natürlich eine Methode getReplaceConverter in der referenzierten Managed-Bean geben.
<h:inputText value="#{bean.stringValue}">
  <f:converter converterId="at.irian.ReplaceConverter"/>
</h:inputText>
<h:inputText value="#{bean.otherStringValue}"
    converter="#{bean.replaceConverter}"/>
Alternativ bietet es sich an, eine zentrale Managed-Bean für alle benutzerdefinierten Konverter der Anwendung zu erstellen. Diese Bean liegt im Application-Scope und stellt für jeden Konverter eine Getter-Methoden zur Verfügung, die eine neue Instanz des Konverters zurückliefert. Listing Managed-Bean für Konverter zeigt, wie eine solche Managed-Bean aussehen könnte.
@ManagedBean @ApplicationScoped
public class ConverterProvider {
  public Converter getReplaceConverter() {
    return new ReplaceConverter();
  }
}
Die Registrierung eines Konverters mit einem Bezeichner bietet einen entscheidenden Vorteil: Mit Facelets lässt sich das Einbinden benutzerdefinierter Konverter dadurch noch weiter vereinfachen. In Abschnitt Sektion:  Tag-Bibliotheken mit Facelets erstellen zeigen wir Ihnen, wie Sie auf einfachstem Weg in einer Tag-Bibliothek ein Tag für einen eigenen Konverter definieren.
Neben dem Einbinden benutzerdefinierter Konverter in einzelne Komponenten bietet JSF außerdem die Möglichkeit, einen Konverter global für einen bestimmten Datentyp zu registrieren. Ab JSF 2.0 reicht es dazu aus, das Element forClass der Annotation @FacesConverter auf den gewünschten Datentyp zu setzen. Listing Konverter für Datentyp mit @FacesConverter zeigt das für den Datentyp Date .
@FacesConverter(forClass = Date.class)
public class MyDateConverter implements Converter {
  ...
}
In JSF vor Version 2.0 mussten Konverter für bestimmte Klassen in der faces-config.xml registriert werden. Wie das funktioniert, zeigt Listing Konverter für Datentyp in faces-config.xml . Wenn unter converter-for-class eine Klasse angegeben wird, für die noch kein Konverter existiert, wird ein neuer Konverter registriert. Diese Art der Konfiguration funktioniert natürlich auch weiterhin mit JSF 2.0 und neueren Versionen.
<converter>
  <converter-for-class>
    java.util.Date
  </converter-for-class>
  <converter-class>
    at.irian.jsfatwork.MyDateTimeConverter
  </converter-class>
</converter>
Im Beispiel MyGourmet 5 in Abschnitt [Sektion:  MyGourmet 5: Konvertierung] wird ein weiterer benutzerdefinierter Konverter für Postleitzahlen entwickelt und in das Beispiel integriert.

2.11.3 MyGourmet 5: Konvertierung

Das Beispiel MyGourmet 5 erweitert MyGourmet 4 aus Abschnitt [Sektion:  MyGourmet 4: Phase-Listener und System-Events] um einige Eingabefelder und zeigt den Einsatz von Standard- und benutzerdefinierten Konvertern.
Beginnend mit diesem Beispiel ändern wir die zugrunde liegende Backing-Bean . Bis jetzt hat es eine Bean gegeben, die alle Eigenschaften des Kunden und den Code für die Seite beinhaltet hat. Im Hinblick auf die Erweiterungen im Zuge dieses Abschnitts ist es sinnvoll, diese Aspekte zu trennen. Die Daten des Kunden befinden sich jetzt in der Klasse Customer im Package at.irian.jsfatwork.domain . Die Managed-Bean CustomerBean ersetzt die bisherige Klasse Customer im Package at.irian.jsfatwork.gui.page . Sie enthält eine Eigenschaft customer für den aktuellen Kunden und Code, der für die Seite relevant ist.
Zur Demonstration der Konverter erhält die Klasse Customer zusätzlich die Eigenschaften birthday , zipCode , city und street . Für die Eingabe des Geburtsdatums auf editCustomer.xhtml fügen wir den Standardkonverter f:convertDateTime mit einem Muster hinzu. Listing Eingabekomponente mit Datumskonverter zeigt das Tag.
<h:inputText id="birthday" size="30"
    value="#{customerBean.customer.birthday}">
  <f:convertDateTime pattern="dd.MM.yyyy"/>
</h:inputText>
Für die Ausgabe des Geburtsdatums auf showCustomer.xhtml wird der gleiche Standardkonverter verwendet. Listing Ausgabekomponente mit Datumskonverter zeigt das Tag.
<h:outputText
    value="#{customerBean.customer.birthday}">
  <f:convertDateTime pattern="dd.MM.yyyy"/>
</h:outputText>
Als Beispiel für einen benutzerdefinierten Konverter wollen wir einen Konverter entwickeln, der die Eingabe von Postleitzahlen mit Ländercodes ermöglicht, ohne dass diese mitgespeichert werden. Dazu werden einfach alle Buchstaben inklusive des ersten Auftretens des Zeichens '-' ignoriert. Der Rest der eingegebenen Zeichenkette muss dann eine Zahl sein. Aus der Eingabe A-1010 wird dann beispielsweise die Zahl 1010 .
Der neue Konverter ZipCodeConverter wird von der JSF-Klasse IntegerConverter abgeleitet, was den Vorteil hat, dass die eigentliche Konvertierung der Postleitzahl ohne den Ländercode von diesem erledigt werden kann. Listing Konverter für Postleitzahlen mit Ländercodes zeigt den Code der Klasse. Mit der Annotation @FacesConverter registrieren wir den Konverter unter dem Bezeichner at.irian.ZipCode für die spätere Verwendung im System.
@FacesConverter(value="at.irian.ZipCode")
public class ZipCodeConverter
    extends IntegerConverter implements Serializable {
  public Object getAsObject(FacesContext ctx,
      UIComponent component,String value)
      throws ConverterException {
    if (value != null && value.length() > 0) {
      int pos = value.indexOf('-');
      for (int i = 0; i < pos; i++) {
        if (!Character.isLetter(value.charAt(i))) {
          throw new ConverterException("Zip invalid.");
        }
      }
      if (pos > -1 && pos < value.length() - 1) {
        return super.getAsObject(
            ctx, component, value.substring(pos + 1));
      }
    }
    return super.getAsObject(ctx, component, value);
  }
}
Interessant ist insbesondere der Teil, der die Fehlerbehandlung übernimmt: Wenn die Postleitzahl nicht dem Muster entspricht, wird eine Exception-Instanz vom Typ ConverterException erzeugt. Die Ausnahme wird über ihren Konstruktor mit einer Nachricht verbunden, die dann im Webbrowser als Fehlermeldung ausgegeben wird.
Der benutzerdefinierte Konverter ZipCodeConverter wird über das Kindelement f:converter in editCustomer.xhtml eingebunden. Listing Konverter über Bezeichner einbinden zeigt das entsprechende Tag.
<h:inputText id="zipCode" size="30" required="true"
    value="#{customerBean.customer.zipCode}">
  <f:converter converterId="at.irian.ZipCode"/>
</h:inputText>
Nachdem wir jetzt alle Eingaben in die benötigten Datentypen konvertiert haben, wenden wir uns im nächsten Abschnitt der Validierung von Benutzereingaben zu.

2.12 Validierung

Die Eingabe von Daten ist eine Angelegenheit, bei der Benutzer gerne Fehler machen - das Aufzeigen und die Verhinderung der Eintragung fehlerhafter Daten ins Modell muss jedem Entwicklungsframework ein Anliegen sein. JSF bietet "out-of-the-box" bereits viele Möglichkeiten an, solche Fehler zu verhindern.
Für die Überprüfung von Benutzereingaben werden in JSF sogenannte Validatoren eingesetzt. Die Verknüpfung von Validatoren und Eingabekomponenten funktioniert sehr einfach - ähnlich wie bei der Einbindung von Konvertern. Neben einer ganzen Reihe von Standardvalidatoren, die im Lieferumfang von JSF enthalten sind, ist es ohne Weiteres möglich, eigene Validatoren zu schreiben.
Mit JSF ab Version 2.0 gestaltet sich die Validierung durch die Unterstützung des Bean-Validation-Konzepts (JSR-303) sogar noch einfacher. Durch neue Mechanismen ist erstmals eine vollständig metadatenbasierte Validierung ohne Umwege möglich. Abschnitt [Sektion:  Bean-Validation nach JSR-303] widmet sich diesem hochaktuellen Thema und zeigt, wie Sie Bean-Validation in Kombination mit JSF in eigenen Projekten einsetzen.
Anschließend wenden wir uns in Abschnitt [Sektion:  Standardvalidatoren] den Standardvalidatoren zu, bevor wir Ihnen in Abschnitt [Sektion:  Benutzerdefinierte Validatoren] zeigen, wie Sie benutzerdefinierte Validatoren erstellen.

2.12.1 Bean-Validation nach JSR-303

JSF 2.0 war ein großer Schritt vorwärts für die Validierung in Webanwendungen. Davor hat Validierung über die Schichtengrenzen hinweg immer zu Redundanz geführt. In vielen Fällen wurden die gleichen Validierungsregeln in mehreren Schichten der Applikation umgesetzt. Neben einem erhöhten Aufwand bei der Implementierung führt dieser Ansatz auch zu einer höheren Fehleranfälligkeit. Die Validierung wurde dann zum Beispiel sowohl über JSF-Validatoren in der Ansicht als auch beim Persistieren der Entitäten im Service angewandt.
Die Kombination von JSF ab Version 2.0 und Bean-Validation (JSR-303) ändert das radikal. Bean-Validation definiert ein Metadatenmodell und eine Java-API, um die Validierung gezielt in den Domainklassen zu bündeln. Validierungsregeln (Constraints) sind in Form von Validatorklassen implementiert, die mit Annotationen an JavaBeans gebunden werden.
Sehen wir uns am Beispiel von MyGourmet an, wie Bean-Validation in der Praxis eingesetzt wird. Existieren für eine Eigenschaft einer Bean, die an eine Eingabekomponente gebunden ist, JSR-303-konforme Metadaten, so wird diese automatisch validiert. Listing Validierung mit Bean-Validation zeigt einen Ausschnitt der Klasse Customer mit Bean-Validation-Metadaten. Mit der Annotation @NotNull wollen wir vermeiden, dass eine der drei Eigenschaften den Wert null annimmt. Die Eigenschaft zipCode ist zusätzlich mit den Annotationen @Min und @Max auf einen Wertebereich von 1000 bis 99999 eingeschränkt.
public class Customer {
  @NotNull @Min(value = 1000) @Max(value = 99999)
  private Integer zipCode;
  @NotNull
  private String city;
  @NotNull
  private String street;
  ...
}
Eine wichtige Voraussetzung ist noch zu beachten: Damit fehlende Benutzereingaben von JavaServer Faces auch wirklich als null -Werte interpretiert werden, muss in der web.xml der Kontextparameter javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL auf true gesetzt werden. Standardmäßig ignoriert JSF Eingabekomponenten ohne Benutzereingabe bei der Validierung. Dieses Verhalten macht dann zum Beispiel den Einsatz von @NotNull unmöglich.
Das wars! Mehr ist nicht notwendig, um Bean-Validation ab JSF 2.0 zu nutzen, wenn eine Implementierung von JSR-303 in der Laufzeitumgebung verfügbar ist. In MyGourmet wird das durch eine Abhängigkeit von Hibernate Validator v4 , der Referenzimplementierung von JSR-303, in der Maven-Projektdatei pom.xml garantiert. Wie Sie sehen, mussten wir keine der Ansichten ändern, um die Validierung zu aktivieren.
Das komplette Beispiel MyGourmet 6 mit allen Änderungen bezüglich der Validierung finden Sie in Abschnitt [Sektion:  MyGourmet 6: Validierung] .
In der folgenden Aufzählung finden Sie eine Liste aller Standard-Constraints in Bean-Validation:
MyFaces ExtVal: JSF bietet leider nicht den vollen Funktionsumfang von Bean-Validation. Alternativ können Sie einen Blick auf das Projekt MyFaces Extensions Validator (auch bekannt als ExtVal) Weiterführende Informationen zum Projekt MyFaces ExtVal finden Sie unter http://myfaces.apache.org/extensions/validator.: werfen, das einen Adapter für JSR-303-konforme Implementierungen zur Verfügung stellt. Dieser Adapter bietet außerdem zusätzliche Funktionalitäten und alternative Konzepte. Beispielsweise wird MyFaces ExtVal für die Gruppenvalidierung (als Alternative zur deklarativen Angabe in der Ansicht) Annotationen für die Eigenschaften und Action-Methoden im Backing-Bean zur Verfügung stellen. Dadurch werden Refactorings erheblich einfacher und können zuverlässig durchgeführt werden. Über den Standard hinaus bietet MyFaces ExtVal ein eigenes Validierungsmodul mit weiteren Funktionalitäten speziell für JSF. Unter anderem wird komponentenübergreifende Validierung unterstützt. MyFaces ExtVal ist auch mit JSF 1.1 und JSF 1.2 einsetzbar.

2.12.1.1 Benutzerdefinierte Constraints mit Bean-Validation

Mit Bean-Validation können Sie sehr einfach eigene Validatoren und Constraints erstellen. Wir wollen dieses Feature in MyGourmet nutzen und einen Validator erstellen, der das Geburtsdatum auf einen bestimmten Zeitraum überprüft. Das Datum darf nicht vor dem 1.1.1900 und nicht in der Zukunft liegen.
Alles, was wir dazu brauchen, ist eine Annotation und eine Validatorklasse. Fangen wir mit der Annotation @Birthday an. Damit diese Annotation als Constraint für Bean-Validation eingesetzt werden kann, muss sie wiederum mit @Constraint annotiert werden. Das Element validatedBy stellt die Verbindung zur Validatorklasse, in unserem Fall , her. Eine Constraint-Annotation muss mindestens über die Elemente message zur Definition einer Fehlermeldung, groups zur Definition der Validatorgruppen und payload zum Übergeben zusätzlicher Informationen verfügen. Listing Annotation für benutzerdefiniertes Constraint zeigt den Code von @Birthday .
@Constraint(validatedBy = BirthdayValidator.class)
@Retention(RUNTIME)
@Target({ANNOTATION_TYPE, METHOD, FIELD})
public @interface Birthday {
  String message() default "Wrong birthday";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}
Die beiden Annotationen @Retention und @Target sorgen dafür, dass die Annotation zur Laufzeit ausgewertet wird und auf andere Annotationen, Methoden und Felder angewendet werden kann. Das Element hat einen Defaultwert, der als Fehlermeldung ausgegeben wird, falls er nicht überschrieben wird. Der Defaultwert sollte im Idealfall auf ein Resource-Bundle verweisen, mehr dazu in Abschnitt [Sektion:  Internationalisierung] .
Zum erfolgreichen Einsatz unseres Constraints fehlt nur noch die Validatorklasse BirthdayValidator . Sie muss das Interface ConstraintValidator , parametrisiert mit der zuvor definierten Annotation und dem zu validierenden Datentyp, implementieren. Die Methode initialize bekommt beim Validierungsvorgang die zugehörige Annotation als Parameter und dient zur Initialisierung des Validators. Die eigentliche Validierung erfolgt dann in der Methode isValid , die den zu validierenden Wert und den aktuellen Kontext übergeben bekommt. Im Falle einer erfolgreichen Validierung muss sie true zurückliefern. Listing Validator für benutzerdefiniertes Constraint zeigt den Sourcecode.
public class BirthdayValidator
    implements ConstraintValidator<Birthday, Date> {
  public void initialize(Birthday birthday) {}

  public boolean isValid(Date date,
      ConstraintValidatorContext ctx) {
    boolean dateCorrect = true;
    if (date != null) {
      ctx.disableDefaultConstraintViolation();
      if (date.after(new Date())) {
        ctx.buildConstraintViolationWithTemplate(
            "Birthday is in the future.")
            .addConstraintViolation();
        dateCorrect = false;
      }
      Calendar cal = Calendar.getInstance();
      cal.set(Calendar.YEAR, 1900);
      cal.set(Calendar.MONTH, 0);
      cal.set(Calendar.DAY_OF_MONTH, 1);
      if (date.before(cal.getTime())) {
        ctx.buildConstraintViolationWithTemplate(
            "Birthday is before Jan 1, 1900.")
            .addConstraintViolation();
        dateCorrect = false;
      }
    }
    return dateCorrect;
  }
}
Da wir in isValid zwei verschiedene Überprüfungen durchführen, wollen wir auch zwei unterschiedliche Fehlermeldungen definieren. Die Standardfehlermeldung soll gar nicht erst angezeigt werden und wird mittels ctx.disableDefaultConstraintViolation() abgeschaltet. Jetzt können wir mit folgendem Code beliebige Fehlermeldungen zum Kontext hinzufügen, die dann in JSF-Nachrichten umgewandelt werden:
ctx.buildConstraintViolationWithTemplate("Fehlermeldung")
    .addConstraintViolation();
Unser benutzerdefiniertes Constraint ist jetzt fertig für den Einsatz. Die Eigenschaft birthday der Klasse Customer sieht mit der neuen Annotation wie folgt aus:
@Birthday
private Date birthday;

2.12.2 Standardvalidatoren

Nach der kurzen Einführung in Bean-Validation mit JSF widmen wir uns im Rest des Abschnitts der klassischen JSF-Validierung. Für die Einbindung als Kind-Tag steht bereits im JSF-Standard eine Liste von Validatoren zur Verfügung:
Wie Sie wahrscheinlich bemerkt haben, ist der Validator zum Überprüfen verpflichtender Werte erst ab JSF 2.0 verfügbar. Wie hat das vorher funktioniert? In älteren Versionen wurde der gleiche Effekt über das Setzen des Attributs required auf den einzelnen Eingabekomponenten erreicht. Ist das Attribut auf true gesetzt, schlägt die Validierung der Komponente bei einem Leerwert fehl. Ab JSF 2.0 können Sie beide Varianten benutzen, der eigene Validator fügt sich allerdings besser in das Validierungskonzept ein.
Bis JSF 2.0 können Sie in benutzerdefinierten Validatoren keine Überprüfung auf null durchführen, weil JSF die Validierung erst gar nicht aufruft, wenn das required -Attribut einer Komponente auf false gesetzt ist und der Benutzer nichts eingegeben hat. Dieses Verhalten war notwendig, weil Validatoren nicht optional gemacht werden konnten.
Ab JSF 2.0 kann dieses Verhalten über den Kontextparameter javax.faces.VALIDATE_EMPTY_FIELDS gesteuert werden. Ein Wert von true (oder auto in Kombination mit Bean-Validation) weist JSF an, auch leere Eingabekomponenten zu validieren.
Validator-Binding: Die Tags der Standardvalidatoren haben ab JSF 1.2 ein zusätzliches Attribut binding , über das eine Validatorinstanz mit einer Eigenschaft einer Managed-Bean verknüpft werden kann. Die verknüpfte Eigenschaft muss den Typ javax.faces.validator.Validator aufweisen.
Ab JSF 2.0 verfügen alle Standardvalidatoren über das Attribut dis-abled , das eine Value-Expression aufnimmt. Wenn der Ausdruck zu true evaluiert, kommt der Validator im Lebenszyklus nicht zum Einsatz.
Kombination von Validatoren: Es ist möglich, Validatoren zu kombinieren - also zum Beispiel sicherzustellen, dass eine Zeichenkette angegeben wurde, die zwischen drei und sechs Zeichen lang ist und aus einem Großbuchstaben gefolgt von einer Reihe weiterer Buchstaben besteht (siehe Listing Kombination von Validatoren ).
<h:inputText value="#{backingBean.wert}">
  <f:validateRequired/>
  <f:validateLength minimum="3" maximum="6"/>
  <f:validateRegex pattern="[A-Z][a-zA-Z]*"/>
</h:inputText>

2.12.3 Benutzerdefinierte Validatoren

Benötigen Sie Überprüfungen, die von Standardvalidatoren nicht abgedeckt werden, können Sie eigene Validatoren schreiben. Am einfachsten funktioniert das mit Validierungsmethoden. Diese werden mit einer Method-Expression im Attribut validator der Eingabekomponente referenziert und müssen die in Listing Signatur einer Validierungsmethode dargestellte Signatur aufweisen. Der Name der Methode ist frei wählbar.
public void validate(
    FacesContext ctx,
    UIComponent component,
    Object value) throws ValidatorException;
Sehen wir uns ein kleines Beispiel im Detail an. Unser benutzerdefinierter Validator soll eine Zeichenkette auf die Werte ja und nein prüfen und bei allen anderen Eingaben eine Fehlermeldung erzeugen. Listing Beispiel einer Validierungsmethode zeigt die entsprechende Methode.
public void yesNoValidate(FacesContext ctx, UIComponent comp,
    Object value) throws ValidatorException {
  if (value instanceof String) {
    String strValue = (String) value;
    if (!(strValue.equals("ja")
        && !(strValue.equals("nein")))) {
      throw new ValidatorException(
          new FacesMessage("messageText", null));
    }
  } else {
      throw new ValidatorException(
          new FacesMessage("messageText", null));
  }
}
Im Fehlerfall wirft die Methode eine ValidatorException mit einer zugeordneten FacesMessage . Sie wird dem Benutzer angezeigt. Die Behandlung von Fehlern und Fehlermeldungen in Form von Nachrichten beim Validieren wird in Abschnitt [Sektion:  Nachrichten] näher erläutert.
Damit diese Methode beim Validierungsvorgang aufgerufen wird, muss sie mit der Eingabekomponente verbunden werden:
<h:inputText value="#{backingBean.wert}"
  validator="#{backingBean.yesNoValidate}"/>
Wenn neben einer Validierungsmethode noch weitere Validatoren mit Tags in die Komponente eingebunden sind, kommt die Validierungsmethode immer als letzte an die Reihe.
Cross-Component-Validierung: Manche Anwendungsfälle machen Validierungsmethoden erforderlich, die nicht nur den Wert einer Komponente validieren. Die Methode muss dazu mit der letzten zu validierenden Komponente verknüpft sein - nur dann kann auf die konvertierten und validierten Werte aller anderen Komponenten zugegriffen werden. Ein Beispiel für die Cross-Component-Validierung finden Sie in MyGourmet 6 in Abschnitt [Sektion:  MyGourmet 6: Validierung] .
Benutzerdefinierte Validatoren können auch als eigenständige Klassen implementiert werden, die dann das Interface javax.faces.validator.Validator implementieren müssen. In einer Seitendeklaration werden solche Validatoren mit dem Tag f:validator als Kindelement in eine Eingabekomponente eingebunden. Das Attribut validatorId dieses Tags bezieht sich auf den Bezeichner, unter dem die Validatorklasse im System registriert ist.
Die Registrierung eines Validators kann wie bei Konvertern in der faces-config.xml oder ab JSF 2.0 auch mit einer Annotation erfolgen. Listing Registrierung eines Validators mit Bezeichner zeigt das entsprechende Fragment der Konfiguration - ist die dazu äquivalente Annotation.
<validator>
  <validator-id>at.irian.YesNo</validator-id>
  <validator-class>
    at.irian.jsfatwork.YesNoValidator
  </validator-class>
</validator>
JSF 2.2: Ab JSF 2.2 ist das Element value der Annotation @FacesValidator optional und wird mit einer Namenskonvention ergänzt. Ist es nicht angegeben, benutzt JSF den Klassennamen mit einem kleinen Anfangsbuchstaben als Validator-ID. Für unsere Validatorklasse YesNoValidator wäre das die ID yesNoValidator .

2.12.4 MyGourmet 6: Validierung

MyGourmet 6 erweitert sein Vorgängerbeispiel MyGourmet 5 aus Abschnitt [Sektion:  MyGourmet 5: Konvertierung] . Der Fokus dieses Beispiels liegt, wie sich aus dem Thema der vorherigen Abschnitte leicht erraten lässt, auf Standard- und benutzerdefinierten Validatoren.
Den Großteil der Validierungsaufgaben in MyGourmet erledigt Bean-Validation - wie wir bereits in Abschnitt [Sektion:  Bean-Validation nach JSR-303] gesehen haben. Listing MyGourmet 6: Bean Customer mit Annotationen zeigt alle Eigenschaften der Klasse Customer mit den Bean-Validation-Annotationen, jedoch ohne die zugehörigen Getter und Setter.
public class Customer {
  @NotNull
  private String firstName;
  @NotNull
  private String lastName;
  @NotNull
  private String email;
  @NotNull @Min(value = 1000) @Max(value = 99999)
  private Integer zipCode;
  @NotNull
  private String city;
  @NotNull
  private String street;
  @Birthday
  private Date birthday;
  private Boolean useCreditCard = Boolean.FALSE;
  @NotNull
  private CreditCardType creditCardType;
  @NotNull
  private String creditCardNumber;
  ...
}
Um die Verwendung der JSF-Standardvalidatoren vorzustellen, könnte die Postleitzahl der Adresse in editCustomer.xhtml anstatt mit Bean-Validation auch mit dem Tag f:validateLongRange auf den Wertebereich 1000 bis 99999 überprüft werden. Listing MyGourmet 6: Validierung der Postleitzahl mit Standardvalidator zeigt das zugehörige Fragment der Seite.
<h:inputText id="zipCode" size="30" required="true"
    value="#{customerBean.customer.zipCode}">
  <f:converter converterId="at.irian.ZipCode"/>
  <f:validateLongRange minimum="1000" maximum="99999"/>
</h:inputText>
Wir wollen Ihnen auch zeigen, wie das Geburtsdatum des Kunden auf klassische Art und Weise validiert wird. Zu diesem Zweck kommt der Validator BirthdayValidator (nicht zu verwechseln mit dem Constraint-Validator aus Abschnitt [Sektion:  Bean-Validation nach JSR-303] !) zum Einsatz, der das Datum auf den Wertebereich überprüft und im System unter dem Namen at.irian.Birthday registriert ist. Listing MyGourmet 6: Validator für das Geburtsdatum zeigt den Code.
@FacesValidator(value = BirthdayValidator.VALIDATOR_ID)
public class BirthdayValidator 
    implements Validator, Serializable {
  private static final long serialVersionUID = 1L;
  public static final String VALIDATOR_ID = "at.irian.Birthday";

  public void validate(FacesContext ctx, UIComponent component,
      Object value) throws ValidatorException {
    Date date = (Date)value;
    if (date.after(new Date())) {
      FacesMessage msg = new FacesMessage(
          FacesMessage.SEVERITY_ERROR,
          "Birthday is in the future.", null);
      throw new ValidatorException(msg);
    }
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, 1900);
    cal.set(Calendar.MONTH, 0);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    if (date.before(cal.getTime())) {
      FacesMessage msg = new FacesMessage(
          FacesMessage.SEVERITY_ERROR,
          "Birthday is before Jan 1, 1900.", null);
      throw new ValidatorException(msg);
    }
  }
}
Gibt der Benutzer ein Datum außerhalb dieses Bereichs an, wird eine ValidatorException mit einer entsprechenden FacesMessage geworfen. Das Einbinden dieses benutzerdefinierten Validators in die Seitendeklaration ist in Listing MyGourmet 6: Validierung des Geburtsdatums zu sehen.
<h:inputText id="birthday" size="30"
    value="#{customerBean.customer.birthday}">
  <f:convertDateTime pattern="dd.MM.yyyy"/>
  <f:validator validatorId="at.irian.Birthday"/>
</h:inputText>
Als Beispiel für eine Cross-Component-Validierung mit einer Validierungsmethode wird die Kreditkartennummer vom gewählten Kartentyp abhängig gemacht. Ist Karte A ausgewählt, muss die Kartennummer vier Zeichen lang sein, ist Karte B ausgewählt, muss sie aus fünf Zeichen bestehen. Die erweiterten Tags für den Typ und die Kartennummer auf der Seite editCustomer.xhtml zeigt Listing MyGourmet 6: Kreditkartendaten mit Validierung .
<h:selectOneListbox id="ccType"
    value="#{customerBean.customer.creditCardType}"
    rendered="#{customerBean.customer.useCreditCard}">
  <f:selectItems value="#{customerBean.creditCardTypes}"/>
  <f:event type="javax.faces.event.PostValidateEvent"
      listener="#{customerBean.postValidateCCType}"/>
</h:selectOneListbox>
<h:inputText id="ccNumber"
    value="#{customerBean.customer.creditCardNumber}"
    rendered="#{customerBean.customer.useCreditCard}"
    validator="#{customerBean.validateCreditNumber}"/>
Beachten Sie in Listing MyGourmet 6: Kreditkartendaten mit Validierung , dass die Komponenten über das rendered -Attribut abhängig vom Wert der Eigenschaft useCreditCard ein- oder ausgeblendet werden. Da JSF nur gerenderte Komponenten validiert, können wir damit auch die Validierung steuern.
Für die Validierung der Kreditkartennummer ist die Methode validateCreditNumber der Klasse CustomerBean zuständig. Die vom Benutzer eingegebene Nummer wird dabei direkt als Argument value an die Methode übergeben. Das Auslesen des aktuellen Kreditkartentyps erfordert allerdings etwas mehr Aufwand. Ein einfacher Zugriff auf die Eigenschaft useCreditCard des aktuellen Kunden liefert in diesem Fall nicht immer den aktuellen Wert. Der Grund dafür ist einfach: Die Eigenschaft im Modell wird erst nach der erfolgreichen Validierung aller Komponenten aktualisiert und hat zum Zeitpunkt des Aufrufs der Validierungsmethode noch den alten Wert. Zum Auslesen des aktuellen, vom Benutzer eingegebenen Werts gibt es mehrere Möglichkeiten.
Zum einen kann die Komponente für den Kreditkartentyp mit dem Attribut binding an eine Eigenschaft der Backing-Bean gebunden werden - ein Vorgang, der auch Component-Binding genannt wird. Das Binding ermöglicht den direkten Zugriff auf die Komponente in der Validierungsmethode. Component-Binding kann aber, wenn die Bean wie in unserem Fall im Session-Scope liegt, zu unangenehmen Nebenwirkungen führen, da die Komponente nur einmal erzeugt und dann in der Bean abgelegt wird.
JSF bietet mithilfe des System-Events PostValidateEvent eine weitere, sehr elegante Möglichkeit, um den aktuellen Kreditkartentyp auszulesen. Dieses Event wird direkt nach dem Validieren einer Komponente ausgelöst. Wir müssen also nur mit dem Tag f:event eine Listener-Methode für dieses Ereignis registrieren (siehe Listing MyGourmet 6: Kreditkartendaten mit Validierung ) und können dann in dieser Methode auf die Komponente und den aktuellen Wert zugreifen. Listing MyGourmet 6: System-Event-Listener für Validierung zeigt die Listener-Methode für das System-Event PostValidateEvent in der Klasse CustomerBean .
public void postValidateCCType(ComponentSystemEvent ev) {
  this.creditCardTypeInput = (UIInput)ev.getComponent();
}
In Listing MyGourmet 6: Validierungsmethode für Kreditkarten-nummer finden Sie die Implementierung der Validierungsmethode in der Klasse CustomerBean . Beachten Sie den Zugriff auf das Feld creditCardTypeInput zum Auslesen des aktuellen Kreditkartentyps. Da die Reihenfolge der Validierung einzelner Komponenten in JSF immer durch den Komponentenbaum bestimmt ist, wird dieses Feld garantiert vor dem Aufruf der Validierungsmethode gesetzt.
public void validateCreditNumber(FacesContext ctx,
    UIComponent comp, Object value) throws ValidatorException {
  CreditCardType ccType =
      (CreditCardType)creditCardTypeInput.getValue();
  Boolean useCC = customer.getUseCreditCard();
  if (useCC != null && useCC && ccType != null) {
    String ccNumber = (String)value;
    int length;
    if (ccType == CreditCardType.CARD_A) length = 4;
    else length = 5;
    if (!ccNumber.matches("
d{" + length + "}")) {
      String msgText = MessageFormat.format(
          "Card number must consist of {0} digits.", length);
      FacesMessage msg = new FacesMessage(
          FacesMessage.SEVERITY_ERROR, msgText, null);
      throw new ValidatorException(msg);
    }
  }
}

2.13 Nachrichten

Der Begriff Nachricht bezeichnet in JSF eine Instanz der Klasse javax.faces.application.FacesMessage , die intern für Meldungen aller Art verwendet wird. Nachrichten werden während der Ausführung des Lebenszyklus oder von der Applikation erzeugt und in eine Queue im Faces-Context eingefügt. Gibt es h:messages - oder h:message -Komponenten auf der Seite, werden die Meldungen gerendert.
Ein Beispiel für die Verwendung von Nachrichten im Lebenszyklus von JSF: Tritt bei der Konvertierung oder Validierung ein Fehler auf, erstellt die betroffene Komponente eine Nachricht und erklärt sich als ungültig. In diesem Fall springt die Ausführung des Lebenszyklus nach der Konvertierung und Validierung direkt zur Render-Response-Phase und die Seite wird mit den Fehlermeldungen erneut angezeigt.
Eine Nachricht kann eine Übersichtsmeldung und Detailinformation aufweisen. Nachrichten müssen aber nicht immer Fehlermeldungen sein. JSF definiert folgende Schweregrade für FacesMessage -Instanzen:
In MyGourmet 6 haben wir Nachrichten in den benutzerdefinierten Konvertern oder Validatoren verwendet. Hier als Beispiel der Code, der eine Fehlermeldung für ein Geburtsdatum in der Zukunft erstellt:
if (date.after(new Date())) {
  FacesMessage msg = new FacesMessage(
      FacesMessage.SEVERITY_ERROR,
      "Birthday is in the future.", null);
  throw new ValidatorException(msg);
}
Nachrichten können aber auch beim Abarbeiten der Applikationslogik über den Aufruf der Methode FacesContext.addMessage() hinzugefügt werden.
Globale und lokale Nachrichten: Im obigen Beispiel wird die Nachricht an den Konstruktor der übergeben. Es handelt sich hier um eine lokale Nachricht. Die Nachricht wird beim Abfangen der Exception in JSF direkt mit der betroffenen Komponente verbunden. Wo fällt das ins Gewicht? Wir haben das schon bei den Nachrichtenkomponenten selbst besprochen - für die Ausgabe der Nachrichten gibt es zwei Tags : Das eine ist das h:messages -Tag, das alle Nachrichten, also sowohl zu einer Komponente gehörende als auch globale Nachrichten, anzeigt, das andere ist das <h:message for=" komponentenId" /> -Tag, das nur die Nachrichten einer gewissen Komponente darstellt. Mit dem ersten Parameter der addMessage() -Methode kann also beeinflusst werden, in welchen Nachrichtenbereichen die Nachricht erscheinen wird.
Die Tags h:messages und h:message besitzen die Attribute show-Summary und showDetail . Mit diesen Attributen kann eingestellt werden, ob die Detailinformation oder die Übersichtsmeldung der Nachricht angezeigt wird. Seit Version 1.2 von JSF gibt es für Eingabekomponenten zusätzlich die Attribute converterMessage , requiredMessage und validatorMessage . Mit ihnen lassen sich gezielt Fehlermeldungen einzelner Komponenten mit benutzerdefinierten Zeichenketten überschreiben. Die Namen sind selbsterklärend. Ebenfalls seit JSF 1.2 gibt es das Attribut label für Eingabekomponenten, dessen Wert bei Fehlermeldungen statt der ID verwendet wird.
Beim Arbeiten mit JSF ist es sinnvoll, ein h:messages -Tag auf der Seite zu haben. Tritt nämlich bei der Konvertierung und Validierung einer Seite ein Fehler auf, wird das Modell nicht verändert und die Applikationslogik nicht ausgeführt - es kann also auch nicht auf eine neue Seite navigiert werden. Ohne das h:messages -Tag ist das Auftreten von Fehlern aber für Benutzer (und natürlich auch für Applikationsentwickler) nicht nachvollziehbar.
Ab JSF 2.0 wird automatisch eine h:messages -Komponente in jeder Seite eingefügt, falls diese noch nicht vorhanden ist - allerdings nur, wenn die Project-Stage auf Development gesetzt ist (wie in Abschnitt Sektion:  Project-Stage gezeigt).
In JSF-Versionen vor 2.0 müssen Sie das h:messages -Tag immer selbst in die Seite einbauen.
Der Text der Nachricht wird im Beispiel oben direkt in englischer Sprache angegeben. Nachrichten, die dem Benutzer angezeigt werden, sollten aber auf jeden Fall internationalisiert sein. Abschnitt [Sektion:  Internationalisierung] zeigt, wie das funktioniert.

2.14 Internationalisierung

Ein wichtiges Thema bei der Entwicklung von Webapplikationen ist die Internationalisierung. Dazu gehört einerseits das Speichern von Zeichenketten unabhängig von der Applikation, damit diese Zeichenketten von Übersetzern in andere Sprachen übertragen werden können. Andererseits kann es je nach Region zu einer anderen Konvertierung von Datumswerten, Zahlen und Währungsangaben kommen. JSF ist dafür gerüstet und macht die Lokalisierung Ihrer Anwendung zum Kinderspiel.

2.14.1 Ermittlung des Lokalisierungscodes

Beim Ausführen einer JSF-Anwendung läuft diese für jeden Anwender mit einem bestimmten Lokalisierungscode, auch Locale genannt. Das Locale setzt sich aus zwei Angaben zusammen. Einerseits wird mit dem Code die Sprache festgelegt, in der die Anwendung ablaufen soll, andererseits erfolgt die Festlegung eines Staates Es gibt erhebliche Unterschiede in der Anwendung ein- und derselben Sprache in verschiedenen Staaten.: . Manchmal wird auch in einer dritten Ebene eine Einteilung vorgenommen, beispielsweise nach Dialekten innerhalb der Sprache eines Staates.
Die Definition der unterstützten Lokalisierungscodes einer Applikation erfolgt in der faces-config.xml -Datei. Listing Konfiguration für Internationalisierung zeigt beispielhaft eine Konfiguration. Die solcherart konfigurierte Applikation unterstützt die Sprachen Deutsch , Englisch für die USA, Englisch für Großbritannien und Französisch .
<application>
  <locale-config>
    <default-locale>en</default-locale>
    <supported-locale>de</supported-locale>
    <supported-locale>en_US</supported-locale>
    <supported-locale>en_GB</supported-locale>
    <supported-locale>fr</supported-locale>
  </locale-config>
  <message-bundle>
    at.irian.jsfatwork.messages
  </message-bundle>
</application>
Lokalisierung im Request: Welcher Lokalisierungscode jetzt tatsächlich ausgewählt wird, liegt am Benutzer, der sich mit dem System verbindet. Der Browser des Benutzers sendet eine HTTP-Kopfzeile mit der gewünschten Lokalisierung. Wenn die Anwendung diese Lokalisierung unterstützt, wird sie ausgewählt - ansonsten kommt die Standardeinstellung zum Einsatz.
Lokalisierung im View: Will man die Auswahl eines Locales nicht von der HTTP-Anfrage abhängig machen Beispiel: Ein deutscher Benutzer sitzt an einem Webclient in einem französischen Internetcafé und will die Benutzerschnittstelle natürlich in seiner Sprache sehen.: , kann der Lokalisierungscode auf einer Seite über die Angabe des locale -Attributs am f:view -Tag zentral gesetzt werden. Dieses Attribut kann dynamisch an die Eigenschaft einer Managed-Bean gebunden werden. Ein Beispiel:
<f:view locale="#{userBean.userLocale}">
    ...
</f:view>

2.14.2 Internationalisierung der JSF-Nachrichten

Durch den Eintrag message-bundle in der faces-config.xml wird festgelegt, wo die Datei für die Nachrichten der Applikation zu finden ist. Es sind also für die Konfiguration aus Listing Konfiguration für Internationalisierung folgende Dateien im Package at.irian.jsfatwork notwendig, um alle angegebenen Sprachen abzudecken:
In diesen Dateien können Sie die Texte für Nachrichten verwalten, die Ihre Anwendung in selbst erstellten Konvertern, Validatoren oder auch Bean-Methoden erzeugt. Des Weiteren können die vom System verwendeten Nachrichten überschrieben werden, wenn das benötigt wird.
Nachdem die Bundles jetzt konfiguriert und erstellt sind, wollen wir sie auch einsetzen. Im Beispiel MyGourmet 6 haben wir in den benutzerdefinierten Validierungsmethoden im Fehlerfall Nachrichten erzeugt. Der Text ist dort allerdings noch direkt angegeben. Um das zu ändern, erstellen wir eine Hilfsmethode, die eine Instanz der Klasse FacesMessage mit einem lokalisierten Text erzeugt. Listing Internationalisierte Nachrichten in der Backing-Bean zeigt diese Methode.
public static FacesMessage getFacesMessage(
    FacesContext ctx, FacesMessage.Severity severity,
    String msgKey, Object... args) {
  Locale loc = ctx.getViewRoot().getLocale();
  ResourceBundle bundle = ResourceBundle.getBundle(
      ctx.getApplication().getMessageBundle(), loc);
  String msg = bundle.getString(msgKey);
  if (args != null) {
    MessageFormat format = new MessageFormat(msg);
    msg = format.format(args);
  }
  return new FacesMessage(severity, msg, null);
}
Der Code ist einfach: Die Methode getMessageBundle() liefert den Namen des Bundles - der View-Root das aktuelle Locale. Mit diesen beiden Informationen wird das konkrete Bundle geladen - daraus wird der Text für den angegebenen Schlüssel ausgelesen. Dieser Text wird dann beim Erstellen der Nachricht als Übersichtsmeldung verwendet. Die Detailinformation wird hier nicht angegeben.
Nachrichten, die von Standardkomponenten erzeugt werden, bieten immer eine Übersichtsmeldung und eine Detailinformation. In der .properties -Datei existiert daher ein Eintrag mit einem an den Schlüssel angehängten Suffix _detail .
Wenn Sie Apache MyFaces einsetzen, können Sie auch die Klasse org.apache.myfaces.shared_impl.util.MessageUtils zum Anlegen von lokalisierten Nachrichten verwenden. Mit dieser Klasse haben Sie auch den Vorteil, dass Detailinformationen nach dem oben genannten Schema automatisch ausgelesen werden.

2.14.2.1 Nachrichten für Bean-Validation

Wenn Sie eigene Constraints für Bean-Validation erstellen, sollten Sie auch internationalisierte Nachrichten verwenden. Bean-Validation ist ein von JSF unabhängiges System - daher werden die Texte an anderer Stelle definiert. Fehlermeldungen, die in geschwungenen Klammern stehen, werden im Resource-Bundle ValidationMessages aufgelöst. Der Text {validator.msg} wird zum Beispiel als Schlüssel für das Resource-Bundle interpretiert.
Der Bean-Validator erstellt JSF-Nachrichten standardmäßig in einem etwas anderen Format als andere JSF-Validatoren. Während bei anderen Validatoren die Fehlermeldung mit dem Label der Eingabekomponente und einem Doppelpunkt beginnt, gibt der Bean-Validator den Text aus, der von der Bean-Validation-API zurückkommt. Dieses Verhalten kann einfach geändert werden. Sie müssen dazu die Message-Format-Vorlage des Bean-Validators im Message-Bundle der Applikation austauschen. Fügen Sie dort die Zeilen aus Listing Bean-Validator-Nachrichten hinzu. Der Platzhalter {0} bezeichnet dabei die eigentliche Fehlermeldung und {1} das Label.
javax.faces.validator.BeanValidator.MESSAGE={1}: {0}
javax.faces.validator.BeanValidator.MESSAGE_detail=
    {1}: {0}
Eine ausführlichere Beschreibung der Internationalisierung von MyGourmet mit Beispielen folgt in Abschnitt [Sektion:  MyGourmet 7: Internationalisierung] .

2.14.3 Internationalisierung der Anwendungstexte

Für die anderen Texte in Applikationen (etwa für Beschreibungen oder Labels) ist die Vorgehensweise etwas anders - seit JSF 1.2 gibt es zwei verschiedene Varianten, ein Resource-Bundle zu referenzieren.
Die erste Methode ist das Tag f:loadBundle , mit dem ein Resource-Bundle für die Verwendung auf der gleichen Seite verfügbar gemacht wird. Dieses Tag sollte nicht mehr verwendet werden, da es die internationalisierten Daten erst ab der Render-Response-Phase zur Verfügung stellt.
Diese Probleme lassen sich mit der neueren Methode vermeiden. Dabei wird das Resource-Bundle in der faces-config.xml mit dem Element resource-bundle im Abschnitt application referenziert. Listing Internationalisierung durch Konfiguration mit resource-bundle zeigt, wie das Beispiel von vorhin mit dieser Methode umgesetzt wird mit den Vorteilen, dass die Texte in allen Seiten verfügbar sind und dass sie auch mit Ajax funktionieren.
<resource-bundle>
  <base-name>de.test.resource.text</base-name>
  <var>text</var>
</resource-bundle>
Der Zugriff auf die Elemente des Resource-Bundles ist für beide Methoden identisch und wird über eine Value-Expression im Attribut value erledigt. Ein Beispiel:
<h:outputText value="#{text.description}"/>
Für den ersten Teil der Value-Expression wird der Name der für die .properties -Datei vergebenen Variablen eingesetzt und als Attributbezeichnung der Schlüssel des Eintrags in der Datei. Die in JSF verborgene Kraft zeigt sich dadurch, dass man diesen Ausdruck in fast jedem Attribut jedes Tags einsetzen kann - nicht nur im value -Attribut.
Für die Internationalisierung von Anwendungen kommt dem h:outputFormat -Tag eine besondere Bedeutung zu. Der entscheidende Vorteil gegenüber h:outputText ist die Unterstützung von Message-Format-Vorlagen. Dadurch wird es möglich, Texte mit Platzhaltern bereits im Resource-Bundle der Anwendung zu definieren. Die Position der Platzhalter kann dabei sogar an die jeweilige Sprache angepasst werden - ein wichtiger Schritt in Richtung Wartbarkeit und zentraler Internationalisierung von Texten.
Im folgenden Beispiel wird eine Statusmeldung für das Profil des Kunden mit Parametern ausgegeben:
<h:outputFormat value="#{msgs.profile_msg}" rendered=
    "#{customerBean.customer.firstName != null}">
  <f:param value="#{customerBean.customer.firstName}"/>
  <f:param value="#{msgs.profile_active}"/>
</h:outputFormat>
Im deutschen Resource-Bundle könnten profile_msg und profile_active wie folgt definiert sein:
profile_msg=Ihr Profil ist {1}, {0}.
profile_active=aktiv
Bei der englischen Übersetzung könnten sich folgende Zeichenketten ergeben:
profile_msg={0}, your profile is {1}.
profile_active=active
Die Anpassung des Beispiels an verschiedene Sprachen spielt sich nur im Resource-Bundle der Applikation ab. Mit dem direkten Einsatz der OutputText -Komponente ist ein solch flexibles Vorgehen unmöglich.
Zugriff von der Backing-Bean: Wie in der Seitendeklaration kann auch vom Backing-Bean-Code aus auf die internationalisierten Texte zugegriffen werden. Vor JSF 1.2 gab es dafür keine spezifische Lösung - Java stellt den dazu benötigten Code zur Verfügung. In Version 1.2 hat sich das geändert. Mit resource-bundle eingebundene Texte können jetzt mit Applica-tion.getResourceBundle() direkt über ihren Namen aufgelöst werden. Listing Internationalisierte Texte in der Backing-Bean zeigt eine Hilfsmethode, um Texte aus einem Resource-Bundle zu laden.
public static String getResourceText(FacesContext ctx,
    String bundleName, String key, Object... args) {
  String text;
  try {
    Application app = ctx.getApplication();
    ResourceBundle bundle = app.getResourceBundle(
        ctx, bundleName);
    text = bundle.getString(key);
  } catch (MissingResourceException e) {
    return "???" + key + "???";
  }
  if (args != null) {
    text = MessageFormat.format(text, args);
  }
  return text;
}
Das Beispiel MyGourmet 7 im nächsten Abschnitt zeigt den praktischen Einsatz von Message- und Resource-Bundle.

2.14.4 MyGourmet 7: Internationalisierung

Das Beispiel MyGourmet 7 entspricht in der Funktionalität genau dem Vorgängerbeispiel MyGourmet 6 aus Abschnitt [Sektion:  MyGourmet 6: Validierung] . Die Änderungen beziehen sich nur auf die Internationalisierung der Anwendung.
Der erste Schritt ist die Konfiguration der Lokalisierung in der Datei faces-config.xml . In unserem Fall ist Deutsch als Standardwert und Englisch als unterstützte Sprache konfiguriert (Listing MyGourmet 7: Konfiguration ).
<application>
  <locale-config>
    <default-locale>de</default-locale>
    <supported-locale>en</supported-locale>
  </locale-config>
  <message-bundle>
    at.irian.jsfatwork.messages
  </message-bundle>
  <resource-bundle>
    <base-name>
      at.irian.jsfatwork.messages
    </base-name>
    <var>msgs</var>
  </resource-bundle>
</application>
Die Resource-Bundles sind im Package at.irian.jsfatwork unter dem Namen messages abgelegt - ein Bundle mit dem Namen messages_de für die deutsche Sprache und ein Bundle mit dem Namen messages_en für die englische Sprache. Durch unsere Konfiguration des resource-bundle -Elements ist das Bundle unter dem Namen msgs in allen Seiten der Anwendung in EL-Ausdrücken verwendbar. In Listing Auszug aus dem deutschen Bundle von MyGourmet finden Sie einen Auszug aus dem Bundle messages_de.properties für die deutsche Sprache.
title_main=MyGourmet
title_show_customer=Kunde
title_edit_customer=Kunde bearbeiten
first_name=Vorname
last_name=Nachname
email=E-Mail-Adresse
birthday=Geburtstag
use_credit_card=Kreditkarte angeben
credit_card_type=Kreditkarten-Typ
credit_card_number=Kreditkarten-Nummer
profile_msg=Ihr Profil ist {1}, {0}.
profile_active=aktiv
credit_card_type_CARD_A=Card A
credit_card_type_CARD_B=Card B
validateBirthday.MAXIMUM=Geburtsdatum liegt in der Zukunft.
validateBirthday.MINIMUM=Geburtsdatum ist vor dem 1.1.1900.
validateCreditCardNumber.NUMBER=Kreditkartennummer 
    muss aus {0} Ziffern bestehen.
javax.faces.validator.BeanValidator.MESSAGE={1}: {0}
Der Zugriff auf die lokalisierten Texte soll exemplarisch anhand des Vornamens des Kunden demonstriert werden. Alle anderen Texte in der Seite werden analog behandelt. Hier das lokalisierte Label für den Vornamen:
<h:outputLabel for="firstName" value="#{msgs.first_name}:"/>
Es referenziert im value -Attribut einen EL-Ausdruck, der sich aus dem Namen des Bundles und dem Schlüssel für den Vornamen zusammensetzt.
Fallweise ist es notwendig, direkt in Java auf lokalisierte Ressourcen zuzugreifen. Die Klasse GuiUtil fasst zu diesem Zweck die Methode zum Auslesen eines lokalisierten Textes aus Listing Internationalisierte Texte in der Backing-Bean und die Methode zum Erstellen einer lokalisierten Nachricht aus Listing Internationalisierte Nachrichten in der Backing-Bean zusammen. In MyGourmet kommt GuiUtil in CustomerBean zum Einsatz. Listing Einsatz von GuiUtil in MyGourmet zeigt exemplarisch zwei Methoden aus dieser Managed-Bean.
public void validateCreditNumber(FacesContext ctx,
    UIComponent component,
    Object value) throws ValidatorException {
  CreditCardType ccType =
      (CreditCardType)creditCardTypeInput.getValue();
  Boolean useCC = customer.getUseCreditCard();
  if (useCC != null && useCC && ccType != null) {
    String ccNumber = (String)value;
    int length;
    if (ccType == CreditCardType.CARD_A) length = 4;
    else length = 5;
    if (!ccNumber.matches("
d{" + length + "}")) {
      FacesMessage msg = GuiUtil.getFacesMessage(
          ctx, FacesMessage.SEVERITY_ERROR,
          "validateCreditCardNumber.NUMBER", length);
      throw new ValidatorException(msg);
    }
  }
}

private String getCCTypeLabel(CreditCardType type) {
  FacesContext c = FacesContext.getCurrentInstance();
  String key = "credit_card_type_" + type.toString();
  return GuiUtil.getResourceText(c, "msgs", key);
}
Die Methode validateCreditNumber ist bereits aus MyGourmet 6 bekannt: Im Unterschied zur vorherigen Version wird hier im Fehlerfall eine lokalisierte Nachricht erstellt. Der Schlüssel des verwendeten Texts lautet validateCreditCardNumber.NUMBER . Ein Blick auf Listing Auszug aus dem deutschen Bundle von MyGourmet zeigt, dass dieser Text eine Message-Format-Vorlage mit einem Platzhalter ist. Beim Erzeugen der Nachricht wird dieser Platzhalter mit der Länge der Kreditkartennummer ersetzt.
Die Methode getCCTypeLabel liefert den lokalisierten Namen für Felder der Enum CreditCardType zurück.
Die Resource-Bundles für Bean-Validation sind unter dem Namen ValidationMessages abgelegt - ein Bundle mit dem Namen ValidationMessages_de für die deutsche Sprache und ein Bundle mit dem Namen ValidationMessages_en für die englische Sprache.