Handhabung von Formulareingaben mit JSF 2.2

Mit JavaServer Faces können bequem und leicht Formulare verwaltet werden. Als Beispiel soll ein Formular zur Registrierung eines Benutzers dienen. Damit die Eingaben in einem Formular serverseitig ausgewertet werden können, braucht jedes JSF-Formular neben seiner XHTML-Ansicht auch eine Instanz, an welche die Eingabedaten gekoppelt werden können. In Java EE 5 wurde eine solche Instanz als Backing Bean bezeichnet. Mit Java EE 6 hat man die Backing Bean dann als eine Variante der Managed Bean untergeordnet und mit Java EE 7 wird in Zukunft alles unter den Begriff Web Beans bzw. CDI fallen. Bevor wir uns aber mit CDI (Contexts and Dependency Injection) auseinandersetzen, möchte ich kurz zeigen, wie das Zusammenspiel aus Managed Bean und JSF-Formular aussieht.

Formulareingaben mit JSF verarbeiten

XHTML

<h:form>
  <label for="user-name">Name</label>
  <h:inputText id="user-name" value="#{userBackingBean.name}" p:placeholder="John Doe" />
 
  <label for="user-email">Email</label>
  <h:inputText id="user-email" value="#{userBackingBean.email}" p:placeholder="Email" />
 
  <label for="user-password">Password</label>
  <h:inputText id="user-password" value="#{userBackingBean.password}" p:placeholder="Password" />
 
  <h:commandButton value="Register" action="#{userBackingBean.submit}" />  
</h:form>

UserBackingBean.java

package com.welovecoding.web.registration;
 
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
 
@ManagedBean
@SessionScoped
public class UserBackingBean implements Serializable {
 
  private final static Logger LOGGER = Logger.getLogger(UserBackingBean.class.getSimpleName());
 
  private String name;
  private String email;
  private String password;
 
  public UserBackingBean() {
  }
 
  public void submit() {
    String template = "Name: {0},  Email: {1}, Password: {2}";
    Object[] values = new Object[]{
      this.name,
      this.email,
      this.password
    };
    LOGGER.log(Level.INFO, template, values);
  }
 
  public String getName() {
    return name;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public String getEmail() {
    return email;
  }
 
  public void setEmail(String email) {
    this.email = email;
  }
 
  public String getPassword() {
    return password;
  }
 
  public void setPassword(String password) {
    this.password = password;
  }
 
}

Der hier gezeigt Code sorgt dafür, dass nachdem Absenden des Formulars alle eingegebenen Daten in der Konsole des ausführenden Anwendungsservers (z.B. GlassFish 4) geloggt werden.

Deprecated Managed Beans

Ab Java EE Version 7 warnt der Compiler für dem Einsatz der Managed Bean-Annotation mit der Meldung: „Annotations from the package javax.faces.bean will be deprecated in the next JSF version. CDI ones are recommended instead“. In diesem Fall muss man als Programmierer nur die @ManagedBean-Deklaration gegen @Named aus dem Paket javax.inject ersetzen.

Mit JSF versendete Formulardaten in einer Datenbank speichern

Zur Speicherung der Daten, die über ein JSF-Formular gesendet wurden, werden vier Akteure benötigt:

  • Das JSF-Formular zur Eingabe der Daten
  • Eine Backing Bean zur Bindung der Formulardaten
  • Eine Entity, welche die empfangenen Daten auf einer Datenbank abbildet
  • Ein Repository, welches über eine Persistence Unit die Operationen auf der Datenbank ausführt

Der Verarbeitungsablauf sieht dann wie folgt aus: Das JSF-Formular steht mit der Backing Bean in Verbindung und überliefert alle Eingabedaten an die Backing Bean. Die Backing Bean wiederum validiert die Eingabedaten (im Beispiel mit der Methode validate) und erstellt bei einer gültigen Eingabe eine Instanz der Klasse UserEntity, welche unsere Geschäftsdaten für eine Benutzerregistration auf unserer Datenbank abbildet. Die UserEntity wird dann über das UserRepository in der Datenbank gespeichert. Nach erfolgreicher Speicherung gibt dann die UserBackingBean in der Methode submit eine JSF-Zeichenkette zurück, welche einen Webseiten-Redirect auslöst.

Weil das UserRepository über die Annotation @Stateless zu einer Enterprise JavaBean (EJB) geworden ist, kann es über die Annotation @EJB in der UserBackingBean verwendet werden.

UserBackingBean.java

package com.welovecoding.web.registration;
 
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
 
@Named
@SessionScoped
public class UserBackingBean implements Serializable {
 
  @EJB
  private UserRepository userRepository;
 
  private final static Logger LOGGER = Logger.getLogger(UserBackingBean.class.getSimpleName());
 
  private String name;
  private String email;
  private String password;
 
  public UserBackingBean() {
  }
 
  public String submit() {
    String redirect = "/error?faces-redirect=true";
 
    String template = "Name: {0},  Email: {1}, Password: {2}";
    Object[] values = new Object[]{
      this.name,
      this.email,
      this.password
    };
    LOGGER.log(Level.INFO, template, values);
 
    boolean isValid = validate();
    if (isValid) {
      UserEntity entity = new UserEntity(name, email, password);
      userRepository.save(entity);
      LOGGER.log(Level.INFO, "Saved user.");
      redirect = "/success?faces-redirect=true";
    }
 
    return redirect;
  }
 
  public boolean validate() {
    return name.length() > 1 && email.length() > 1 && password.length() > 1;
  }
 
  public String getName() {
    return name;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public String getEmail() {
    return email;
  }
 
  public void setEmail(String email) {
    this.email = email;
  }
 
  public String getPassword() {
    return password;
  }
 
  public void setPassword(String password) {
    this.password = password;
  }
 
}

UserEntity.java

package com.welovecoding.web.registration;
 
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
@Entity
@Table(name = "users")
public class UserEntity implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
 
  @NotNull
  @Size(min = 1, max = 255)
  @Column
  private String name;
 
  @NotNull
  @Column
  private String email;
 
  @NotNull
  @Column
  private String password;
 
  public UserEntity() {
  }
 
  public UserEntity(String name, String email, String password) {
    this.name = name;
    this.email = email;
    this.password = password;
  }
 
  public Integer getId() {
    return id;
  }
 
  public void setId(Integer id) {
    this.id = id;
  }
 
  public String getName() {
    return name;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public String getEmail() {
    return email;
  }
 
  public void setEmail(String email) {
    this.email = email;
  }
 
  public String getPassword() {
    return password;
  }
 
  public void setPassword(String password) {
    this.password = password;
  }
 
}

UserRepository.java

package com.welovecoding.web.registration;
 
import com.welovecoding.web.config.Names;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
 
@Stateless
public class UserRepository {
  @PersistenceContext(unitName = "your-Persistence-Unit")
  EntityManager em;
 
  public UserRepository() {
  }
 
  public void save(UserEntity user) {
    em.persist(user);
    em.flush();
  }
}

Hinweis: Sollte die Datenbank-Tabelle „users“ der UserEntity noch nicht vorhanden sein, so kann sie automatisch bei einem Aufruf erstellt werden. Dazu muss man nur die „Table Generation Strategy“ der Webapplikation ändern. Das tut man in der Datei „webapp/src/main/resources/META-INF/persistence.xml“ mit einem Eintrag für javax.persistence.schema-generation.database.action.

Beispiel:

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" 
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <!-- Persistence Unit for MySQL -->
  <persistence-unit name="com.welovecoding.web_wlc-webapp_war_1.0-SNAPSHOTPU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/welovecoding</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="javax.persistence.schema-generation.database.action" value="create"/>
    </properties>
  </persistence-unit>
</persistence>

Eingabefehler anzeigen

Um eine Meldung bei einer fehlerhaften Eingabe anzuzeigen, muss ein h:message in der XHTML-Seite eingefügt werden. Das h:message referenziert dann über sein for-Attribut die id des entsprechenden Eingabefelds. Sofern ein h:message-Feld vorliegt, kann dann über die Backing Bean in das Feld über die Angabe der clientId geschrieben werden. Die clientId setzt sich zusammen aus der ID des Formulars und der ID des Eingabefelds verbunden durch einen Doppelpunkt. Beispiel: formId:inputId. Ein Code-Beispiel soll diese Zuordnung verdeutlichen:

JSF-Formular

<h:form id="register-user-form">
  <label for="user-name">Name</label>
  <h:inputText id="user-name" value="#{userBackingBean.name}" p:placeholder="John Doe" />
  <h:message for="user-name" />
 
  <label for="user-email">Email</label>
  <h:inputText id="user-email" value="#{userBackingBean.email}" p:placeholder="Email" />
  <h:message for="user-email" />
 
  <label for="user-password">Password</label>
  <h:inputText id="user-password" value="#{userBackingBean.password}" p:placeholder="Password" />
  <h:message for="user-password" />
 
  <h:commandButton value="Register" action="#{userBackingBean.submit}" />  
</h:form>

UserBackingBean.java

...
 
@Named
@SessionScoped
public class UserBackingBean implements Serializable {
 
  @EJB
  private UserRepository userRepository;
 
  private final static Logger LOGGER = Logger.getLogger(UserBackingBean.class.getSimpleName());
 
  private String name;
  private String email;
  private String password;
 
  public UserBackingBean() {
  }
 
  public String submit() {
    String formId = "register-user-form";
    String inputId = "user-name";
    String clientId = formId + ":" + inputId;
    String message = "Error: Your username is too short.";
 
    FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(message));
 
    return "";
  }
...
}

Hinweis: Die Rückgabe einer leeren Zeichenkette („“) führt in JSF dazu, dass wieder auf Seite weitergeleitet wird, von der das Formular abgeschickt wurde.

Eigene Validierungsmethoden pro Eingabefeld implementieren

Es ist möglich, jedem Eingabefeld eine eigene Validerungsmethode über das validator-Attribut zuzuweisen (siehe JavaServer Faces JSF Validation Tutorial). Wie das geht, zeigt folgender exemplarischer Code:

JSF-Formular

<h:form id="register-user-form">
  <label for="user-name">Name</label>
  <h:inputText id="user-name" 
               value="#{userBackingBean.name}" 
               validator="#{userBackingBean.checkName}"
               p:placeholder="John Doe" 
               />
  <h:message for="user-name" />
 
  <h:commandButton value="Register" action="#{userBackingBean.submit}" />  
</h:form>

UserBackingBean.java

...
 
@Named
@SessionScoped
public class UserBackingBean implements Serializable {
 
  ...
 
  public UserBackingBean() {
  }
 
  public String submit() {
    return "";
  }
 
  public void checkName(FacesContext context, UIComponent component, Object value) {
    String formId = "register-user-form";
    String inputId = "user-name";
    String clientId = formId + ":" + inputId;
    String message = "Error: Your username is too short.";
 
    FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(message));
  }
 
...
}

Hinweis: Die clientId kann auch über die UIComponent bezogen werden. Beispiel:

public void checkName(FacesContext context, UIComponent component, Object value) {
  String message = "Error: Your username is too short."; 
  FacesContext.getCurrentInstance().addMessage(component.getClientId(), new FacesMessage(message));
}

Validierung mit AJAX

Möchte man die checkName-Methode aus dem vorherigen Beispiel nicht erst bei einem Klick auf den h:commandButton machen, sondern sofort, nachdem der Nutzer das Feld ausgefüllt hat, so kann man in JSF 2.2 Gebrauch von AJAX-Requests machen (siehe Ajax Validation).

JSF-Formular

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:p="http://xmlns.jcp.org/jsf/passthrough"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
 
...
 
<h:form id="register-user-form">
  <label for="user-name">Name</label>
  <h:inputText id="user-name" 
               value="#{userBackingBean.name}" 
               validator="#{userBackingBean.checkName}"
               p:placeholder="John Doe">
        <f:ajax event="blur" render="register-user-name-message" />    
  <h:message for="user-name" id="register-user-name-message" />
 
  <h:commandButton value="Register" action="#{userBackingBean.submit}">
    <f:ajax execute="@form" render="@form" />
   </h:commandButton>
</h:form>
 
...

Hinweis: Bei Verwendung von AJAX-Validierungen sollte die Backing Bean die Annotation @ViewScoped (Package: javax.faces.view) haben und nicht @SessionScoped, da sonst bei jedem AJAX-Request eine neue Bean erzeugt wird.

Wer noch die extra Meile gehen möchte, der kann dank der mit JSF 2.2 eingeführten Pass-through Elements die JSF-Tags h:form, h:inputText & Co. durch Standard-HTML5-Elemente ersetzen. Dazu ist aber die Verwendung des Namespaces xmlns:jsf="http://xmlns.jcp.org/jsf" nötig.

TODO:
– Validierung der Eingabedaten mit eigener Annotation

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.