JavaServer Faces (JSF)

+ andere TechDocs
+


JavaServer Faces (JSF) ist ein Web Application Framework und stellt in Java EE den Standard dar, um grafische Oberflächen über einen Java-EE-Webserver anzubieten, die als interaktive HTML-Webseiten im Webbrowser angezeigt werden. JSF 1.1 entstand 2004. Seit 2013 gibt JSF in der Version 2.2.

Diese Webseite soll nur einen Einstieg vermitteln. Vertiefende Informationen finden Sie unter:

Natürlich gibt es viele zu JSF konkurrierende Optionen, zwei besonders bekannte sind GWT und AngularJS:



Inhalt

  1. Konzepte und Begriffe im JSF-Umfeld
  2. Download der Programmierbeispiele und Vorbemerkungen
  3. Hello World mit JSF
  4. GUI-Elemente ein- und ausblenden ohne und mit Ajax
  5. Ajax-gesteuerte Auswahlliste und Seitenverlassen-Warnmeldung
  6. Fehler beim Selektieren in einer JSF-Tabelle
  7. JSF-Tabelle mit korrekter Selektion
  8. JSF-Tabelle mit Mehrfach-Selektion, Neuanlage und Editierung
  9. JSF-Tabelle mit Paginierung, Sortierung und Exportfunktion, via PrimeFaces
  10. JSF-Tabelle mit Paginierung und Sortierung mit Tomahawk
  11. JSF-Tabelle mit Paginierung und Sortierung für sehr große Datenbanktabellen
  12. Templating und Composition
  13. Composite Component minimal
  14. Composite Component mit Facet
  15. Composite Component mit Methodenbindung
  16. Composite Component mit FacesComponent Backing Bean
  17. Confirm Dialog mit PrimeFaces
  18. Implementierungen, Versionen, Session, Classpath, Libs etc. anzeigen



Konzepte und Begriffe im JSF-Umfeld

Die folgenden Erläuterungen stellen nur kurze Erinnerungsstützen und keine exakten Definitionen dar.



Download der Programmierbeispiele und Vorbemerkungen

Die folgenden Programmierbeispiele können entweder wie im Folgenden beschrieben wird, aufgebaut werden, oder alternativ als Zipdatei-Download herunter geladen werden.

Bitte beachten Sie, dass die Beispiele den neuen seit JSF 2.2 gültigen Namespace verwenden. Falls Sie eine ältere JSF-Version einsetzen, müssen Sie überall "xmlns.jcp.org" durch "java.sun.com" ersetzen.

Falls Maven die benötigten Libraries nicht downloaden kann, weil Sie innerhalb eines Firmennetzwerks arbeiten und nur über einen Proxy-Server ins Internet gelangen, beachten Sie die Hinweise unter Zugang zu Internet-Repositories über Firmen-Proxy.



Hello World mit JSF

Dieses Programmierbeispiel zeigt als Einstieg den "Hello JSF World"-Text auf einer per JSF erzeugten HTML-Webseite.

Dabei kann per Maven-Plugin zur Laufzeit automatisch ein Jetty-Webserver als Servlet Container gestartet werden, so dass ohne weitere Installationen die Webseite im Webbrowser aufgerufen werden kann.

  1. Voraussetzung ist ein installiertes JDK mit mindestens der Version 7 und Maven 3.x.

  2. Führen Sie im Kommandozeilenfenster aus:

    md \MeinWorkspace\JsfHelloWorld

    cd \MeinWorkspace\JsfHelloWorld

    md src\main\jetty

    md src\main\webapp\WEB-INF

  3. Erstellen Sie im JsfHelloWorld-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>JsfHelloWorld</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
      <name>JsfHelloWorld</name>
      <build>
        <finalName>${project.artifactId}</finalName>
        <defaultGoal>install</defaultGoal>
        <plugins>
          <plugin>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-maven-plugin</artifactId>
            <version>9.2.6.v20141205</version>
            <configuration>
              <webAppConfig>
                <contextPath>/${project.artifactId}</contextPath>
                <overrideDescriptor>src/main/jetty/override-mojarra-web.xml</overrideDescriptor>
              </webAppConfig>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
              <source>1.7</source>
              <target>1.7</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-api</artifactId>
          <version>2.2.8</version>
        </dependency>
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-impl</artifactId>
          <version>2.2.8</version>
        </dependency>
      </dependencies>
    </project>
    
  4. Erstellen Sie im src\main\webapp\WEB-INF-Verzeichnis die Deployment-Descriptor-Datei: web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
      <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
      </servlet-mapping>
    </web-app>
    
  5. Erstellen Sie im src\main\jetty-Verzeichnis die Jetty/Mojarra-Konfigurationsdatei: override-mojarra-web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
      <listener>
        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
      </listener>
    </web-app>
    

    Erläuterungen hierzu finden Sie unter JSF 2 on Jetty 8, Jetty/Howto/Configure, Could not find Factory und JSF 2.2 and CDI running on Jetty/Tomcat.

  6. Erstellen Sie im src\main\webapp-Verzeichnis die Facelets-XHTML-Datei: hello.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
      <title>Hello JSF World</title>
    </head>
    <body>
      <h3><h:outputText value="Hello JSF World" /></h3>
      <p> <h:outputText value="Host: #{header['Host']}" /><br/>
          <h:outputText value="User-Agent: #{header['User-Agent']}" /></p>
    </body>
    </html>
    

    "<h:..." sind Tags aus der HTML Tag Library. "#{...}" sind EL-Ausdrücke. "header" ist ein JSP Implicit Object. "Host" und "User-Agent" sind HTTP-Headerfelder. Außer den JSP Implicit Objects gibt es noch weitere JSF Implicit Objects.

  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfHelloWorld

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfHelloWorld]
     |- [src]
     |   '- [main]
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- hello.xhtml
     '- pom.xml
    
  8. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfHelloWorld

    mvn jetty:run

    start http://localhost:8080/JsfHelloWorld/hello.xhtml

    Die Ausgabe im Webbrowser beginnt mit: Hello JSF World:

    JsfHelloWorld-Webseite
  9. Um die Web-Anwendung in beliebigen anderen Servlet Containern, Java-Webservern oder Java EE Application Servern (die mindestens Servlet 3.0 unterstützen) zu deployen, erzeugen Sie die WAR-Datei JsfHelloWorld.war im target-Unterverzeichnis und deployen sie in Ihren Java-Webserver, beispielsweise so:

    cd \MeinWorkspace\JsfHelloWorld
    mvn package
    copy target\JsfHelloWorld.war \JBoss\server\default\deploy [JBoss]
    copy target\JsfHelloWorld.war \GlassFish\glassfish\domains\domain1\autodeploy [GlassFish]
    copy target\JsfHelloWorld.war \WebLogic\user_projects\domains\MeineDomain\autodeploy [Oracle WebLogic]
    copy target\JsfHelloWorld.war \Geronimo\deploy [Geronimo]

    (Alternativ können Sie statt per copy-Kommando mit Hilfe des cargo-Plug-ins über "mvn cargo:deployer-deploy" deployen.)



GUI-Elemente ein- und ausblenden ohne und mit Ajax

Dieses Programmierbeispiel zeigt zwei Varianten, wie GUI-Elemente ein- und ausgeblendet werden können:

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist das vorherige Programmierbeispiel Hello World mit JSF (es werden die drei Dateien pom.xml, web.xml und override-mojarra-web.xml benötigt).

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfHelloWorld JsfAusblendenOhneUndMitAjax\ /S

    cd JsfAusblendenOhneUndMitAjax

    del src\main\webapp\hello.xhtml

    md src\main\java\de\meinefirma\meinprojekt\backingbeans

  3. Ersetzen Sie im JsfAusblendenOhneUndMitAjax-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfAusblendenOhneUndMitAjax".

  4. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Backing-Bean-Datei: AusblendenBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.ViewScoped;
    import javax.faces.context.FacesContext;
    import javax.faces.event.ValueChangeEvent;
    
    // Backing Bean fuer ausblenden.xhtml
    @ManagedBean
    @ViewScoped
    public class AusblendenBean
    {
       private Boolean meineCheckbox1 = Boolean.FALSE;
       private Boolean meineCheckbox2 = Boolean.FALSE;
       private String  inputText = "... ...";
    
       public void checkboxChanged1( ValueChangeEvent vcEvent )
       {
          Boolean chckbx = (Boolean) vcEvent.getNewValue();
          if( chckbx != null ) { meineCheckbox1 = chckbx; }
          FacesContext.getCurrentInstance().renderResponse();
       }
    
       public void checkboxChanged2( ValueChangeEvent vcEvent )
       {
          Boolean chckbx = (Boolean) vcEvent.getNewValue();
          if( chckbx != null ) { meineCheckbox2 = chckbx; }
          FacesContext.getCurrentInstance().renderResponse();
       }
    
       public String getUhrzeit()
       {
          return (new SimpleDateFormat( "HH:mm:ss.SSS" )).format( new Date() ) + " Uhr";
       }
    
       public Boolean getMeineCheckbox1() { return meineCheckbox1; }
       public Boolean getMeineCheckbox2() { return meineCheckbox2; }
       public String  getInputText()      { return inputText; }
       public void setMeineCheckbox1( Boolean meineCheckbox ) { this.meineCheckbox1 = meineCheckbox; }
       public void setMeineCheckbox2( Boolean meineCheckbox ) { this.meineCheckbox2 = meineCheckbox; }
       public void setInputText(      String  inputText     ) { this.inputText = inputText; }
    }
    

    Bitte beachten Sie die Verkürzung des JSF Request Processing Lifecycle durch den Aufruf der FacesContext.getCurrentInstance().renderResponse()-Methode.

  5. Erstellen Sie im src\main\webapp-Verzeichnis die Facelets-XHTML-Datei: ausblenden.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
      <title>GUI-Elemente ein- und ausblenden ohne und mit Ajax</title>
    </h:head>
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
      <h2><h:outputText value="GUI-Elemente ein- und ausblenden ohne und mit Ajax" /></h2>
      <h:messages style="color: red" />
    
      <h:form>
        <h:outputText value="#{ausblendenBean.uhrzeit}: Uhrzeit des letzten vollst&auml;ndigen Requests" />
      </h:form>
    
      <h:form>
        <h3><h:outputText value="Ein- und ausblenden ohne Ajax" /></h3>
        <p><h:selectBooleanCheckbox onclick="this.form.submit()" immediate="true"
                                    value="#{ausblendenBean.meineCheckbox1}"
                                    valueChangeListener="#{ausblendenBean.checkboxChanged1}" />
           <h:outputText value=" Eingabefeld aktivieren (ohne Ajax)" /></p>
        <p><h:inputText  value="#{ausblendenBean.inputText}" rendered="#{ausblendenBean.meineCheckbox1}" />
           <h:outputText value=" Zusatzinfo eingeben"        rendered="#{ausblendenBean.meineCheckbox1}" /></p>
      </h:form>
    
      <h:form>
        <h3><h:outputText value="Ein- und ausblenden mit Ajax" /></h3>
        <p><h:selectBooleanCheckbox value="#{ausblendenBean.meineCheckbox2}"
                                    valueChangeListener="#{ausblendenBean.checkboxChanged2}">
             <f:ajax render="zusatz" />
           </h:selectBooleanCheckbox>
           <h:outputText value=" Eingabefeld aktivieren (mit Ajax)" /></p>
        <h:panelGrid id="zusatz" columns="2">
          <h:inputText  value="#{ausblendenBean.inputText}" rendered="#{ausblendenBean.meineCheckbox2}" />
          <h:outputText value=" Zusatzinfo eingeben"        rendered="#{ausblendenBean.meineCheckbox2}" />
        </h:panelGrid>
      </h:form>
    
    </h:body>
    </html>
    

    Bitte beachten Sie die Verkürzung des JSF Request Processing Lifecycle durch das immediate="true"-Attribut. Beachten Sie außerdem den onclick="this.form.submit()"-Ausdruck, die beiden valueChangeListener="#{...}" und das <f:ajax render="..." />-Tag.

  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfAusblendenOhneUndMitAjax

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfAusblendenOhneUndMitAjax]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- [backingbeans]
     |       |                   '- AusblendenBean.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- ausblenden.xhtml
     '- pom.xml
    
  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfAusblendenOhneUndMitAjax

    mvn jetty:run

    start http://localhost:8080/JsfAusblendenOhneUndMitAjax/ausblenden.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfAusblendenOhneUndMitAjax-Webseite
  8. Klicken Sie die beiden Checkboxen an. Der jeweilige GUI-Ausschnitt wird ein- oder ausgeblendet. Bei der Nicht-Ajax-Variante ändert sich jedes mal die oben angezeigte Uhrzeit, weil dabei die komplette HTML-Seite neu aufgebaut wird. Bei der Ajax-Variante ändert sie sich nicht, weil nur das <h:panelGrid... mit der id="zusatz" neu gerendert wird.



Ajax-gesteuerte Auswahlliste und Seitenverlassen-Warnmeldung

Dieses Programmierbeispiel zeigt zweierlei:

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist das Einführungs-Programmierbeispiel Hello World mit JSF (es werden die drei Dateien pom.xml, web.xml und override-mojarra-web.xml benötigt).

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfHelloWorld JsfAuswahllisteMitAjax\ /S

    cd JsfAuswahllisteMitAjax

    del src\main\webapp\hello.xhtml

    md src\main\java\de\meinefirma\meinprojekt\backingbeans

  3. Ersetzen Sie im JsfAuswahllisteMitAjax-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfAuswahllisteMitAjax".

  4. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Backing-Bean-Datei: AuswahllistenBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.util.*;
    import javax.faces.bean.*;
    
    // Backing Bean fuer auswahllisten.xhtml
    @ManagedBean
    @ViewScoped
    public class AuswahllistenBean
    {
       private static List<Produkt> produkte = new ArrayList<Produkt>();
       static {
          produkte.add( new Produkt( "lst", "Meine Abc-Liste", new String[] { "csv", "xml", "xls", "pdf" }, true ) );
          produkte.add( new Produkt( "rpt", "Mein XY-Report", new String[] { "doc", "pdf" }, true ) );
          produkte.add( new Produkt( "prs", "Meine Präsentation", new String[] { "ppt", "pdf" }, true ) );
          produkte.add( new Produkt( "img", "Meine Übersichtsgrafik", new String[] { "jpg" }, false ) );
       }
       private Produkt produkt;
       private String  formatValue;
       private String  zipValue;
    
       public String getProduktId()
       {
          return ( produkt != null ) ? produkt.id : null;
       }
    
       public void setProduktId( String prodId )
       {
          for( Produkt prod : produkte ) {
             if( prod.id.equals( prodId ) ) {
                produkt = prod;
                break;
             }
          }
       }
    
       public List<Produkt> getListe1()
       {
          return produkte;
       }
    
       public String[] getListe2()
       {
          return ( produkt != null ) ? produkt.formate : new String[] { "zuerst Produkt auswählen" };
       }
    
       public String getFormatValue()
       {
          return formatValue;
       }
    
       public void setFormatValue( String formatValue )
       {
          this.formatValue = formatValue;
       }
    
       public boolean getZipDisabled()
       {
          return produkt == null || !produkt.zipbar;
       }
    
       public String getZipValue()
       {
          if( getZipDisabled() ) { zipValue = "nein"; }
          return zipValue;
       }
    
       public void setZipValue( String zipValue )
       {
          this.zipValue = zipValue;
       }
    
       public boolean getSubmitDisabled()
       {
          return produkt == null;
       }
    
       public String submit()
       {
          System.out.println( "\nSubmit: Produkt=" + produkt.titel + ", Format=" + formatValue + ", Zip=" + zipValue );
          return null;
       }
    
       public String cancel()
       {
          System.out.println( "\nCancel" );
          return "auswahllisten.xhtml";
       }
    
       public static class Produkt
       {
          public String   id;
          public String   titel;
          public String[] formate;
          public boolean  zipbar;
          public Produkt( String id, String titel, String[] formate, boolean zipbar ) {
             this.id      = id;
             this.titel   = titel;
             this.formate = formate;
             this.zipbar  = zipbar;
          }
          public String getId()    { return id; }
          public String getTitel() { return titel; }
       }
    }
    
  5. Erstellen Sie im src\main\webapp-Verzeichnis die Facelets-XHTML-Datei: auswahllisten.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    
    <h:head>
      <title>Ajax-gesteuerte Auswahlliste und Seitenverlassen-Warnmeldung</title>
      <script language="JavaScript" type="text/javascript">
        function seitenwechselWarnung( einschalten ) {
          unloadMessage = "Es wurden Daten geändert, aber noch nicht gespeichert.\nSoll trotzdem die Seite verlassen werden?";
          window.onbeforeunload = einschalten ? function() { return unloadMessage; } : null;
        }
      </script>
    </h:head>
    
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
      <h2><h:outputText value="Ajax-gesteuerte Auswahlliste und Seitenverlassen-Warnmeldung" /></h2>
      <h:messages style="color: red" />
      <h:form>
        <h:panelGrid id="ajaxRendered" columns="1">
    
          <h:outputText value="Produktauswahl (erste Auswahlliste)" style="font-weight:bold" />
          <h:selectOneListbox value="#{auswahllistenBean.produktId}" onchange="seitenwechselWarnung(true)">
            <f:selectItems value="#{auswahllistenBean.liste1}" var="item" itemValue="#{item.id}" itemLabel="#{item.titel}" />
            <f:ajax render="ajaxRendered" />
          </h:selectOneListbox>
    
          <br/><h:outputText value="Ausgabeformat (abhängige zweite Auswahlliste)" style="font-weight:bold" />
          <h:selectOneListbox value="#{auswahllistenBean.formatValue}" onchange="seitenwechselWarnung(true)">
            <f:selectItems value="#{auswahllistenBean.liste2}" />
          </h:selectOneListbox>
    
          <br/><h:outputText value="Zippen" style="font-weight:bold" />
          <h:selectOneRadio value="#{auswahllistenBean.zipValue}" disabled="#{auswahllistenBean.zipDisabled}"
                            layout="pageDirection" onchange="seitenwechselWarnung(true)">
            <f:selectItem itemValue="nein" itemLabel="ungezippt" />
            <f:selectItem itemValue="ja"   itemLabel="gezippt" />
          </h:selectOneRadio>
    
          <br/><h:outputText value="Speichern oder Abbrechen" style="font-weight:bold" />
          <h:panelGrid columns="3">
            <h:commandButton type="submit" value="Speichern" action="#{auswahllistenBean.submit}"
                             disabled="#{auswahllistenBean.submitDisabled}" onclick="seitenwechselWarnung(false)" />
            <h:outputText value=" " />
            <h:commandButton value="Abbrechen" action="#{auswahllistenBean.cancel}" immediate="true"
                             onclick="seitenwechselWarnung(false)" />
          </h:panelGrid>
    
          <br/><h:outputText value="Webseite verlassen per Link" style="font-weight:bold" />
          <p><a href="http://www.google.de">Seite verlassen</a></p>
    
        </h:panelGrid>
      </h:form>
    </h:body>
    </html>
    

    Beachten Sie die drei verschiedenen Arten, um die Inhalte der drei h:selectOne...-Auswahllisten zu definieren:
    a) Über: <f:selectItems value="#{...}" var="..." itemValue="#{...}" itemLabel="#{...}" />
    b) Über: <f:selectItems value="#{...}" />
    c) Über: <f:selectItem itemValue="..." itemLabel="..." />

    Beachten Sie außerdem das Ajax-Kommando <f:ajax render="ajaxRendered" />, sowie die on...="seitenwechselWarnung(...)"-JavaScript-Aufrufe zur Steuerung der Seitenwechselwarnung.

  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfAuswahllisteMitAjax

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfAuswahllisteMitAjax]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- [backingbeans]
     |       |                   '- AuswahllistenBean.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- auswahllisten.xhtml
     '- pom.xml
    
  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfAuswahllisteMitAjax

    mvn jetty:run

    start http://localhost:8080/JsfAuswahllisteMitAjax/auswahllisten.xhtml

    Wählen Sie auf der Webseite ein Produkt und ändern Sie Einstellungen. Beachten Sie:
    a) Die Inhalte der zweiten abhängigen Auswahlliste hängen von der gewählten Einstellung der ersten Auswahlliste ab.
    b) Bei ungespeicherten Änderungen erhalten Sie beim Versuch, die Webseite zu verlassen, eine (Browser-abhängige) Warnmeldung ähnlich zu der folgenden:

    JsfAuswahllisteMitAjax-Webseite
  8. Falls Sie folgende Fehlermeldung erhalten:

    Server Error Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Ungültiges Byte 1 von 1-Byte-UTF-8-Sequenz

    Achten Sie darauf, dass Sie auswahllisten.xhtml mit UTF-8-Character-Encoding speichern.

  9. Ein reines JavaScript-Beispiel zu window.onbeforeunload finden Sie unter: JavaScript-Beispiel: Seitenwechsel-Warnung.



Fehler beim Selektieren in einer JSF-Tabelle

Dieses Programmierbeispiel zeigt die Darstellung einer beliebig langen Liste von Elementen mit mehreren Attributen in einer JSF-Tabelle.

Als Datenelemente werden in diesem einfachen Beispiel zu Beginn ein paar Testdaten erzeugt. Änderungen werden per Datei persistiert. Das Beispiel ist so vorbereitet, dass statt per Datei auch per JPA in eine Datenbank persistiert werden könnte.

Das Wichtigste an diesem Programmierbeispiel ist allerdings folgendes:
Es wird ein häufiger Programmierfehler beim Selektieren von einzelnen Elementen in der Tabelle gezeigt, der fatale Folgen haben kann:
Es kann dazu kommen, dass ungewollt ein falsches Element gelöscht wird!

  1. Voraussetzung ist das Einführungs-Programmierbeispiel Hello World mit JSF (es werden die drei Dateien pom.xml, web.xml und override-mojarra-web.xml benötigt).

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfHelloWorld JsfTabelleMitSelektionsFehler\ /S

    cd JsfTabelleMitSelektionsFehler

    del src\main\webapp\hello.xhtml

    md src\main\java\de\meinefirma\meinprojekt\backingbeans

    md src\main\java\de\meinefirma\meinprojekt\entities

  3. Ersetzen Sie im JsfTabelleMitSelektionsFehler-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfTabelleMitSelektionsFehler".

  4. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\entities die Entity-Datei: MeineEntity.java

    package de.meinefirma.meinprojekt.entities;
    
    import java.io.Serializable;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /** Zu speichernde Daten, kann leicht zu JPA-@Entity erweitert werden */
    public class MeineEntity implements Serializable
    {
       private static final long serialVersionUID = 1L;
       // Technische id, soll unique sein (wie Primary Key):
       private Integer id;
       private String  bezeichnung;
       private Date    datum;
    
       public MeineEntity( int id, String bezeichnung, Date datum )
       {
          this.id = Integer.valueOf( id );
          this.bezeichnung = bezeichnung;
          this.datum = datum;
       }
    
       public Integer getId()          { return id; }
       public String  getBezeichnung() { return bezeichnung; }
       public Date    getDatum()       { return datum; }
       public void    setId(          Integer id )          { this.id = id; }
       public void    setBezeichnung( String  bezeichnung ) { this.bezeichnung = bezeichnung; }
       public void    setDatum(       Date    datum )       { this.datum = datum; }
    
       @Override
       public String toString()
       {
          return "{ id=" + id + ", bezeichnung=" + bezeichnung + ", datum=" + (new SimpleDateFormat( "yyyy-MM-dd" )).format( datum ) + " }";
       }
    
       @Override
       public boolean equals( Object obj )
       {
          if( this == obj ) { return true; }
          if( obj == null ) { return false; }
          if( getClass() != obj.getClass() ) { return false; }
          MeineEntity other = (MeineEntity) obj;
          if( id == null ) { return other.id == null; }
          return id.equals( other.id );
       }
    
       @Override
       public int hashCode()
       {
          return 31 + (( id == null ) ? 0 : id.hashCode());
       }
    }
    
  5. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\entities die DAO-Datei: MeineEntityDao.java

    package de.meinefirma.meinprojekt.entities;
    
    import java.io.*;
    import java.util.*;
    
    /** DAO fuer leichteren Zugriff auf die gespeicherten Datenobjekte */
    public class MeineEntityDao
    {
       private static final String DATEI = "MeineDaten.dat";
    
       @SuppressWarnings("unchecked")
       public static List<MeineEntity> findAll()
       {
          return readObject( List.class, DATEI );
       }
    
       public static void writeAll( List<MeineEntity> entitiesList )
       {
          writeObject( entitiesList, DATEI );
       }
    
       public static void saveOrUpdateEntity( MeineEntity entity )
       {
          List<MeineEntity> entitiesList = findAll();
          if( entitiesList == null || entity == null ) { return; }
          entitiesList.remove( entity );
          entitiesList.add( entity );
          writeAll( entitiesList );
       }
    
       public static List<MeineEntity> createTestDaten()
       {
          List<MeineEntity> entitiesList = new ArrayList<MeineEntity>();
          entitiesList.add( new MeineEntity(  1, "Apfelsinen", new Date() ) );
          entitiesList.add( new MeineEntity(  7, "Birnen",     new Date( (new Date()).getTime() - 1000000000 ) ) );
          entitiesList.add( new MeineEntity( 42, "Tomaten",    new Date( (new Date()).getTime() - 2000000000 ) ) );
          writeAll( entitiesList );
          return entitiesList;
       }
    
       // Primitiver Persistenzmechanismus zum Speichern der Daten (fuer Tests nur in Datei, statt DB per JPA):
    
       public static <T> T readObject( Class<T> clss, String datei )
       {
          if( !(new File( datei )).exists() ) { return null; }
          try( ObjectInputStream in = new ObjectInputStream( new FileInputStream( datei ) ); ) {
             return clss.cast( in.readObject() );
          } catch( ClassNotFoundException | IOException e ) {
             throw new RuntimeException( e );
          }
       }
    
       public static void writeObject( Object obj, String datei )
       {
          try( ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( datei ) ); ) {
            out.writeObject( obj );
            out.flush();
          } catch( IOException e ) {
             throw new RuntimeException( e );
          }
       }
    }
    
  6. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Backing-Bean-Datei: MeineBackingBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.util.*;
    import javax.faces.application.FacesMessage;
    import javax.faces.bean.*;
    import javax.faces.context.FacesContext;
    import de.meinefirma.meinprojekt.entities.*;
    
    /** Achtung: Diese Klasse enthaelt einen Fehler, siehe Text!
        Backing Bean, verknuepft die Daten und die Geschaeftslogik mit der JSF-View */
    @ManagedBean
    @RequestScoped
    public class MeineBackingBean
    {
       private List<MeineEntity> meineDatenListe;
       private MeineEntity einzelnesElement;
    
       public MeineBackingBean()
       {
          meineDatenListe = MeineEntityDao.findAll();
    
          // Erzeuge eine Daten-Vorbelegung fuer erste einfache Tests:
          if( meineDatenListe == null ) {
             meineDatenListe = MeineEntityDao.createTestDaten();
          }
       }
    
       public String elementLoeschen()
       {
          meineDatenListe = MeineEntityDao.findAll();
          if( meineDatenListe == null || einzelnesElement == null ) {
             addFacesMessage( FacesMessage.SEVERITY_ERROR, "Fehler: Datenelement fehlt." );
             return null;
          }
          if( !meineDatenListe.remove( einzelnesElement ) ) {
             addFacesMessage( FacesMessage.SEVERITY_ERROR, "Fehler: Datenelement existiert nicht mehr." );
             return null;
          }
          MeineEntityDao.writeAll( meineDatenListe );
          return null;
       }
    
       public void addFacesMessage( FacesMessage.Severity severity, String msg )
       {
          FacesMessage facesMsg = new FacesMessage( severity, msg, null );
          FacesContext.getCurrentInstance().addMessage( null, facesMsg );
       }
    
       public List<MeineEntity> getMeineDatenListe()  { return meineDatenListe; }
       public MeineEntity       getEinzelnesElement() { return einzelnesElement; }
       public void setMeineDatenListe(  List<MeineEntity> meineDatenListe ) { this.meineDatenListe = meineDatenListe; }
       public void setEinzelnesElement( MeineEntity einzelnesElement )      { this.einzelnesElement = einzelnesElement; }
    }
    
  7. Erzeugen Sie im Verzeichnis src\main\webapp die CSS-Stylesheet-Datei: stylesTbl.css

    body {
      font-family: Helvetica, Arial, Sans-Serif;
      font-size: 75%;
    }
    table {
      border-collapse: collapse;
      border: 1px solid #d6d6d6;
      background-color: #f7f7f7;
    }
    table th {
      border: 1px solid #d6d6d6;
      background-color: #eeeeee;
      padding: 5px;
    }
    table td {
      border: 1px solid #d6d6d6;
      padding: 5px;
    }
    
  8. Erzeugen Sie im Verzeichnis src\main\webapp die Facelets-XHTML-Datei: tabelle.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
      <title>JSF-Tabelle mit Fehler bei Elementauswahl</title>
      <link rel="stylesheet" type="text/css" href="stylesTbl.css" />
    </head>
    <body>
    <h2><h:outputText value="JSF-Tabelle mit Fehler bei Elementauswahl" /></h2>
      <h:messages style="color: red" />
      <h:form>
        <h:dataTable value="#{meineBackingBean.meineDatenListe}" var="loopElement">
          <h:column>
            <f:facet name="header"><h:outputText value="ID"></h:outputText></f:facet>
            <h:outputText value="#{loopElement.id}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Bezeichnung"></h:outputText></f:facet>
            <h:outputText value="#{loopElement.bezeichnung}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Datum"></h:outputText></f:facet>
            <h:outputText value="#{loopElement.datum}">
              <f:convertDateTime pattern="dd.MM.yyyy" />
            </h:outputText>
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Aktion"></h:outputText></f:facet>
            <h:commandButton value="Element l&ouml;schen" action="#{meineBackingBean.elementLoeschen}">
              <f:setPropertyActionListener target="#{meineBackingBean.einzelnesElement}" value="#{loopElement}" />
            </h:commandButton>
          </h:column>
        </h:dataTable>
      </h:form>
    </body>
    </html>
    

    "#{...}" sind EL-Ausdrücke. "<h:..." sind Tags aus der HTML Tag Library. "<f:..." sind Tags aus der Core Tag Library. "f:convertDateTime" ist ein JSF Standard Converter, siehe Tag convertDateTime. "f:setPropertyActionListener" ist ein Core Tag für einen Action Listener, welcher beim Submit Werte in eine Managed Bean überträgt, siehe Tag setPropertyActionListener.

  9. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabelleMitSelektionsFehler

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfTabelleMitSelektionsFehler]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               |- [backingbeans]
     |       |               |   '- MeineBackingBean.java
     |       |               '- [entities]
     |       |                   |- MeineEntity.java
     |       |                   '- MeineEntityDao.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- stylesTbl.css
     |           '- tabelle.xhtml
     '- pom.xml
    
  10. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabelleMitSelektionsFehler

    if exist MeineDaten.dat del MeineDaten.dat

    mvn jetty:run

    start http://localhost:8080/JsfTabelleMitSelektionsFehler/tabelle.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfTabelleMitSelektionsFehler-Webseite
  11. Löschen Sie einzelne Elemente. Es werden die korrekten Elemente gelöscht und die veränderte Liste wird korrekt persistiert.

  12. Folgendermaßen passiert das Unglück:

    1. Löschen Sie die "Datenbankdatei" MeineDaten.dat und starten Sie die Webseite neu:

      if exist MeineDaten.dat del MeineDaten.dat

      start http://localhost:8080/JsfTabelleMitSelektionsFehler/tabelle.xhtml

      Es werden wieder alle drei Elemente angezeigt.

    2. Öffnen Sie entweder ein zweites Webbrowser-Fenster oder einen weiteren Webbrowser-Tab. Geben Sie auch in diesem Webbrowser-Fenster als URL-Adresse http://localhost:8080/JsfTabelleMitSelektionsFehler/tabelle.xhtml ein.

    3. Löschen Sie im ersten Webbrowser-Fenster die oberste Zeile der angezeigten Tabelle, also das "Apfelsinen"-Element.

    4. Wechseln Sie in das andere Webbrowser-Fenster und löschen Sie dort das zweite Element, also das "Birnen"-Element.

    5. Als Ergebnis erhalten Sie:
      Das "Birnen"-Element wurde nicht gelöscht.
      Aber das "Tomaten"-Element wurde ungewollt gelöscht.

  13. Folgender Ausschnitt aus der tabelle.xhtml assoziiert, dass das zum Löschen ausgewählte loopElement-Element korrekt in die Backing-Bean-Property meineBackingBean.einzelnesElement gespeichert wird:

      <h:form>
        <h:dataTable value="#{meineBackingBean.meineDatenListe}" var="loopElement">
            ...
            <h:commandButton value="Element loeschen" action="#{meineBackingBean.elementLoeschen}">
              <f:setPropertyActionListener target="#{meineBackingBean.einzelnesElement}" value="#{loopElement}" />
            </h:commandButton>
    
  14. Leider ist das nicht immer der Fall. JSF merkt sich nicht die Datenobjekte der Liste. Das wäre bei sehr langen Listen und großen Datenelementen mit vielen Attributen sehr speicherintensiv.
    Stattdessen passiert Folgendes beim Anklicken des Löschen-Buttons:
    Es wird der Zeilen-Index des Löschen-Buttons in der Tabellenliste ermittelt. Dann wird die Liste der Datenelemente über #{meineBackingBean.meineDatenListe} neu gelesen und anschließend das zum Zeilen-Index gehörende Datenelement aus dieser neuen Liste in #{meineBackingBean.einzelnesElement} gespeichert. Dabei wird nicht geprüft, ob das Datenelement mit dem in der Liste an dieser Position angezeigtem Element übereinstimmt. Da die Liste der Datenelemente neu gelesen wird (entweder wie im Beispiel aus einer Datei, oder in realen Anwendungen aus einer Datenbank), kann es aber in der Zwischenzeit durch andere Anwender parallel konkurrierend Änderungen gegeben haben. Andere Anwender können Daten gelöscht oder eingefügt haben. In solchen Fällen wird ein beliebiges anderes Datenelement ungewollt gelöscht, ohne Fehlermeldung, was in echten Anwendungen fatale Folgen haben kann.

  15. Es gibt im Wesentlichen zwei Korrekturmöglichkeiten:



JSF-Tabelle mit korrekter Selektion

Dieses Programmierbeispiel zeigt wieder die Darstellung einer beliebig langen Liste von Elementen mit mehreren Attributen in einer JSF-Tabelle. Daraus können wieder einzelne Elemente gelöscht werden.

Anders als beim letzten Beispiel funktioniert diesmal auch dann die Selektion eines Elements korrekt, wenn mehrere Anwender parallel arbeiten (oder Hintergrund-Jobs die Daten ändern).

  1. Voraussetzung ist das vorherige Programmierbeispiel Fehler beim Selektieren in einer JSF-Tabelle.

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfTabelleMitSelektionsFehler JsfTabelleMitKorrekterSelektion\ /S

    cd JsfTabelleMitKorrekterSelektion

  3. Ersetzen Sie im JsfTabelleMitKorrekterSelektion-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitSelektionsFehler" durch "JsfTabelleMitKorrekterSelektion".

  4. Ändern Sie im src\main\java\de\meinefirma\meinprojekt\backingbeans-Verzeichnis in der MeineBackingBean.java Folgendes:

    a) Löschen Sie den Text "Achtung: Diese Klasse enthaelt einen Fehler, siehe Text!".

    b) Ersetzen Sie "@RequestScoped" durch "@ViewScoped". Achten Sie dabei darauf, dass Sie @ViewScoped aus dem Package "javax.faces.bean" verwenden.

    Die Klassendefinition beginnt jetzt so:

    /** Backing Bean, verknuepft die Daten und die Geschaeftslogik mit der JSF-View */
    @ManagedBean
    @ViewScoped
    public class MeineBackingBean
    {
    
  5. Ersetzen Sie im src\main\webapp-Verzeichnis in der tabelle.xhtml an zwei Stellen "JSF-Tabelle mit Fehler bei Elementauswahl" durch "JSF-Tabelle mit Elementauswahl".

  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabelleMitKorrekterSelektion

    if exist MeineDaten.dat del MeineDaten.dat

    mvn jetty:run

    start http://localhost:8080/JsfTabelleMitKorrekterSelektion/tabelle.xhtml

  7. Versuchen Sie den oben beschriebenen Fehler zu reproduzieren. Der Fehler tritt nicht mehr auf.

  8. Erklärung:
    Durch den auf @ViewScoped geänderten Scope wird der Konstruktor, in dem die Daten geladen werden, nur zu Beginn durchlaufen, aber nicht beim Lösch-Request.

    Erläuterungen zu den Scopes finden Sie unter @RequestScoped und @ViewScoped, falls Sie die alten JSF-Scopes verwenden, bzw. unter @RequestScoped und @ViewScoped, falls Sie die neuen CDI-Scopes verwenden.

    Übrigens kann die Verwendung von @ViewScoped statt @RequestScoped auch zu einem deutlichen Performanceschub sorgen, falls im Konstruktor zeitintensive Berechnungen oder Datenbankabfragen stattfinden.

    Verwenden Sie @SessionScoped nur, wenn dies zwingend notwendig ist, da es damit leicht zu hohem Speicherverbrauch kommen kann.



JSF-Tabelle mit Mehrfach-Selektion, Neuanlage und Editierung

Dieses Programmierbeispiel zeigt:

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist eins der beiden letzten Programmierbeispiele, beispielsweise JSF-Tabelle mit korrekter Selektion.

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfTabelleMitKorrekterSelektion JsfTabelleMitNeuUndEdit\ /S

    cd JsfTabelleMitNeuUndEdit

  3. Ersetzen Sie im JsfTabelleMitNeuUndEdit-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitKorrekterSelektion" durch "JsfTabelleMitNeuUndEdit".

  4. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt\backingbeans-Verzeichnis den Inhalt der MeineBackingBean.java durch:

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.util.*;
    import javax.faces.application.FacesMessage;
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.ViewScoped;
    import javax.faces.context.FacesContext;
    import de.meinefirma.meinprojekt.entities.*;
    
    // Backing Bean, verknuepft die Daten und die Geschaeftslogik mit der JSF-View
    @ManagedBean
    @ViewScoped
    public class MeineBackingBean
    {
       private static final String KEY_IN_SESSION = "einzelnesElement";
       private List<MeineEntity> meineDatenListe;
       private MeineEntity einzelnesElement;
       private Map<MeineEntity,Boolean> merkerMap = new HashMap<MeineEntity,Boolean>();
    
       public MeineBackingBean()
       {
          meineDatenListe = MeineEntityDao.findAll();
    
          einzelnesElement = (MeineEntity) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get( KEY_IN_SESSION );
          FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put( KEY_IN_SESSION, null );
    
          // Die folgenden Zeilen erzeugen eine Daten-Vorbelegung fuer erste einfache Tests:
          if( meineDatenListe == null ) {
             meineDatenListe = MeineEntityDao.createTestDaten();
          }
       }
    
       public String neuesElementErstellen()
       {
          meineDatenListe = MeineEntityDao.findAll();
          int idNeu = 0;
          for( MeineEntity me : meineDatenListe ) {
             idNeu = Math.max( idNeu, me.getId().intValue() + 1 );
          }
          einzelnesElement = new MeineEntity( idNeu, null, new Date() );
          FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put( KEY_IN_SESSION, einzelnesElement );
          return "einzelnesElementEditieren.xhtml";
       }
    
       public String einzelnesElementEditieren()
       {
          FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put( KEY_IN_SESSION, einzelnesElement );
          return "einzelnesElementEditieren.xhtml";
       }
    
       public String einzelnesElementSpeichern()
       {
          MeineEntityDao.saveOrUpdateEntity( einzelnesElement );
          return "tabelle.xhtml";
       }
    
       public String markierteElementeLoeschen()
       {
          meineDatenListe = MeineEntityDao.findAll();
          for( Map.Entry<MeineEntity,Boolean> mapEntry : merkerMap.entrySet() ) {
             if( mapEntry.getValue().booleanValue() ) {
                if( !meineDatenListe.remove( mapEntry.getKey() ) ) {
                   addFacesMessage( FacesMessage.SEVERITY_ERROR, "Fehler: Datenelement existiert nicht mehr." );
                }
             }
          }
          MeineEntityDao.writeAll( meineDatenListe );
          merkerMap.clear();
          return null;
       }
    
       public void addFacesMessage( FacesMessage.Severity severity, String msg )
       {
          FacesMessage facesMsg = new FacesMessage( severity, msg, null );
          FacesContext.getCurrentInstance().addMessage( null, facesMsg );
       }
    
       public List<MeineEntity>        getMeineDatenListe()  { return meineDatenListe; }
       public MeineEntity              getEinzelnesElement() { return einzelnesElement; }
       public Map<MeineEntity,Boolean> getMerkerMap()        { return merkerMap; }
       public void setMeineDatenListe(  List<MeineEntity> meineDatenListe )  { this.meineDatenListe = meineDatenListe; }
       public void setEinzelnesElement( MeineEntity einzelnesElement )       { this.einzelnesElement = einzelnesElement; }
       public void setMerkerMap(        Map<MeineEntity,Boolean> merkerMap ) { this.merkerMap = merkerMap; }
    }
    

    Über die merkerMap-Map werden die Mehrfach-Selektionen gespeichert. In der markierteElementeLoeschen()-Methode erfolgt dann die mehrfache Löschung.

    Bitte beachten Sie den Trick mit der SessionMap, um Daten von einer XHTML-Seite zur nächsten zu übertragen. Wenn Sie diesen Trick vermeiden wollen, können Sie den neuen Scope @ConversationScoped einsetzen. Verwenden Sie @SessionScoped nur, wenn dies zwingend notwendig ist, da es damit leicht zu hohem Speicherverbrauch kommen kann.

    Beachten Sie außerdem die Navigation auf andere Webseiten (bzw. Facelets-XHTML-Dateien) über die Return-Strings der Action-Methoden, z.B. return "tabelle.xhtml". return null bedeutet, dass die Webseite nicht gewechselt werden soll.

  5. Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der tabelle.xhtml durch:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
      <title>JSF-Tabelle mit Mehrfach-Selektion sowie Neuanlage- und &Auml;nderungsm&ouml;glichkeit</title>
      <link rel="stylesheet" type="text/css" href="stylesTbl.css" />
    </head>
    <body>
    <h2><h:outputText value="JSF-Tabelle mit Mehrfach-Selektion sowie Neuanlage- und &Auml;nderungsm&ouml;glichkeit" /></h2>
      <h:messages style="color: red" />
      <h:form>
        <h:dataTable value="#{meineBackingBean.meineDatenListe}" var="loopElement">
          <h:column>
            <f:facet name="header"><h:outputText value="Auswahl"></h:outputText></f:facet>
            <h:selectBooleanCheckbox value="#{meineBackingBean.merkerMap[ loopElement ]}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="ID"></h:outputText></f:facet>
            <h:outputText value="#{loopElement.id}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Bezeichnung"></h:outputText></f:facet>
            <h:outputText value="#{loopElement.bezeichnung}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Datum"></h:outputText></f:facet>
            <h:outputText value="#{loopElement.datum}">
              <f:convertDateTime pattern="dd.MM.yyyy" />
            </h:outputText>
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Aktion"></h:outputText></f:facet>
            <h:commandButton value="Element &auml;ndern" action="#{meineBackingBean.einzelnesElementEditieren}">
              <f:setPropertyActionListener target="#{meineBackingBean.einzelnesElement}" value="#{loopElement}" />
            </h:commandButton>
          </h:column>
        </h:dataTable>
        <h:outputText value="&lt;br/&gt;" escape="false" />
        <h:commandButton value="Markierte Elemente l&ouml;schen" action="#{meineBackingBean.markierteElementeLoeschen}" />
        <h:outputText value="&nbsp;&nbsp;&nbsp;" />
        <h:commandButton value="Neues Element erstellen" action="#{meineBackingBean.neuesElementErstellen}" />
      </h:form>
    </body>
    </html>
    

    Beachten Sie, wie die Mehrfach-Selektionen von h:selectBooleanCheckbox in die merkerMap übertragen werden.

  6. Erzeugen Sie im Verzeichnis src\main\webapp die Facelets-XHTML-Datei: einzelnesElementEditieren.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
      <title>Einzelnes Element editieren</title>
      <link rel="stylesheet" type="text/css" href="stylesTbl.css" />
    </head>
    <body>
    <h2><h:outputText value="Einzelnes Element editieren" /></h2>
      <h:messages style="color: red" />
      <h:form>
        <h:panelGrid columns="2">
          <h:outputText value="ID" />
          <h:inputText  label="ID"          value="#{meineBackingBean.einzelnesElement.id}" disabled="true" />
          <h:outputText value="Bezeichnung" />
          <h:inputText  label="Bezeichnung" value="#{meineBackingBean.einzelnesElement.bezeichnung}" />
          <h:outputText value="Datum" />
          <h:inputText  label="Datum"       value="#{meineBackingBean.einzelnesElement.datum}">
            <f:convertDateTime pattern="dd.MM.yyyy" />
          </h:inputText>
        </h:panelGrid>
        <h:commandButton value="Speichern" action="#{meineBackingBean.einzelnesElementSpeichern}" />
        <h:commandButton value="Abbrechen" action="tabelle.xhtml" immediate="true" />
      </h:form>
    </body>
    </html>
    

    Beachten Sie das immediate="true"-Attribut beim "Abbrechen"-Button. Dies ist notwendig, damit auch bei fehlerhaften Eingaben (z.B. ungültiger Datum-String) abgebrochen werden kann, ohne dass der JSF-Validator im JSF-Request-Lifecycle dies blockiert.

  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabelleMitNeuUndEdit

    if exist MeineDaten.dat del MeineDaten.dat

    mvn jetty:run

    start http://localhost:8080/JsfTabelleMitNeuUndEdit/tabelle.xhtml

  8. Im Webbrowser erscheint:

    JsfTabelleMitNeuUndEdit-Webseite JsfTabelleMitNeuUndEdit-Webseite


JSF-Tabelle mit Paginierung, Sortierung und Exportfunktion, via PrimeFaces

Dieses Programmierbeispiel demonstriert:

PrimeFaces-UI-Themes Bitte beachten Sie, dass dieses Beispiel nur für "mittelgroße" Listen geeignet ist, die vollständig in den Arbeitsspeicher geladen werden können. Bei "sehr großen" Listen (viele Millionen Datenelemente) funktioniert die hier gezeigte Vorgehensweise nicht. In solchen Fällen müsste die Sortierung in dem Datenhaltungssystem (meistens Datenbank) erfolgen, und anschließend dürfte nur der relevante "Datenausschnitt" übertragen und angezeigt werden. Dies demonstriert das Programmierbeispiel JSF-Tabelle mit Paginierung und Sortierung für sehr große Datenbanktabellen.

Sehen Sie sich die Doku zu den verwendeten PrimeFaces-Komponenten an:
DataTable Basic, DataTable Paginator, DataTable Sort, DataExporter, ThemeSwitcher, Theme Gallery (auf COMMUNITY klicken), PrimeFaces UI Themes.

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist irgendeines der drei obigen JSF-Tabellen-Programmierbeispiele, also entweder Fehler beim Selektieren in einer JSF-Tabelle, JSF-Tabelle mit korrekter Selektion oder JSF-Tabelle mit Mehrfach-Selektion, Neuanlage und Editierung (alternativ können Sie auch die fünf benötigten Dateien pom.xml, web.xml, override-mojarra-web.xml, MeineEntity.java und MeineEntityDao.java neu erstellen). Die folgenden Kommandos gehen davon aus, dass Sie JSF-Tabelle mit korrekter Selektion (JsfTabelleMitKorrekterSelektion) wählen.

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfTabelleMitKorrekterSelektion JsfTabellePagSortExportPrimefaces\ /S

    cd JsfTabellePagSortExportPrimefaces

    md src\main\webapp\img

    tree /F

  3. Ersetzen Sie im neuen JsfTabellePagSortExportPrimefaces-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitKorrekterSelektion" durch "JsfTabellePagSortExportPrimefaces".

  4. Fügen Sie in der pom.xml folgende Dependencies hinzu:

        <dependency>
          <groupId>org.primefaces</groupId>
          <artifactId>primefaces</artifactId>
          <version>6.1</version>
        </dependency>
        <dependency>
          <groupId>org.primefaces.extensions</groupId>
          <artifactId>all-themes</artifactId>
          <version>1.0.8</version>
        </dependency>
        <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi</artifactId>
          <version>3.16</version>
        </dependency>
        <dependency>
          <groupId>com.lowagie</groupId>
          <artifactId>itext</artifactId>
          <version>2.1.7</version>
        </dependency>
        <dependency>
          <groupId>org.apache.xmlbeans</groupId>
          <artifactId>xmlbeans</artifactId>
          <version>2.6.0</version>
        </dependency>
    
  5. Kopieren Sie die folgenden vier Icons in das src\main\webapp\img-Verzeichnis (oder erstellen Sie selbst eigene entsprechende Icons), und bewahren Sie dabei die Originaldateinamen:

    icon-excel.png   icon-pdf.png   icon-csv.png   icon-xml.png
  6. Fügen Sie im Verzeichnis src\main\webapp\WEB-INF zur web.xml hinzu:

      <context-param>
        <param-name>primefaces.THEME</param-name>
        <param-value>flick</param-value>
      </context-param>
    

    flick ist ein "PrimeFaces UI Theme". Sehen Sie sich die verschiedenen Themes an, und wählen Sie das für Sie passendste: Theme Gallery (auf COMMUNITY klicken), PrimeFaces UI Themes.

  7. Ersetzen Sie im Verzeichnis src\main\webapp den Inhalt der CSS-Stylesheet-Datei stylesTbl.css durch:

    body {
        font-family: Arial, Verdana, Helvetica, sans-serif;
        font-size: 75%;
        font-weight: normal;
        color: #000000;
        background: #FFFFFF;
        margin: 15px;
        padding: 10px;
        line-height: 12px;
    }
    table {
        border: none;
        border-collapse: collapse;
        border-width: 0px;
        margin: 0px;
        padding: 0px;
    }
    
  8. Fügen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\entities zu der DAO-Datei MeineEntityDao.java folgende Methode hinzu (oder erfinden Sie eine sinnvollere Liste von Daten):

       public static List<MeineEntity> createVieleTestDaten( int anzahl )
       {
          List<MeineEntity> entitiesList = new ArrayList<MeineEntity>();
          Random rndm = new Random();
          for( int i = 0; i < anzahl; i++ ) {
             int i1 = rndm.nextInt( 26 );
             int i2 = rndm.nextInt( 26 );
             String s = String.valueOf( new char[] { (char) ('A' + i1), (char) ('A' + i2) } );
             MeineEntity e = new MeineEntity(  i1 * i2, s, new Date() );
             if( entitiesList.contains( e ) ) {
                i--;
             } else {
                entitiesList.add( e );
             }
          }
          writeAll( entitiesList );
          return entitiesList;
       }
    
  9. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt\backingbeans-Verzeichnis den Inhalt der MeineBackingBean.java durch:

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.util.List;
    import javax.faces.bean.*;
    import de.meinefirma.meinprojekt.entities.*;
    
    @ManagedBean
    @ViewScoped
    public class MeineBackingBean
    {
       private List<MeineEntity> meineDatenListe;
    
       public MeineBackingBean()
       {
          meineDatenListe = MeineEntityDao.findAll();
    
          // Die folgenden Zeilen erzeugen eine Daten-Vorbelegung fuer erste einfache Tests:
          if( meineDatenListe == null ) {
             meineDatenListe = MeineEntityDao.createVieleTestDaten( 52 );
          }
       }
    
       public List<MeineEntity> getSortableEntities()  { return meineDatenListe; }
    }
    
  10. Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der tabelle.xhtml durch:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:p="http://primefaces.org/ui">
    <h:head>
      <title>JSF-Tabelle mit Paginierung, Sortierung und Exportfunktion, via PrimeFaces</title>
      <link rel="stylesheet" type="text/css" href="stylesTbl.css" />
    </h:head>
    <h:body>
    
      <h2><h:outputText value="JSF-Tabelle mit Paginierung, Sortierung und Exportfunktion, via PrimeFaces" /></h2>
    
      <h:messages errorClass="errorMessages" infoClass="infoMessages" layout="table" />
    
      <h:form>
        <p:dataTable id="meineDataTable" var="item" value="#{meineBackingBean.sortableEntities}"
                     paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
                     currentPageReportTemplate="(Eintrag: {startRecord} - {endRecord} von {totalRecords}, Seite: {currentPage} von {totalPages})"
                     paginator="true" rows="10" rowsPerPageTemplate="5,10,20" style="margin-bottom:20px">
          <f:facet name="header">Meine Datentabelle</f:facet>
          <p:column headerText="ID" sortBy="#{item.id}">
            <h:outputText value="#{item.id}" />
          </p:column>
           <p:column headerText="Bezeichnung" sortBy="#{item.bezeichnung}">
            <h:outputText value="#{item.bezeichnung}" />
          </p:column>
          <p:column headerText="Datum" sortBy="#{item.datum}">
            <h:outputText value="#{item.datum}">
              <f:convertDateTime pattern="dd.MM.yyyy" />
            </h:outputText>
          </p:column>
        </p:dataTable>
     
        <h3>Export der angezeigten Daten:</h3>
        <h:commandLink>
          <p:graphicImage value="/img/icon-excel.png" />
          <p:dataExporter type="xls" target="meineDataTable" fileName="meineDaten" pageOnly="true" />
        </h:commandLink>
        <h:commandLink>
          <p:graphicImage value="/img/icon-pdf.png" />
          <p:dataExporter type="pdf" target="meineDataTable" fileName="meineDaten" pageOnly="true" />
        </h:commandLink>
        <h:commandLink>
          <p:graphicImage value="/img/icon-csv.png" />
          <p:dataExporter type="csv" target="meineDataTable" fileName="meineDaten" pageOnly="true" />
        </h:commandLink>
        <h:commandLink>
          <p:graphicImage value="/img/icon-xml.png" />
          <p:dataExporter type="xml" target="meineDataTable" fileName="meineDaten" pageOnly="true" />
        </h:commandLink>
    
        <h3>Export der gesamten Datenliste:</h3>
        <h:commandLink>
          <p:graphicImage value="/img/icon-excel.png" />
          <p:dataExporter type="xls" target="meineDataTable" fileName="meineDaten" />
        </h:commandLink>
        <h:commandLink>
          <p:graphicImage value="/img/icon-pdf.png" />
          <p:dataExporter type="pdf" target="meineDataTable" fileName="meineDaten" />
        </h:commandLink>
        <h:commandLink>
          <p:graphicImage value="/img/icon-csv.png" />
          <p:dataExporter type="csv" target="meineDataTable" fileName="meineDaten" />
        </h:commandLink>
        <h:commandLink>
          <p:graphicImage value="/img/icon-xml.png" />
          <p:dataExporter type="xml" target="meineDataTable" fileName="meineDaten" />
        </h:commandLink>
      </h:form>
    
    </h:body>
    </html>
    
  11. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabellePagSortExportPrimefaces

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfTabellePagSortExportPrimefaces]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               |- [backingbeans]
     |       |               |   '- MeineBackingBean.java
     |       |               '- [entities]
     |       |                   |- MeineEntity.java
     |       |                   '- MeineEntityDao.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [img]
     |           |   |- icon-csv.png
     |           |   |- icon-excel.png
     |           |   |- icon-pdf.png
     |           |   '- icon-xml.png
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- stylesTbl.css
     |           '- tabelle.xhtml
     '- pom.xml
    
  12. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabellePagSortExportPrimefaces

    mvn jetty:run

    Warten Sie bis "[INFO] Started Jetty Server" erscheint und öffnen Sie die Webseite:

    start http://localhost:8080/JsfTabellePagSortExportPrimefaces/tabelle.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfTabellePagSortExportPrimefaces-Webseite

    Testen Sie die Sortierung, indem Sie auf die Spaltentitel in der Tabelle klicken. Testen Sie auch die Paginierung über die Paginierungs-Icons und Seitenzahlen. Und führen Sie die verschiedenen Exportfunktionen für Excel, PDF, CSV und XML über die acht unteren Icons aus.

  13. Falls Sie Probleme mit PrimeFaces haben, sollten Sie sich den PrimeFaces-Logger ansehen, siehe PrimeFaces Log. Er wird durch einfaches Einfügen von "<p:log id="log" /> in Ihre Facelets-XHTML-Datei aktiviert.

  14. Falls Sie das in Ihrer HTML- oder Facelets-XHTML-Seite implementierte JavaScript debuggen wollen: Verwenden Sie hierzu den Firefox-Webbrowser, laden Sie darin die zu debuggende Webseite, und starten Sie mit "Strg+Shift+S" den JavaScript-Debugger. Mit der rechten Maustaste können Sie im JavaScript-Code Breakpoints setzen.
    Falls Sie PrimeFaces-JavaScript debuggen wollen: Das PrimeFaces-JavaScript ist "minified" und dadurch beim Debuggen kaum zu verstehen. Dies können Sie ändern wie beschrieben ist unter: How to debug javascript in PrimeFaces.



JSF-Tabelle mit Paginierung und Sortierung mit Tomahawk

Dieses Programmierbeispiel demonstriert:

Bitte beachten Sie, dass Apache MyFaces Tomahawk nicht mehr weiter entwickelt wird, und dass die vorhandenen Tomahawk-Versionen nicht mit allen JSF-Implementierungen verwendbar ist. Sie hierzu die Bemerkungen zur Tomahawk-Kompatibilität. Für Neuentwicklungen sollten Sie statt Tomahawk besser PrimeFaces verwenden, siehe hierzu die obige Demo JSF-Tabelle mit Paginierung, Sortierung und Exportfunktion, via PrimeFaces.

Bitte beachten Sie außerdem, dass dieses Beispiel nur für "mittelgroße" Listen geeignet ist, die vollständig in den Arbeitsspeicher geladen werden können. Bei "sehr großen" Listen (viele Millionen Datenelemente) funktioniert die hier gezeigte Vorgehensweise nicht. In solchen Fällen müsste die Sortierung in dem Datenhaltungssystem (meistens Datenbank) erfolgen, und anschließend dürfte nur der relevante "Datenausschnitt" übertragen und angezeigt werden. Dies demonstriert das anschließend folgende Programmierbeispiel.

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist irgendeines der drei ersten JSF-Tabellen-Programmierbeispiele, also entweder Fehler beim Selektieren in einer JSF-Tabelle, JSF-Tabelle mit korrekter Selektion oder JSF-Tabelle mit Mehrfach-Selektion, Neuanlage und Editierung (alternativ können Sie auch die vier benötigten Dateien pom.xml, web.xml, MeineEntity.java und MeineEntityDao.java neu erstellen). Die folgenden Kommandos gehen davon aus, dass Sie JSF-Tabelle mit korrekter Selektion (JsfTabelleMitKorrekterSelektion) wählen.

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfTabelleMitKorrekterSelektion JsfTabellePagSortTmhwk\ /S

    cd JsfTabellePagSortTmhwk

    md src\main\webapp\img

    del src\main\webapp\stylesTbl.css

  3. Ersetzen Sie im JsfTabellePagSortTmhwk-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitKorrekterSelektion" durch "JsfTabellePagSortTmhwk".

  4. Ersetzen Sie im JsfTabellePagSortTmhwk-Projektverzeichnis in der pom.xml die Zeile

      <overrideDescriptor>src/main/jetty/override-mojarra-web.xml</overrideDescriptor>
    

    durch:

      <overrideDescriptor>src/main/jetty/override-myfaces-web.xml</overrideDescriptor>
    
  5. Ersetzen Sie im JsfTabellePagSortTmhwk-Projektverzeichnis in der pom.xml den <dependencies>-Block

      <dependencies>
        ...
      </dependencies>
    

    durch:

      <dependencies>
        <dependency>
          <groupId>org.apache.myfaces.core</groupId>
          <artifactId>myfaces-api</artifactId>
          <version>2.2.6</version>
        </dependency>
        <dependency>
          <groupId>org.apache.myfaces.core</groupId>
          <artifactId>myfaces-impl</artifactId>
          <version>2.2.6</version>
        </dependency>
        <dependency>
          <groupId>org.apache.myfaces.tomahawk</groupId>
          <artifactId>tomahawk21</artifactId>
          <version>1.1.14</version>
        </dependency>
      </dependencies>
    
  6. Erstellen Sie im src\main\jetty-Verzeichnis die Jetty/MyFaces-Konfigurationsdatei: override-myfaces-web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
      <listener>
        <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
      </listener>
    </web-app>
    

    Erläuterungen hierzu finden Sie unter JSF 2 on Jetty 8, Jetty/Howto/Configure, Could not find Factory und JSF 2.2 and CDI running on Jetty/Tomcat.

  7. Kopieren Sie die folgenden sechs Icons in das src\main\webapp\img-Verzeichnis (oder erstellen Sie selbst eigene entsprechende Icons), und bewahren Sie dabei die Originaldateinamen:

    pagin-arrow-first   pagin-arrow-fr   pagin-arrow-previous   pagin-arrow-next   pagin-arrow-ff   pagin-arrow-last
  8. Erstellen Sie im Verzeichnis src\main\webapp die CSS-Stylesheet-Datei: stylesTmhwk.css

    table {
        border: none;
        border-collapse: collapse;
        border-width: 0px;
        margin: 0px;
        padding: 0px;
    }
    
    body {
        font-family: Arial, Verdana, Helvetica, sans-serif, Times New Roman;
        font-size: 12px;
        color: #000000;
        background: #FFFFFF;
        font-weight: normal;
        margin: 15px;
        padding: 10px;
        line-height: 12px;
    }
    
    a:link, a:visited, a:active {
        color: #008dd9;
        text-decoration: none;
        font-weight: bold;
    }
    
    .standardTable {
        font-family: Arial, Verdana, Helvetica, sans-serif, Times New Roman;
        font-size: 12px;
        color: #000000;
        padding: 2px;
        border: 1px solid #008dd9;
    }
    
    .standardTable_Header {
        color: #000000;
        background-color: #E5EFF8;
        padding: 5px;
        text-align: center;
        border: 1px solid #008dd9;
    }
    
    .standardTable_Row1       { background-color: #FFFFFF; }
    .standardTable_Row1:hover { background-color: #e6e6e6; }
    .standardTable_Row2       { background-color: #F1F1F1; }
    .standardTable_Row2:hover { background-color: #e6e6e6; }
    
    .standardTable_Column { padding: 5px;  border-right: 1px solid #008dd9; }
    
    .paginatorActiveColumn { padding: 3px; background-color: #e6e6e6; }
    
    .errorMessages {
       font-family: Arial, Verdana, Helvetica, sans-serif, Times New Roman;
       font-size: 12px;
       font-weight: bold;
       color: red;
       margin-left: 15px;
    }
    
    .infoMessages {
       font-family: Arial, Verdana, Helvetica, sans-serif, Times New Roman;
       font-size: 12px;
       font-weight: bold;
       color: green;
       margin-left: 15px;
    }
    
  9. Fügen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\entities zu der DAO-Datei MeineEntityDao.java folgende Methode hinzu (oder erfinden Sie eine sinnvollere Liste von Daten):

       public static List<MeineEntity> createVieleTestDaten( int anzahl )
       {
          List<MeineEntity> entitiesList = new ArrayList<MeineEntity>();
          Random rndm = new Random();
          for( int i = 0; i < anzahl; i++ ) {
             int i1 = rndm.nextInt( 26 );
             int i2 = rndm.nextInt( 26 );
             String s = String.valueOf( new char[] { (char) ('A' + i1), (char) ('A' + i2) } );
             MeineEntity e = new MeineEntity(  i1 * i2, s, new Date() );
             if( entitiesList.contains( e ) ) {
                i--;
             } else {
                entitiesList.add( e );
             }
          }
          writeAll( entitiesList );
          return entitiesList;
       }
    
  10. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt\backingbeans-Verzeichnis den Inhalt der MeineBackingBean.java durch:

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.util.List;
    import javax.faces.bean.*;
    import de.meinefirma.meinprojekt.entities.*;
    
    @ManagedBean
    @ViewScoped
    public class MeineBackingBean
    {
       private List<MeineEntity> meineDatenListe;
       private String  sortColumn;
       private boolean sortAscending;
    
       public MeineBackingBean()
       {
          meineDatenListe = MeineEntityDao.findAll();
    
          // Die folgenden Zeilen erzeugen eine Daten-Vorbelegung fuer erste einfache Tests:
          if( meineDatenListe == null ) {
             meineDatenListe = MeineEntityDao.createVieleTestDaten( 52 );
          }
       }
    
       public List<MeineEntity> getSortableEntities()  { return meineDatenListe; }
       public String            getSortColumn()        { return sortColumn; }
       public boolean           isSortAscending()      { return sortAscending; }
       public void setSortColumn(    String sortColumn )     { this.sortColumn    = sortColumn; }
       public void setSortAscending( boolean sortAscending ) { this.sortAscending = sortAscending; }
    }
    
  11. Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der tabelle.xhtml durch:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:t="http://myfaces.apache.org/tomahawk">
    <h:head>
      <title>JSF-Tabelle mit Paginierung und Sortierung mit Tomahawk</title>
      <link rel="stylesheet" type="text/css" href="stylesTmhwk.css" />
    </h:head>
    <h:body>
      <h2><h:outputText value="JSF-Tabelle mit Paginierung und Sortierung mit Tomahawk" /></h2>
      <h:messages errorClass="errorMessages" infoClass="infoMessages" layout="table" />
      <h:form>
    
        <t:dataTable id                = "meineDataTable"
                     var               = "item"
                     value             = "#{meineBackingBean.sortableEntities}"
                     sortColumn        = "#{meineBackingBean.sortColumn}"
                     sortAscending     = "#{meineBackingBean.sortAscending}"
                     preserveDataModel = "true"
                     preserveSort      = "true"
                     sortable          = "true"
                     rows              = "8"
                     styleClass="standardTable" headerClass="standardTable_Header"
                     rowClasses="standardTable_Row1,standardTable_Row2">
          <t:column styleClass="standardTable_Column">
            <f:facet name="header">
              <t:commandSortHeader columnName="id" arrow="true">
                <h:outputText value="ID" />
              </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{item.id}" />
          </t:column>
          <t:column styleClass="standardTable_Column">
            <f:facet name="header">
              <t:commandSortHeader columnName="bezeichnung" arrow="true">
                <h:outputText value="Bezeichnung" />
              </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{item.bezeichnung}" />
          </t:column>
          <t:column styleClass="standardTable_Column">
            <f:facet name="header">
              <t:commandSortHeader columnName="datum" arrow="true">
                <h:outputText value="Datum" />
              </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{item.datum}">
              <f:convertDateTime pattern="dd.MM.yyyy" />
            </h:outputText>
          </t:column>
        </t:dataTable>
    
        <h:panelGrid>
    
          <t:dataScroller id="meineDataTable_scroller" for="meineDataTable" fastStep="10" pageCountVar="pageCount"
                          pageIndexVar="pageIndex" paginator="true" paginatorMaxPages="9"
                          paginatorActiveColumnClass="paginatorActiveColumn"
                          paginatorColumnStyle="padding:3px;"   immediate="true" rendered="#{!(pageCount le 1)}">
            <f:facet name="first">
              <t:graphicImage url="/img/pagin-arrow-first.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="last">
              <t:graphicImage url="/img/pagin-arrow-last.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="previous">
              <t:graphicImage url="/img/pagin-arrow-previous.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="next">
              <t:graphicImage url="/img/pagin-arrow-next.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="fastforward">
              <t:graphicImage url="/img/pagin-arrow-ff.gif" style="padding:5px;border:none" alt="" rendered="#{(pageCount gt 10)}"/>
            </f:facet>
            <f:facet name="fastrewind">
              <t:graphicImage url="/img/pagin-arrow-fr.gif" style="padding:5px;border:none" alt="" rendered="#{(pageCount gt 10)}"/>
            </f:facet>
          </t:dataScroller>
    
          <t:dataScroller for="meineDataTable" rowsCountVar="rowsCount"
                          displayedRowsCountVar="rowCountVar" firstRowIndexVar="firstRowIndex"
                          lastRowIndexVar="lastRowIndex" pageCountVar="pageCount"
                          immediate="true" pageIndexVar="pageIndex">
            <h:outputFormat rendered="#{!(pageCount le 1)}" value="Gesamt {0}, Seite {1}/{2}">
              <f:param value="#{rowsCount}" />
              <f:param value="#{pageIndex}" />
              <f:param value="#{pageCount}" />
            </h:outputFormat>
          </t:dataScroller>
    
        </h:panelGrid>
    
      </h:form>
    </h:body>
    </html>
    

    Beachten Sie die Einbindung von Apache MyFaces Tomahawk über xmlns:t="http://myfaces.apache.org/tomahawk". Sehen Sie sich folgende Dokus an: <t:dataTable>, <t:dataScroller>, Wiki zu Tomahawk, Working_with_auto_sortable_tables, Managing the DataScroller.

  12. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabellePagSortTmhwk

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfTabellePagSortTmhwk]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               |- [backingbeans]
     |       |               |   '- MeineBackingBean.java
     |       |               '- [entities]
     |       |                   |- MeineEntity.java
     |       |                   '- MeineEntityDao.java
     |       |- [jetty]
     |       |   |- override-mojarra-web.xml
     |       |   '- override-myfaces-web.xml
     |       '- [webapp]
     |           |- [img]
     |           |   |- pagin-arrow-ff.gif
     |           |   |- pagin-arrow-first.gif
     |           |   |- pagin-arrow-fr.gif
     |           |   |- pagin-arrow-last.gif
     |           |   |- pagin-arrow-next.gif
     |           |   '- pagin-arrow-previous.gif
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- stylesTmhwk.css
     |           '- tabelle.xhtml
     '- pom.xml
    
  13. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabellePagSortTmhwk

    if exist MeineDaten.dat del MeineDaten.dat

    mvn jetty:run

    start http://localhost:8080/JsfTabellePagSortTmhwk/tabelle.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfTabellePagSortTmhwk-Webseite

    Testen Sie die Sortierung, indem Sie auf die Spaltentitel in der Tabelle klicken. Beachten Sie, dass die Sortierungsfunktion nicht die aktuelle Ansicht sortiert, sondern die gesamte Liste sortiert, und dann den korrekten Ausschnitt zeigt. Testen Sie auch die Paginierung über die unten angefügten Icons und Seitenzahlen. Von den sechs Paginierungs-Icons werden im Beispiel nur vier angezeigt, weil die Liste nicht so viele Elemente enthält (rendered="#{(pageCount gt 10)}").

  14. Falls Sie eine der folgenden Exceptions erhalten:

    Dann verwenden Sie wahrscheinlich eine ungünstige Kombination von Versionen der JSF-Implementierung und Tomahawk:

    Siehe hierzu auch: Issue TOMAHAWK-1674



JSF-Tabelle mit Paginierung und Sortierung für sehr große Datenbanktabellen

Wie das vorherige demonstriert auch dieses Programmierbeispiel Sortierung und Paginierung in JSF-Tabellen, sowie die Verwendung von Tomahawk und MyFaces.

Anders als das vorherige Programmierbeispiel ist dieses für sehr große Datenmengen beispielsweise aus einer Datenbank geeignet. Um dies möglichst realitätsnah zu zeigen, wird in diesem Beispiel per JPA eine Derby-Datenbank einbezogen.

Bei sehr langen Datenlisten ist wichtig:

Für den Umgang mit sehr großen Listen gibt es verschiedene Vorgehensweisen, siehe hierzu auch: MyFaces: Working With Large Tables, RichFaces 4 Data Models, PrimeFaces Lazy DataTable.

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist das vorherige JSF-Tabellen-Programmierbeispiele JSF-Tabelle mit Paginierung und Sortierung mit Tomahawk.

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfTabellePagSortTmhwk JsfTabellePagSortTmhwkLargeDB\ /S

    cd JsfTabellePagSortTmhwkLargeDB

    md src\main\java\de\meinefirma\meinprojekt\pageddata

    md src\main\resources\META-INF

  3. Ersetzen Sie im JsfTabellePagSortTmhwkLargeDB-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabellePagSortTmhwk" durch "JsfTabellePagSortTmhwkLargeDB".

  4. Fügen Sie im JsfTabellePagSortTmhwkLargeDB-Projektverzeichnis in der pom.xml hinter der Zeile

        <plugins>
    

    ein:

          <plugin>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa-maven-plugin</artifactId>
            <version>2.3.0</version>
            <configuration>
              <includes>**/entity/*.class</includes>
              <excludes>**/entity/*Dao.class</excludes>
              <addDefaultConstructor>true</addDefaultConstructor>
              <enforcePropertyRestrictions>true</enforcePropertyRestrictions>
            </configuration>
            <executions>
              <execution>
                <id>enhancer</id>
                <phase>process-classes</phase>
                <goals>
                  <goal>enhance</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
    
  5. Fügen Sie im JsfTabellePagSortTmhwkLargeDB-Projektverzeichnis in der pom.xml vor der Zeile

      </dependencies>
    

    ein:

        <dependency>
          <groupId>org.apache.openjpa</groupId>
          <artifactId>openjpa</artifactId>
          <version>2.3.0</version>
        </dependency>
        <dependency>
          <groupId>org.apache.derby</groupId>
          <artifactId>derby</artifactId>
          <version>10.11.1.1</version>
        </dependency>
    

    OpenJPA dient als JPA-Provider und Derby implementiert eine einfache Datenbank.

  6. Erstellen Sie im src\main\resources\META-INF-Verzeichnis die JPA-Konfigurationsdatei: persistence.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0"
        xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                            http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
      <persistence-unit name="MeineJpaPU" transaction-type="RESOURCE_LOCAL">
        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <class>de.meinefirma.meinprojekt.entities.MeineEntity</class>
        <properties>
          <property name="openjpa.ConnectionDriverName"     value="org.apache.derby.jdbc.EmbeddedDriver" />
          <property name="openjpa.ConnectionURL"            value="jdbc:derby:./target/Derby-DB/mydb;create=true" />
          <property name="openjpa.ConnectionUserName"       value="" />
          <property name="openjpa.ConnectionPassword"       value="" />
          <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema" />
        </properties>
      </persistence-unit>
    </persistence>
    

    Sehen Sie sich hierzu die Bedeutung der OpenJPA Properties, die allgemeinen Kommentare in PersistenceUnitInfo, die Persistence-Schema-XSD-Datei und die Beispiele unter persistence.xml an.

  7. Erstellen Sie im src\main\java\de\meinefirma\meinprojekt\pageddata-Verzeichnis das generische Interface für den "Paged Data Reader": PagedDataReader.java

    package de.meinefirma.meinprojekt.pageddata;
    
    import java.util.List;
    
    public interface PagedDataReader<D>
    {
       public List<D> readPageOfData( String sortColumn, boolean sortAscending, int firstRowIndex, int rowsPerPage );
       public int getRowsCountAll();
    }
    
  8. Erstellen Sie im src\main\java\de\meinefirma\meinprojekt\pageddata-Verzeichnis die generische Klasse, welche die lange Liste simuliert ("Paged Data List"), und Hilfsmethoden für den Data Scroller zur Verfügung stellt: PagedDataList.java

    package de.meinefirma.meinprojekt.pageddata;
    
    import java.util.*;
    import javax.faces.event.ActionEvent;
    import org.apache.myfaces.custom.datascroller.ScrollerActionEvent;
    
    public class PagedDataList<D> implements List<D>
    {
       private PagedDataReader<D> dao;
       private List<D> onePageOfData;
       private int     firstRowIndex = 0;
       private int     rowsPerPage;
       private int     rowsCountAll;
       private String  sortColumn;
       private boolean sortAscending = true;
    
       /** Konstruktor, dao = Data Acces Object, rowsPerPage = Anzahl in JSF-Tabelle angezeigter Zeilen */
       public PagedDataList( PagedDataReader<D> dao, int rowsPerPage )
       {
          this.dao         = dao;
          this.rowsPerPage = rowsPerPage;
          refreshPageOfData();
       }
    
       /** Einzelnes Datenelement (= Zeile), der Index bezieht sich auf die Gesamtgroesse */
       @Override
       public D get( int i )
       {
          int j = i - firstRowIndex;
          if( j < 0 || j >= onePageOfData.size() ) { return null; }
          return onePageOfData.get( j );
       }
    
       /** Gesamtgroesse der Datenliste (nicht die Groesse der aktuellen Seite) */
       @Override
       public int size()
       {
          return rowsCountAll;
       }
    
       @Override
       public boolean isEmpty()
       {
          return rowsCountAll == 0;
       }
    
       public void scrollerAction( ActionEvent event )
       {
          if( event instanceof ScrollerActionEvent ) {
             String facet = ((ScrollerActionEvent) event).getScrollerfacet();
             int    page  = ((ScrollerActionEvent) event).getPageIndex();
             int    rowsCountLastPage = rowsCountAll % rowsPerPage;
             if( rowsCountLastPage == 0 ) { rowsCountLastPage = rowsPerPage; }
             if( page == -1 ) {
                if( "next".equals( facet ) ) {
                   firstRowIndex += rowsPerPage;
                } else if( "previous".equals( facet ) ) {
                   firstRowIndex -= rowsPerPage;
                } else if( "fastf".equals( facet ) ) {
                   firstRowIndex += 10 * rowsPerPage;
                } else if( "fastr".equals( facet ) ) {
                   firstRowIndex -= 10 * rowsPerPage;
                } else if( "first".equals( facet ) ) {
                   firstRowIndex = 0;
                } else if( "last".equals( facet ) ) {
                   firstRowIndex = rowsCountAll - rowsCountLastPage;
                }
             } else {
                firstRowIndex = (page - 1) * rowsPerPage;
             }
             if( firstRowIndex < 0 ) { firstRowIndex = 0; }
             if( firstRowIndex >= rowsCountAll ) { firstRowIndex = rowsCountAll - rowsCountLastPage; }
             refreshPageOfData();
          }
       }
    
       public void setSortColumn( String sortColumn )
       {
          boolean diff = this.sortColumn == null || !this.sortColumn.equals( sortColumn );
          this.sortColumn = sortColumn;
          if( diff ) { refreshPageOfData(); }
       }
    
       public void setSortAscending( boolean sortAscending )
       {
          boolean diff = this.sortAscending != sortAscending;
          this.sortAscending = sortAscending;
          if( diff ) { refreshPageOfData(); }
       }
    
       public List<D> getSortableEntities() { return this; }
       public int     getFirstRowIndex()    { return firstRowIndex; }
       public int     getRowsPerPage()      { return rowsPerPage; }
       public String  getSortColumn()       { return sortColumn; }
       public boolean isSortAscending()     { return sortAscending; }
    
       private void refreshPageOfData()
       {
          onePageOfData = dao.readPageOfData( sortColumn, sortAscending, firstRowIndex, rowsPerPage );
          rowsCountAll  = dao.getRowsCountAll();
       }
    
       @Override public boolean add( D arg0 ) { throw new UnsupportedOperationException( "add" ); }
       @Override public void add( int arg0, D arg1 ) { throw new UnsupportedOperationException( "add" ); }
       @Override public boolean addAll( Collection<? extends D> arg0 ) { throw new UnsupportedOperationException( "addAll" ); }
       @Override public boolean addAll( int arg0, Collection<? extends D> arg1 ) { throw new UnsupportedOperationException( "addAll" ); }
       @Override public void clear() { throw new UnsupportedOperationException( "clear" ); }
       @Override public boolean contains( Object arg0 ) { throw new UnsupportedOperationException( "contains" ); }
       @Override public boolean containsAll( Collection<?> arg0 ) { throw new UnsupportedOperationException( "containsAll" ); }
       @Override public int indexOf( Object arg0 ) { throw new UnsupportedOperationException( "indexOf" ); }
       @Override public Iterator<D> iterator() { throw new UnsupportedOperationException( "iterator" ); }
       @Override public int lastIndexOf( Object arg0 ) { throw new UnsupportedOperationException( "lastIndexOf" ); }
       @Override public ListIterator<D> listIterator() { throw new UnsupportedOperationException( "listIterator" ); }
       @Override public ListIterator<D> listIterator( int arg0 ) { throw new UnsupportedOperationException( "listIterator" ); }
       @Override public boolean remove( Object arg0 ) { throw new UnsupportedOperationException( "remove" ); }
       @Override public D remove( int arg0 ) { throw new UnsupportedOperationException( "remove" ); }
       @Override public boolean removeAll( Collection<?> arg0 ) { throw new UnsupportedOperationException( "removeAll" ); }
       @Override public boolean retainAll( Collection<?> arg0 ) { throw new UnsupportedOperationException( "retainAll" ); }
       @Override public D set( int arg0, D arg1 ) { throw new UnsupportedOperationException( "set" ); }
       @Override public List<D> subList( int arg0, int arg1 ) { throw new UnsupportedOperationException( "subList" ); }
       @Override public Object[] toArray() { throw new UnsupportedOperationException( "toArray" ); }
       @Override public <T> T[] toArray( T[] arg0 ) { throw new UnsupportedOperationException( "toArray" ); }
    }
    
  9. Führen Sie im src\main\java\de\meinefirma\meinprojekt\entities-Verzeichnis in der Entity-Klasse MeineEntity.java folgende Änderungen durch.

    Fügen Sie bei den Import-Anweisungen hinzu:

    import javax.persistence.*;
    

    Ersetzen Sie die Zeile

    /** Zu speichernde Daten, kann leicht zu JPA-@Entity erweitert werden */
    

    durch

    @Entity
    

    und die Zeile

    // Technische id, soll unique sein (wie Primary Key):
    

    durch

    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    

    und ersetzen Sie den Konstruktor

       public MeineEntity( int id, String bezeichnung, Date datum )
       {
          ...
       }
    

    durch

       protected MeineEntity()
       {
       }
    
       public MeineEntity( String bezeichnung, Date datum )
       {
          this.bezeichnung = bezeichnung;
          this.datum = datum;
       }
    
  10. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt\entities-Verzeichnis den Inhalt der MeineEntityDao.java durch:

    package de.meinefirma.meinprojekt.entities;
    
    import java.util.*;
    import javax.persistence.*;
    import de.meinefirma.meinprojekt.pageddata.PagedDataReader;
    
    public class MeineEntityDao implements PagedDataReader<MeineEntity>
    {
       EntityManagerFactory emf = Persistence.createEntityManagerFactory( "MeineJpaPU" );
    
       @Override
       public int getRowsCountAll()
       {
          EntityManager em = emf.createEntityManager();
          Query query = em.createNativeQuery( "Select count(*) from MeineEntity" );
          return ((Integer) query.getSingleResult()).intValue();
       }
    
       @Override
       @SuppressWarnings("unchecked")
       public List<MeineEntity> readPageOfData( String sortColumn, boolean sortAscending, int firstRowIndex, int rowsPerPage )
       {
          EntityManager em = emf.createEntityManager();
          Query query = em.createQuery( "Select e from MeineEntity e order by e." +
                                         (( sortColumn != null && !sortColumn.isEmpty() ) ? sortColumn.toLowerCase() : "id") +
                                         (( sortAscending ) ? " asc" : " desc") );
          query.setFirstResult( firstRowIndex );
          query.setMaxResults( rowsPerPage );
          return query.getResultList();
       }
    
       public void createTestDataIfNoData( int anzahl )
       {
          List<MeineEntity>  lst0 = readPageOfData( null, true, 0, 1 );
          if( lst0 != null && lst0.size() > 0 ) { return; }
    
          Random rndm = new Random();
          for( int i = 0; i < anzahl; i++ ) {
             int i1 = rndm.nextInt( 26 );
             int i2 = rndm.nextInt( 26 );
             String s = String.valueOf( new char[] { (char) ('A' + i1), (char) ('A' + i2) } );
             createEntity( new MeineEntity(  s, new Date() ) );
          }
       }
    
       public <T> void createEntity( T entity )
       {
          EntityManager     em = emf.createEntityManager();
          EntityTransaction tx = em.getTransaction();
          try {
             tx.begin();
             em.persist( entity );
             tx.commit();
          } catch( RuntimeException ex ) {
             if( tx != null && tx.isActive() ) tx.rollback();
             throw ex;
          } finally {
             em.close();
          }
       }
    }
    
  11. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt\backingbeans-Verzeichnis den Inhalt der MeineBackingBean.java durch:

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.util.List;
    import javax.faces.bean.*;
    import de.meinefirma.meinprojekt.entities.*;
    import de.meinefirma.meinprojekt.pageddata.PagedDataList;
    
    @ManagedBean
    @ViewScoped
    public class MeineBackingBean
    {
       private static final int DEFAULT_ROWS_PER_PAGE = 10;
       private static final int DEFAULT_ANZAHL_TESTDATEN = 500;
       private PagedDataList<MeineEntity> pagedDataList;
    
       public MeineBackingBean()
       {
          MeineEntityDao dao = new MeineEntityDao();
          dao.createTestDataIfNoData( DEFAULT_ANZAHL_TESTDATEN );
          pagedDataList = new PagedDataList<MeineEntity>( dao, DEFAULT_ROWS_PER_PAGE );
       }
    
       public PagedDataList<MeineEntity> getPagedDataList() { return pagedDataList; }
       public List<MeineEntity> getSortableEntities() { return pagedDataList.getSortableEntities(); }
       public int     getRowsPerPage()  { return pagedDataList.getRowsPerPage(); }
       public String  getSortColumn()   { return pagedDataList.getSortColumn(); }
       public boolean isSortAscending() { return pagedDataList.isSortAscending(); }
       public void setSortColumn(    String  sortColumn    ) { pagedDataList.setSortColumn(    sortColumn ); }
       public void setSortAscending( boolean sortAscending ) { pagedDataList.setSortAscending( sortAscending ); }
    }
    
  12. Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der tabelle.xhtml durch:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:t="http://myfaces.apache.org/tomahawk">
    <h:head>
      <title>JSF-Tabelle mit Paginierung und Sortierung für sehr große Datenbanktabellen</title>
      <link rel="stylesheet" type="text/css" href="stylesTmhwk.css" />
    </h:head>
    <h:body>
      <h2><h:outputText value="JSF-Tabelle mit Paginierung und Sortierung für sehr große Datenbanktabellen" /></h2>
      <h:messages errorClass="errorMessages" infoClass="infoMessages" layout="table" />
      <h:form>
    
        <t:dataTable id                = "meineDataTable"
                     var               = "item"
                     value             = "#{meineBackingBean.sortableEntities}"
                     sortColumn        = "#{meineBackingBean.sortColumn}"
                     sortAscending     = "#{meineBackingBean.sortAscending}"
                     rows              = "#{meineBackingBean.rowsPerPage}"
                     preserveDataModel = "true"
                     preserveSort      = "true"
                     styleClass="standardTable" headerClass="standardTable_Header"
                     rowClasses="standardTable_Row1,standardTable_Row2">
          <t:column styleClass="standardTable_Column">
            <f:facet name="header">
              <t:commandSortHeader columnName="id" arrow="true">
                <h:outputText value="ID" />
              </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{item.id}" />
          </t:column>
          <t:column styleClass="standardTable_Column">
            <f:facet name="header">
              <t:commandSortHeader columnName="bezeichnung" arrow="true">
                <h:outputText value="Bezeichnung" />
              </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{item.bezeichnung}" />
          </t:column>
          <t:column styleClass="standardTable_Column">
            <f:facet name="header">
              <t:commandSortHeader columnName="datum" arrow="true">
                <h:outputText value="Datum" />
              </t:commandSortHeader>
            </f:facet>
            <h:outputText value="#{item.datum}">
              <f:convertDateTime pattern="dd.MM.yyyy" />
            </h:outputText>
          </t:column>
        </t:dataTable>
    
        <h:panelGrid>
    
          <t:dataScroller id="meineDataTable_scroller" for="meineDataTable" fastStep="10" pageCountVar="pageCount"
                          pageIndexVar="pageIndex" paginator="true" paginatorMaxPages="9"
                          paginatorActiveColumnClass="paginatorActiveColumn"
                          paginatorColumnStyle="padding:3px;" immediate="true" rendered="#{!(pageCount le 1)}"
                          actionListener="#{meineBackingBean.pagedDataList.scrollerAction}">
            <f:facet name="first">
              <t:graphicImage url="/img/pagin-arrow-first.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="last">
              <t:graphicImage url="/img/pagin-arrow-last.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="previous">
              <t:graphicImage url="/img/pagin-arrow-previous.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="next">
              <t:graphicImage url="/img/pagin-arrow-next.gif" style="padding:5px;border:none" alt=""/>
            </f:facet>
            <f:facet name="fastforward">
              <t:graphicImage url="/img/pagin-arrow-ff.gif" style="padding:5px;border:none" alt="" rendered="#{(pageCount gt 10)}"/>
            </f:facet>
            <f:facet name="fastrewind">
              <t:graphicImage url="/img/pagin-arrow-fr.gif" style="padding:5px;border:none" alt="" rendered="#{(pageCount gt 10)}"/>
            </f:facet>
          </t:dataScroller>
    
          <t:dataScroller for="meineDataTable" rowsCountVar="rowsCount"
                          displayedRowsCountVar="rowCountVar" firstRowIndexVar="firstRowIndex"
                          lastRowIndexVar="lastRowIndex" pageCountVar="pageCount"
                          immediate="true" pageIndexVar="pageIndex">
            <h:outputFormat rendered="#{!(pageCount le 1)}" value="Gesamt {0}, Seite {1}/{2}">
              <f:param value="#{rowsCount}" />
              <f:param value="#{pageIndex}" />
              <f:param value="#{pageCount}" />
            </h:outputFormat>
          </t:dataScroller>
    
        </h:panelGrid>
    
      </h:form>
    </h:body>
    </html>
    
  13. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabellePagSortTmhwkLargeDB

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfTabellePagSortTmhwkLargeDB]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               |- [backingbeans]
     |       |               |   '- MeineBackingBean.java
     |       |               |- [entities]
     |       |               |   |- MeineEntity.java
     |       |               |   '- MeineEntityDao.java
     |       |               '- [pageddata]
     |       |                   |- PagedDataList.java
     |       |                   '- PagedDataReader.java
     |       |- [jetty]
     |       |   |- override-mojarra-web.xml
     |       |   '- override-myfaces-web.xml
     |       |- [resources]
     |       |   '- [META-INF]
     |       |       '- persistence.xml
     |       '- [webapp]
     |           |- [img]
     |           |   |- pagin-arrow-ff.gif
     |           |   |- pagin-arrow-first.gif
     |           |   |- pagin-arrow-fr.gif
     |           |   |- pagin-arrow-last.gif
     |           |   |- pagin-arrow-next.gif
     |           |   '- pagin-arrow-previous.gif
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- stylesTmhwk.css
     |           '- tabelle.xhtml
     '- pom.xml
    
  14. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTabellePagSortTmhwkLargeDB

    mvn jetty:run

    start http://localhost:8080/JsfTabellePagSortTmhwkLargeDB/tabelle.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfTabellePagSortTmhwk-Webseite

    Testen Sie die Sortierung, indem Sie auf die Spaltentitel in der Tabelle klicken. Beachten Sie, dass die Sortierungsfunktion nicht die aktuelle Ansicht sortiert, sondern die gesamte Liste sortiert, und dann den korrekten Ausschnitt zeigt. Testen Sie auch die Paginierung über die unten angefügten Icons und Seitenzahlen.



Templating und Composition

Dieses Programmierbeispiel demonstriert das Facelets Templating. Allgemeine Informationen hierzu finden Sie unter Facelets Templating und Composition.

  1. Voraussetzung ist das Einführungs-Programmierbeispiel Hello World mit JSF (es werden die drei Dateien pom.xml, web.xml und override-mojarra-web.xml benötigt).

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfHelloWorld JsfTemplating\ /S

    cd JsfTemplating

    del src\main\webapp\hello.xhtml

    md src\main\webapp\WEB-INF\templates

  3. Ersetzen Sie im JsfTemplating-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfTemplating".

  4. Erstellen Sie im src\main\webapp\WEB-INF\templates-Verzeichnis die Template-XHTML-Datei: meinTemplate.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <h:head>
      <title>Templating</title>
    </h:head>
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
      <h2><h:outputText value="#{meinTitel}" /></h2>
      <h:messages style="color: red" />
      <p><h:outputText value="Hier ist der einheitliche Seitenkopf aus dem Template mit Logo, Navigation etc. ..."
                       style="background-color: lightblue" /></p>
    
      <ui:insert name="meinContent1" />
    
      <ui:insert name="meinContent2">
        <p>Dies ist der Default Content aus dem Template, falls "meinContent2" nicht definiert wird.</p>
      </ui:insert>
    
      <p><h:outputText value="Hier ist der einheitliche Seitenabschluss aus dem Template mit Links zum Impressum etc. ..."
                       style="background-color: lightblue" /></p>
    </h:body>
    </html>
    
  5. Erstellen Sie im src\main\webapp-Verzeichnis die Client-XHTML-Datei: meinErgebnis1.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    
    <ui:composition template="/WEB-INF/templates/meinTemplate.xhtml">
      <ui:param name="meinTitel" value="Meine 1. Ergebnisseite"/>
    
      <ui:define name="meinContent1">
        <p>Mein 1. XHTML-Inhalt:</p>
        <p><h:inputText  value=" ... ... " /><h:outputText value=" Abc Xyz" /></p>
        <p><h:commandButton value="Mein Button" /></p>
      </ui:define>
    
      <ui:define name="meinContent2">
        <p>Mein 2. XHTML-Inhalt:</p>
        <p>...</p>
      </ui:define>
    
    </ui:composition>
    
    </html>
    
  6. Erstellen Sie im src\main\webapp-Verzeichnis die Client-XHTML-Datei: meinErgebnis2.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    
    <ui:composition template="/WEB-INF/templates/meinTemplate.xhtml">
      <ui:param name="meinTitel" value="Meine 2. Ergebnisseite"/>
    
      <ui:define name="meinContent1">
        <p>Mein 1. XHTML-Inhalt der 2. Ergebnisseite:</p>
        <p><h:inputText  value=" ... ... " /><h:outputText value=" Abc Xyz 2" /></p>
        <p><h:commandButton value="Mein Button 2" /></p>
      </ui:define>
    
    </ui:composition>
    
    </html>
    

    Bitte beachten Sie, dass diesmal der "meinContent2" nicht definiert wird.

  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTemplating

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfTemplating]
     |- [src]
     |   '- [main]
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   |- [templates]
     |           |   |   '- meinTemplate.xhtml
     |           |   '- web.xml
     |           |- meinErgebnis1.xhtml
     |           '- meinErgebnis2.xhtml
     '- pom.xml
    
  8. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfTemplating

    mvn jetty:run

    start http://localhost:8080/JsfTemplating/meinErgebnis1.xhtml

    start http://localhost:8080/JsfTemplating/meinErgebnis2.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfTemplating-Webseite


Composite Component minimal

Dieses Programmierbeispiel zeigt eine sehr einfache "minimale" Composite Component, welche keinen Sinn macht, außer das Prinzip zu verdeutlichen. Anschließend folgen erweiterte Beispiele.

Allgemeine Informationen finden Sie unter Composite Components (Kompositkomponenten).

  1. Voraussetzung ist das Einführungs-Programmierbeispiel Hello World mit JSF (es werden die drei Dateien pom.xml, web.xml und override-mojarra-web.xml benötigt).

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfHelloWorld JsfCompositeComponent\ /S

    cd JsfCompositeComponent

    del src\main\webapp\hello.xhtml

    md src\main\webapp\resources\components

  3. Ersetzen Sie im JsfCompositeComponent-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfCompositeComponent".

  4. Erstellen Sie im src\main\webapp\resources\components-Verzeichnis die Composite-Component-XHTML-Datei: meineCompositeComponent1.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:cc="http://xmlns.jcp.org/jsf/composite">
    
    <cc:interface>
      <cc:attribute name="label" />
      <cc:attribute name="value" />
      <cc:editableValueHolder name="input" />
    </cc:interface>
    
    <cc:implementation>
      <h:outputLabel value="#{cc.attrs.label}" for="input" />&nbsp;
      <h:inputText   value="#{cc.attrs.value}" id="input" />
    </cc:implementation>
    
    </html>
    

    cc: referenziert die Composite Components Tag Library. Composite Components bestehen aus einem <cc:interface>-Teil für Übergabeattribute und einem <cc:implementation>-Teil für die eigentliche Implementierung. Die im <cc:interface>-Teil per <cc:attribute... definierten Attribute werden im <cc:implementation>-Teil über "#{cc.attrs...}" angesprochen. Durch das Interface-Attribut <cc:editableValueHolder... wird das Eingabefeld input aus der Komponente veröffentlicht, wodurch bei Bedarf das nutzende Facelets-XHTML darauf zugreifen kann und z.B. einen Konverter einsetzen kann.

  5. Erstellen Sie im src\main\webapp-Verzeichnis die Client-XHTML-Datei: meineCcSeite1.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:mycc="http://xmlns.jcp.org/jsf/composite/components">
    
    <h:body>
      <mycc:meineCompositeComponent1 label="Mein Label" value="Mein Input-Text" />
    </h:body>
    
    </html>
    

    Beachten Sie, wie sich der neue Namespace "http://xmlns.jcp.org/jsf/composite/components" zusammensetzt: Er beginnt immer mit "http://xmlns.jcp.org/jsf/composite/" (bzw. in früheren JSF-Versionen mit "http://java.sun.com/jsf/composite/") und dann folgt das bzw. folgen die Verzeichnisse unterhalb von resources bis zu dem Verzeichnis, in dem sich die eigene Composite Component befindet, im Beispiel ist dies das Verzeichnis components.

    Um den Wert des Eingabefelds in einer Backing Bean zu verwenden, müsste statt value="Mein Input-Text" ein entsprechender EL-Ausdruck eingesetzt werden, z.B. value="#{meineBean.meinInputWert}"

  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfCompositeComponent]
     |- [src]
     |   '- [main]
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [resources]
     |           |   '- [components]
     |           |       '- meineCompositeComponent1.xhtml
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- meineCcSeite1.xhtml
     '- pom.xml
    
  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    mvn jetty:run

    start http://localhost:8080/JsfCompositeComponent/meineCcSeite1.xhtml

    Sie erhalten:

    JsfCompositeComponent1-Webseite


Composite Component mit Facet

Dieses Programmierbeispiel erweitert das vorherige um die Möglichkeit, im nutzenden Faceltes-XHTML per <f:facet... definierte XHTML-Fragmente an die Composite Component weiterzugeben. In der Composite Component wird es im <cc:interface>-Teil über cc:facet... übergeben und im <cc:implementation>-Teil per cc:renderFacet... eingefügt und dargestellt.

  1. Voraussetzung ist das vorherige Programmierbeispiel Composite Component minimal.

  2. Erstellen Sie im src\main\webapp\resources\components-Verzeichnis die Composite-Component-XHTML-Datei: meineCompositeComponent2.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:cc="http://xmlns.jcp.org/jsf/composite">
    
    <cc:interface>
      <cc:facet name="meinFacet1" required="true" />
      <cc:facet name="meinFacet2" />
    </cc:interface>
    
    <cc:implementation>
      <h3><h:outputLabel value="Facet 1:" /></h3>
      <cc:renderFacet name="meinFacet1" />
      <h:panelGroup layout="block" rendered="#{!empty cc.facets.meinFacet2}">
        <h3><h:outputLabel value="Facet 2:" /></h3>
        <cc:renderFacet name="meinFacet2" />
      </h:panelGroup>
    </cc:implementation>
    
    </html>
    
  3. Erstellen Sie im src\main\webapp-Verzeichnis die Client-XHTML-Datei: meineCcSeite2.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:mycc="http://xmlns.jcp.org/jsf/composite/components">
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
    
      <mycc:meineCompositeComponent2>
        <f:facet name="meinFacet1">
          <h:outputLabel value="abc" for="input" />&nbsp;
          <h:inputText   value="Abc" id="input" />
        </f:facet>
        <f:facet name="meinFacet2">
          <h:outputLabel value="xyz" for="input" />&nbsp;
          <h:inputText   value="Xyz" id="input" />
        </f:facet>
      </mycc:meineCompositeComponent2>
    
    </h:body>
    </html>
    
  4. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfCompositeComponent]
     |- [src]
     |   '- [main]
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [resources]
     |           |   '- [components]
     |           |       |- meineCompositeComponent1.xhtml
     |           |       '- meineCompositeComponent2.xhtml
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- meineCcSeite1.xhtml
     |           '- meineCcSeite2.xhtml
     '- pom.xml
    
  5. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    mvn jetty:run

    start http://localhost:8080/JsfCompositeComponent/meineCcSeite2.xhtml

    Sie erhalten:

    JsfCompositeComponent2-Webseite


Composite Component mit Methodenbindung

Dieses Programmierbeispiel erweitert das vorherige um die Möglichkeit, einer Composite Component eine Methode (z.B. von einer Backing Bean) zu übergeben, die innerhalb der Composite Component aufgerufen wird, z.B. bei Betätigung eines Buttons innerhalb der Composite Component. Die Methode wird über <cc:interface><cc:attribute name="action" method-signature="..." targets="..." /> übergeben.

  1. Voraussetzung ist das Programmierbeispiel Composite Component minimal.

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    md src\main\java\de\meinefirma\meinprojekt\backingbeans

  3. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Backing-Bean-Datei: MeineBackingBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.ViewScoped;
    
    @ManagedBean
    @ViewScoped
    public class MeineBackingBean
    {
       private String uhrzeit = "Button wurde noch nicht angeklickt";
    
       public String getUhrzeit() { return uhrzeit; }
    
       public String meineButtonMethode()
       {
          uhrzeit = (new SimpleDateFormat( "HH:mm:ss" )).format( new Date() ) + " Uhr";
          return null;
       }
    }
    
  4. Erstellen Sie im src\main\webapp\resources\components-Verzeichnis die Composite-Component-XHTML-Datei: meineCompositeComponent3.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:cc="http://xmlns.jcp.org/jsf/composite">
    
    <cc:interface>
      <cc:attribute name="label" default="Mein Button" />
      <cc:attribute name="action" required="true"
                    method-signature="java.lang.String f()" targets="meinButton" />
      <cc:actionSource name="meinButton" />
    </cc:interface>
    
    <cc:implementation>
      <h:commandButton id="meinButton" value="#{cc.attrs.label}" />
    </cc:implementation>
    
    </html>
    

    Die Backing-Bean-Methode wird über <cc:interface><cc:attribute name="action" method-signature="..." targets="..." /> an die Composite Component übergeben. targets="..." definiert dabei das Aktionselement, welches den Methodenaufruf auslösen soll. Über den <cc:actionSource...-Eintrag wird das Aktionselement der Composite Component veröffentlicht, so dass bei Bedarf Action Listener registriert werden können.

  5. Erstellen Sie im src\main\webapp-Verzeichnis die Client-XHTML-Datei: meineCcSeite3.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:mycc="http://xmlns.jcp.org/jsf/composite/components">
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
    
      <h:form>
        <mycc:meineCompositeComponent3 action="#{meineBackingBean.meineButtonMethode}" />
        <h:outputText value=" --&gt; #{meineBackingBean.uhrzeit}" />
      </h:form>
    
    </h:body>
    </html>
    
  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfCompositeComponent]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- [backingbeans]
     |       |                   '- MeineBackingBean.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [resources]
     |           |   '- [components]
     |           |       |- meineCompositeComponent1.xhtml
     |           |       |- meineCompositeComponent2.xhtml
     |           |       '- meineCompositeComponent3.xhtml
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- meineCcSeite1.xhtml
     |           |- meineCcSeite2.xhtml
     |           '- meineCcSeite3.xhtml
     '- pom.xml
    
  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    mvn jetty:run

    start http://localhost:8080/JsfCompositeComponent/meineCcSeite3.xhtml

    Nach Klick auf den Button erhalten Sie:

    JsfCompositeComponent3-Webseite


Composite Component mit FacesComponent Backing Bean

Dieses Programmierbeispiel erweitert das vorherige um die Möglichkeit, in einer Composite Component Java-Code aus einer FacesComponent Backing Bean auszuführen.

  1. Voraussetzung ist das vorherige Programmierbeispiel Composite Component mit Methodenbindung.

  2. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die FacesComponent-Backing-Bean-Datei: MeineComponentBackingBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import javax.faces.component.FacesComponent;
    import javax.faces.component.NamingContainer;
    import javax.faces.component.UIInput;
    
    @FacesComponent
    public class MeineComponentBackingBean extends UIInput implements NamingContainer
    {
       private String uhrzeit = "Button wurde noch nicht angeklickt";
    
       public String getUhrzeit() { return uhrzeit; }
    
       public String meineButtonMethode()
       {
          uhrzeit = (new SimpleDateFormat( "HH:mm:ss" )).format( new Date() ) + " Uhr";
          return null;
       }
    
       @Override
       public String getFamily()
       {
          return "javax.faces.NamingContainer";
       }
    }
    

    Damit die Klasse in der Composite Component bei <cc:interface componentType="..." /> verwendet werden kann, muss sie

  3. Erstellen Sie im src\main\webapp\resources\components-Verzeichnis die Composite-Component-XHTML-Datei: meineCompositeComponent4.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:cc="http://xmlns.jcp.org/jsf/composite">
    
    <cc:interface componentType="meineComponentBackingBean" />
    
    <cc:implementation>
      <h:form>
        <h:commandButton id="meinButton" value="Mein Button" action="#{cc.meineButtonMethode}" />
        <h:outputText value=" --&gt; #{cc.uhrzeit}" />
      </h:form>
    </cc:implementation>
    
    </html>
    

    Beachten Sie, wie über <cc:interface componentType="..." /> die FacesComponent Backing Bean angegeben wird, und wie über #{cc...} auf die Attribute und Methoden der FacesComponent Backing Bean zugegriffen wird.

  4. Erstellen Sie im src\main\webapp-Verzeichnis die Client-XHTML-Datei: meineCcSeite4.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:mycc="http://xmlns.jcp.org/jsf/composite/components">
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
    
      <mycc:meineCompositeComponent4 />
    
    </h:body>
    </html>
    
  5. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfCompositeComponent]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- [backingbeans]
     |       |                   |- MeineBackingBean.java
     |       |                   '- MeineComponentBackingBean.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [resources]
     |           |   '- [components]
     |           |       |- meineCompositeComponent1.xhtml
     |           |       |- meineCompositeComponent2.xhtml
     |           |       |- meineCompositeComponent3.xhtml
     |           |       '- meineCompositeComponent4.xhtml
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- meineCcSeite1.xhtml
     |           |- meineCcSeite2.xhtml
     |           |- meineCcSeite3.xhtml
     |           '- meineCcSeite4.xhtml
     '- pom.xml
    
  6. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfCompositeComponent

    mvn jetty:run

    start http://localhost:8080/JsfCompositeComponent/meineCcSeite4.xhtml



Confirm Dialog mit PrimeFaces

Dieses Programmierbeispiel zeigt:

Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:

  1. Voraussetzung ist das vorherige Programmierbeispiel Hello World mit JSF (es werden die drei Dateien pom.xml, web.xml und override-mojarra-web.xml benötigt).

  2. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    xcopy JsfHelloWorld JsfConfirmDialogPrmfcs\ /S

    cd JsfConfirmDialogPrmfcs

    del src\main\webapp\hello.xhtml

    md src\main\java\de\meinefirma\meinprojekt\backingbeans

  3. Ersetzen Sie im JsfConfirmDialogPrmfcs-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfConfirmDialogPrmfcs".

  4. Ergänzen Sie in der pom.xml vor </dependencies> die Abhängigkeit:

        <dependency>
          <groupId>org.primefaces</groupId>
          <artifactId>primefaces</artifactId>
          <version>5.1</version>
        </dependency>
    
  5. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Backing-Bean-Datei: ConfirmDialogsBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import javax.faces.application.FacesMessage;
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.ViewScoped;
    import javax.faces.context.FacesContext;
    
    // Backing Bean fuer confirmDialogs.xhtml
    @ManagedBean
    @ViewScoped
    public class ConfirmDialogsBean
    {
       public void doAktion1()
       {
          System.out.println( "Aktion 1 durchgefuehrt." );
          FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_INFO, "Aktion 1 durchgeführt.", null );  
          FacesContext.getCurrentInstance().addMessage( null, message );  
       }
    
       public void doAktion2()
       {
          System.out.println( "Aktion 2 durchgefuehrt." );
          FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_INFO, "Aktion 2 durchgeführt.", null );  
          FacesContext.getCurrentInstance().addMessage( null, message );  
       }
    }
    

    doAktion1() und doAktion2() sind zwei Methoden, welche nur nach expliziter Bestätigung von Sicherheitsabfragen gestartet werden sollen.

  6. Erstellen Sie im src\main\webapp-Verzeichnis die Facelets-XHTML-Datei: confirmDialogs.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html"
          xmlns:pf="http://primefaces.org/ui">
    <h:head>
      <title>Zwei PrimeFaces-Confirm-Dialoge auf einer Seite</title>
    </h:head>
    <h:body style="font-family: Helvetica, Arial, Sans-Serif;">
      <h2><h:outputText value="Zwei PrimeFaces-Confirm-Dialoge auf einer Seite" /></h2>
      <h:messages style="color: red" />
      <h:form>
        <pf:growl id="messages" />
        <pf:commandButton value="Führe Aktion 1 aus" onclick="PF('confirmation1').show()" />
        <pf:confirmDialog widgetVar="confirmation1"  header="Bestätigungsabfrage zu Aktion 1" message="Wollen Sie wirklich Aktion 1 ausführen?"
                          severity="alert" closeable="false" showEffect="fade" hideEffect="fold">
          <pf:commandButton value="ja"   action="#{confirmDialogsBean.doAktion1}" oncomplete="PF('confirmation1').hide()" update="messages" />
          <pf:commandButton value="nein" onclick="PF('confirmation1').hide()" />
        </pf:confirmDialog>
        <br/><br/>
        <pf:commandButton value="Führe Aktion 2 aus" onclick="PF('confirmation2').show()" />
        <pf:confirmDialog widgetVar="confirmation2"  header="Bestätigungsabfrage zu Aktion 2" message="Wollen Sie wirklich Aktion 2 ausführen?"
                          severity="alert" closeable="false" showEffect="fade" hideEffect="fold">
          <pf:commandButton style="float:right" value="nein" onclick="PF('confirmation2').hide()" />
          <pf:commandButton style="float:right" value="ja"   action="#{confirmDialogsBean.doAktion2}" oncomplete="PF('confirmation2').hide()" update="messages" />
        </pf:confirmDialog>
      </h:form>
    </h:body>
    </html>
    

    Diese XHTML-Seite beinhaltet folgende Besonderheiten:

  7. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfConfirmDialogPrmfcs

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfConfirmDialogPrmfcs]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- [backingbeans]
     |       |                   '- ConfirmDialogsBean.java
     |       |- [jetty]
     |       |   '- override-mojarra-web.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           '- confirmDialogs.xhtml
     '- pom.xml
    
  8. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfConfirmDialogPrmfcs

    mvn jetty:run

    start http://localhost:8080/JsfConfirmDialogPrmfcs/confirmDialogs.xhtml

    Im Webbrowser erscheint die Ausgabe:

    JsfConfirmDialogPrmfcs-Webseite
  9. Falls Sie folgende Fehlermeldung erhalten:

    Server Error Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Ungültiges Byte 1 von 1-Byte-UTF-8-Sequenz

    Achten Sie darauf, dass Sie confirmDialogs.xhtml mit UTF-8-Character-Encoding speichern.

  10. Falls Sie Probleme mit PrimeFaces haben, sollten Sie sich den PrimeFaces-Logger ansehen, siehe PrimeFaces Log. Er wird durch einfaches Einfügen von "<p:log id="log" /> in Ihre Facelets-XHTML-Datei aktiviert.

  11. Falls Sie das in Ihrer HTML- oder Facelets-XHTML-Seite implementierte JavaScript debuggen wollen: Verwenden Sie hierzu den Firefox-Webbrowser, laden Sie darin die zu debuggende Webseite, und starten Sie mit "Strg+Shift+S" den JavaScript-Debugger. Mit der rechten Maustaste können Sie im JavaScript-Code Breakpoints setzen.
    Falls Sie PrimeFaces-JavaScript debuggen wollen: Das PrimeFaces-JavaScript ist "minified" und dadurch beim Debuggen kaum zu verstehen. Dies können Sie ändern wie beschrieben ist unter: How to debug javascript in PrimeFaces.



Implementierungen, Versionen, Session, Classpath, Libs etc. anzeigen

Manchmal gibt es Probleme mit der Reihenfolge im CLASSPATH, insbesondere in Java EE Application Servern, die eigene jar-Libs mitbringen (siehe z.B. JAXB-Provider-Problem und Classloader-Reihenfolge). Dann wird eventuell ungewollt eine andere Version oder sogar eine andere Implementierung verwendet, als vorgesehen. Problematisch ist auch, wenn aus verschiedenen Libs gleichnamige Klassen mit dem selben Package-Namen mehrfach, aber eventuell in verschiedenen Versionen und Funktionalitäten, eingebunden sind. Das folgende Beispiel zeigt, wie die Implementierungen und Versionen einiger Libs sowie mehrfache Klassen ermittelt und angezeigt werden können. Damit im Beispiel überhaupt etwas angezeigt werden kann, wird ein Dummy-Projekt mit einigen willkürlichen Dependencies angelegt.

  1. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace

    md JsfInspectImplementationsVersions

    cd JsfInspectImplementationsVersions

    md src\main\java\de\meinefirma\meinprojekt\backingbeans

    md src\main\jetty

    md src\main\resources\META-INF

    md src\main\webapp\WEB-INF

  2. Erstellen Sie im JsfInspectImplementationsVersions-Projektverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>de.meinefirma.meinprojekt</groupId>
      <artifactId>JsfInspectImplementationsVersions</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
      <name>JsfInspectImplementationsVersions</name>
      <build>
        <finalName>${project.artifactId}</finalName>
        <defaultGoal>install</defaultGoal>
        <plugins>
          <plugin>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-maven-plugin</artifactId>
            <version>9.2.6.v20141205</version>
            <configuration>
              <webAppConfig>
                <contextPath>/${project.artifactId}</contextPath>
    
                <!-- Mojarra: -->
                <overrideDescriptor>src/main/jetty/override-mojarra-web.xml</overrideDescriptor>
                <!-- oder alternativ MyFaces:
                <overrideDescriptor>src/main/jetty/override-myfaces-web.xml</overrideDescriptor>
                -->
    
              </webAppConfig>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
              <source>1.7</source>
              <target>1.7</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
    
        <!-- Mojarra: -->
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-api</artifactId>
          <version>2.2.8</version>
        </dependency>
        <dependency>
          <groupId>com.sun.faces</groupId>
          <artifactId>jsf-impl</artifactId>
          <version>2.2.8</version>
        </dependency>
        <!-- oder alternativ MyFaces:
        <dependency>
          <groupId>org.apache.myfaces.core</groupId>
          <artifactId>myfaces-api</artifactId>
          <version>2.2.6</version>
        </dependency>
        <dependency>
          <groupId>org.apache.myfaces.core</groupId>
          <artifactId>myfaces-impl</artifactId>
          <version>2.2.6</version>
        </dependency>
        -->
    
        <dependency>
          <groupId>org.apache.myfaces.tomahawk</groupId>
          <artifactId>tomahawk20</artifactId>
          <version>1.1.14</version>
        </dependency>
        <dependency>
          <groupId>org.primefaces</groupId>
          <artifactId>primefaces</artifactId>
          <version>5.1</version>
        </dependency>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>javax.validation</groupId>
          <artifactId>validation-api</artifactId>
          <version>1.1.0.Final</version>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-validator</artifactId>
          <version>5.1.3.Final</version>
        </dependency>
        <dependency>
          <groupId>org.apache.openjpa</groupId>
          <artifactId>openjpa</artifactId>
          <version>2.3.0</version>
        </dependency>
        <dependency>
          <groupId>org.apache.derby</groupId>
          <artifactId>derby</artifactId>
          <version>10.11.1.1</version>
        </dependency>
        <!-- Nur falls vorhanden:
        <dependency>
          <groupId>com.oracle.jdbc</groupId>
          <artifactId>ojdbc7</artifactId>
          <version>12.1</version>
        </dependency>
        -->
      </dependencies>
    </project>
    
  3. Erstellen Sie im src\main\webapp\WEB-INF-Verzeichnis die Deployment-Descriptor-Datei: web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
      <!-- Nur falls erforderlich (z.B. in bestimmten WebLogic-Versionen):
      <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
      </context-param>
      -->
      <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
      </servlet-mapping>
    </web-app>
    
  4. Erstellen Sie im src\main\jetty-Verzeichnis die Jetty/Mojarra-Konfigurationsdatei: override-mojarra-web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
      <listener>
        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
      </listener>
    </web-app>
    
  5. Erstellen Sie im src\main\jetty-Verzeichnis die Jetty/MyFaces-Konfigurationsdatei: override-myfaces-web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
      <listener>
        <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
      </listener>
    </web-app>
    
  6. Erstellen Sie im src\main\resources\META-INF-Verzeichnis die JPA-Konfigurationsdatei: persistence.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0"
        xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                            http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
      <persistence-unit name="MeineJpaPU" transaction-type="RESOURCE_LOCAL">
        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <properties>
          <property name="openjpa.ConnectionDriverName"     value="org.apache.derby.jdbc.EmbeddedDriver" />
          <property name="openjpa.ConnectionURL"            value="jdbc:derby:./target/Derby-DB/mydb;create=true" />
          <property name="openjpa.ConnectionUserName"       value="" />
          <property name="openjpa.ConnectionPassword"       value="" />
          <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema" />
        </properties>
      </persistence-unit>
    </persistence>
    
  7. Erzeugen Sie im Verzeichnis src\main\webapp die CSS-Stylesheet-Datei: stylesTbl.css

    body {
      font-family: Helvetica, Arial, Sans-Serif;
      font-size: 75%;
    }
    table {
      border-collapse: collapse;
      border: 1px solid #d6d6d6;
      background-color: #f7f7f7;
    }
    table th {
      border: 1px solid #d6d6d6;
      background-color: #eeeeee;
      padding: 5px;
    }
    table td {
      border: 1px solid #d6d6d6;
      padding: 5px;
    }
    
  8. Erzeugen Sie im Verzeichnis src\main\webapp die Facelets-XHTML-Datei: inspectImplementationsVersions.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
      <title>Implementierungen, Versionen, Session, Classpath, Libs etc.</title>
      <link rel="stylesheet" type="text/css" href="stylesTbl.css" />
    </head>
    <h:body>
      <h2><h:outputText value="Implementierungen, Versionen, Session, Classpath, Libs etc." /></h2>
      <h:messages style="color: red" />
      <h:form>
        <h:dataTable value="#{inspectImplementationsVersionsBean.implementationsVersionsList}" var="loopItem">
          <h:column>
            <f:facet name="header"><h:outputText value="Attribut"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[0]}" style="white-space: nowrap" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Implementierung, Version, Wert"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[1]}" />
          </h:column>
        </h:dataTable>
      </h:form>
      <br/>
      <h:form>
        <h:dataTable value="#{inspectImplementationsVersionsBean.sessionMapList}" var="loopItem">
          <h:column>
            <f:facet name="header"><h:outputText value="Session Key"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[0]}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Session Value"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[1]}" />
          </h:column>
        </h:dataTable>
      </h:form>
      <br/>
      <h:form>
        <h:dataTable value="#{inspectImplementationsVersionsBean.classpathString}" var="loopItem">
          <h:column>
            <f:facet name="header"><h:outputText value="Environment-Classpath"></h:outputText></f:facet>
            <h:outputText value="#{loopItem}" />
          </h:column>
        </h:dataTable>
      </h:form>
      <br/>
      <h:form>
        <h:dataTable value="#{inspectImplementationsVersionsBean.libsAndMulClassCnt}" var="loopItem">
          <h:column>
            <f:facet name="header"><h:outputText value="Pfad oder Lib"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[0]}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Mehrfache Klassen"></h:outputText></f:facet>
            <h:commandButton value="#{loopItem[1]}" rendered="#{!empty loopItem[1]}" action="#{inspectImplementationsVersionsBean.zeigeMultipleClasses}">
              <f:setPropertyActionListener target="#{inspectImplementationsVersionsBean.ausgewaehlteLib}" value="#{loopItem[0]}" />
            </h:commandButton>
            <h:outputText value="#{loopItem[2]}" />
          </h:column>
        </h:dataTable>
      </h:form>
      <br/>
    </h:body>
    </html>
    
  9. Erzeugen Sie im Verzeichnis src\main\webapp die Facelets-XHTML-Datei: multipleClasses.xhtml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:f="http://xmlns.jcp.org/jsf/core"
          xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
      <title>Mehrfache Klassen</title>
      <link rel="stylesheet" type="text/css" href="stylesTbl.css" />
    </head>
    <h:body>
      <h2><h:outputText value="Klassen, die in mehreren Libs enthalten sind, und die Libs" /></h2>
      <h:messages style="color: red" />
      <h:form>
        <h:dataTable value="#{inspectImplementationsVersionsBean.multipleClassesList}" var="loopItem">
          <h:column>
            <f:facet name="header"><h:outputText value="Klassen, die in mehreren Libs enthalten sind"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[0]}" />
          </h:column>
          <h:column>
            <f:facet name="header"><h:outputText value="Libs, welche die Klasse enthalten"></h:outputText></f:facet>
            <h:outputText value="#{loopItem[1]}" />
          </h:column>
        </h:dataTable>
        <br/>
        <h:commandButton value="zurück" action="#{inspectImplementationsVersionsBean.zuInspectImplementationsVersions}" />
      </h:form>
      <br/>
    </h:body>
    </html>
    
  10. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Backing-Bean-Datei: InspectImplementationsVersionsBean.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.io.InputStream;
    import java.net.InetAddress;
    import java.util.*;
    import javax.faces.bean.*;
    import javax.faces.context.*;
    import javax.persistence.*;
    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpServletRequest;
    
    @ManagedBean
    @ViewScoped
    public class InspectImplementationsVersionsBean
    {
       // Falls es keinen EntityManager gibt, muessen die folgenden zwei Zeilen deaktiviert werden,
       // ansonsten muss der PERSISTENCE_UNIT_NAME angepasst werden:
       private static final String PERSISTENCE_UNIT_NAME = "MeineJpaPU";
       private EntityManagerFactory emf = Persistence.createEntityManagerFactory( PERSISTENCE_UNIT_NAME );
       private static final int MAX_LEN = 160;
       private static final String KEY_IN_SESSION_INSPECTLIBSANDCLASSES = "InspectLibsAndClasses";
       private InspectLibsAndClasses inspectLibsAndClasses;
    
       public InspectImplementationsVersionsBean()
       {
          final ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
          final ServletContext  servletContext  = (ServletContext) externalContext.getContext();
          Map<String,Object> sessionMap = externalContext.getSessionMap();
          inspectLibsAndClasses = (InspectLibsAndClasses) sessionMap.get( KEY_IN_SESSION_INSPECTLIBSANDCLASSES );
          sessionMap.put( KEY_IN_SESSION_INSPECTLIBSANDCLASSES, null );
          if( inspectLibsAndClasses == null ) {
             inspectLibsAndClasses = new InspectLibsAndClasses(
                new InspectLibsAndClasses.ServletContextStellvertreter() {
                   @Override public InputStream getResourceAsStream( String resourcePath ) {
                      return servletContext.getResourceAsStream( resourcePath );
                   }
                   @Override public Set<String> getResourcePaths( String resourceLibPath ) {
                      return servletContext.getResourcePaths( resourceLibPath );
                   }
                } );
          }
       }
    
       // Bitte beachten: Je nach eingebundenen Libs stehen nicht alle Funktionen zur Verfuegung
       public List<String[]> getImplementationsVersionsList() throws Exception
       {
          FacesContext ctx = FacesContext.getCurrentInstance();
          HttpServletRequest request = (HttpServletRequest) ctx.getExternalContext().getRequest();
          String angefragterServer = request.getServerName();
          if( request.getServerPort() != request.getLocalPort() ) { angefragterServer += ":" + request.getServerPort(); }
          angefragterServer += " (";
          if( request.getLocalName() != null && !request.getLocalName().equals( request.getServerName() )
                                             && !request.getLocalName().equals( request.getLocalAddr() )) {
             angefragterServer += request.getLocalName() + ", ";
          }
          angefragterServer += request.getLocalAddr() + ":" + request.getLocalPort() + ")";
    
          String requestClientServer = "Anfragender Client: " + request.getRemoteUser() + " (" + request.getRemoteAddr() + ":" + request.getRemotePort() +
                                       "), angefragter Server: " + angefragterServer;
          String betriebssystemPatchlevel = System.getProperty( "sun.os.patch.level" );
          betriebssystemPatchlevel =
                ( betriebssystemPatchlevel == null || betriebssystemPatchlevel.trim().isEmpty() || "unknown".equalsIgnoreCase( betriebssystemPatchlevel ) )
                ? "" : (betriebssystemPatchlevel + ", " );
          List<String[]> result = new ArrayList<String[]>();
          addKeyValue( "Host-Name", InetAddress.getLocalHost().getHostName() + " (" + InetAddress.getLocalHost().getHostAddress() + ")", result );
          addKeyValue( "Betriebssystem", System.getProperty( "os.name" ) + ", " + System.getProperty( "os.version" ) + ", "
                + betriebssystemPatchlevel + System.getProperty( "os.arch" ) + ", "
                + System.getProperty( "sun.arch.data.model" ) + " bit", result );
          addKeyValue( "Java-Version", System.getProperty( "java.runtime.version" ) + " (" + System.getProperty( "java.vm.version" ) + ")", result );
          addKeyValue( "Server-Info", ((ServletContext) ctx.getExternalContext().getContext()).getServerInfo(), result );
          addKeyValue( "Context-Name", ctx.getExternalContext().getContextName(), result );
          addKeyValue( "DatatypeFactory", javax.xml.datatype.DatatypeFactory.DATATYPEFACTORY_PROPERTY, result );
          addKeyValue( "DatatypeFactoryImpl", javax.xml.datatype.DatatypeFactory.DATATYPEFACTORY_IMPLEMENTATION_CLASS, result );
          addClassName( "XMLGregorianCalendar", javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar( "2015" ).getClass(), result );
          try {
             addClassName( "JAXB-Provider", javax.xml.bind.JAXBContext.newInstance().getClass(), result );
          } catch( NoClassDefFoundError e ) { /* ok */ }
          try {
             Class<?> bcClss = Class.forName( "org.bouncycastle.bcpg.BCPGKey" );
             addImplementationVersionFromPackage( "BouncyCastle", bcClss.getPackage(), result );
          } catch( ClassNotFoundException e ) { /* ok */ }
          addImplementationVersionFromPackage( "Slf4j", "org.slf4j", result );
          addSpecificationVersionFromPackage( "EJB-Spec", "javax.ejb", result );
          addImplementationVersionFromPackage( "Hibernate-Validator", "org.hibernate.validator", result );
          try {
             Class<?> oraClss = Class.forName( "oracle.jdbc.OracleDriver" );
             addSpecificationVersionFromPackage( "Oracle-JDBC-Spec", oraClss.getPackage(), result );
             addImplementationVersionFromPackage( "Oracle-JDBC-Impl", oraClss.getPackage(), result );
          } catch( ClassNotFoundException e ) { /* ok */ }
          addSpecificationVersionFromPackage( "JPA-Spec", EntityManager.class.getPackage(), result );
          addImplementationVersionFromPackage( "JPA-Impl", EntityManager.class.getPackage(), result );
          if( emf != null ) {
             addImplementationVersionFromPackage( "JPA-Provider", emf.getClass().getPackage(), result );
          }
          addImplementationVersionFromPackage( "EclipseLink", "org.eclipse.persistence", result );
          addImplementationVersionFromPackage( "Jetty", "org.eclipse.jetty.server", result );
          addSpecificationVersionFromPackage( "Servlet-Spec", "javax.servlet", result );
          addImplementationVersionFromPackage( "JAX-RS", "javax.ws.rs", result );
          addImplementationVersionFromPackage( "Jersey 1", "com.sun.jersey.api.client", result );
          addImplementationVersionFromPackage( "Jersey 2", "org.glassfish.jersey.servlet", result );
          addSpecificationVersionFromPackage( "JSF-Spec", FacesContext.class.getPackage(), result );
          addImplementationVersionFromPackage( "JSF-API", FacesContext.class.getPackage(), result );
          addImplementationVersionFromPackage( "JSF-Impl", ctx.getClass().getPackage(), result );
          addImplementationVersionFromPackage( "MyFaces", "org.apache.myfaces.context", result );
          addClassName( "JSF-RenderKit", ctx.getRenderKit().getClass(), result );
          addImplementationVersionFromPackage( "Tomahawk", "org.apache.myfaces.tomahawk.application", result );
          addImplementationVersionFromPackage( "PrimeFaces", "org.primefaces.context", result );
          addKeyValue( "Context", ctx.getExternalContext().getContext().toString().replace( "{", " {" ).replace( ",", ", " ), result );
          addKeyValue( "Request-Client/Server", requestClientServer, result );
          addKeyValue( "Request-Objekt", ctx.getExternalContext().getRequest(), result );
          addKeyValue( "RequestParameterMap", ctx.getExternalContext().getRequestParameterMap(), result );
          String locMsg = null;
          locMsg = InspectLibsAndClasses.concatWithComma( locMsg, "ViewRoot-Locale=", ctx.getViewRoot().getLocale() );
          locMsg = InspectLibsAndClasses.concatWithComma( locMsg, "Default-Locale=",  ctx.getApplication().getDefaultLocale() );
          locMsg = InspectLibsAndClasses.concatWithComma( locMsg, "MessageBundle=",   ctx.getApplication().getMessageBundle() );
          addKeyValue( "Locale/MessageBundle", locMsg, result );
          return result;
       }
    
       public List<String[]> getSessionMapList()
       {
          List<String[]> result = new ArrayList<String[]>();
          Map<String,Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
          Map<String,Object> sessionMapSorted = new TreeMap<String,Object>( sessionMap );
          for( Map.Entry<String,Object> entry : sessionMapSorted.entrySet() ) {
             addKeyValue( entry.getKey(), entry.getValue(), MAX_LEN / 2, result );
          }
          return result;
       }
    
       public List<String> getClasspathString()
       {
          String classpath = System.getProperty( "java.class.path" );
          if( classpath == null ) { return null; }
          return Arrays.asList( new String[] { classpath.replace( ";", "; " ).replace( ":", ": " ) } );
       }
    
       public List<String[]> getLibsAndMulClassCnt()
       {
          return inspectLibsAndClasses.getLibsAndMulClassCnt();
       }
    
       public String zeigeMultipleClasses()
       {
          Map<String,Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
          sessionMap.put( KEY_IN_SESSION_INSPECTLIBSANDCLASSES, inspectLibsAndClasses );
          return "multipleClasses.xhtml";
       }
    
       public List<String[]> getMultipleClassesList()
       {
          return inspectLibsAndClasses.getMultipleClassesList();
       }
    
       public String zuInspectImplementationsVersions()
       {
          Map<String,Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
          sessionMap.put( KEY_IN_SESSION_INSPECTLIBSANDCLASSES, inspectLibsAndClasses );
          return "inspectImplementationsVersions.xhtml";
       }
    
       // Diese Methode funktioniert nur in bestimmten Konstellationen und nur, wenn ein Zugriff auf
       // EntityManagerFactory und EntityManagerFactory.getProperties() moeglich ist:
       public List<String[]> getEntityManagerPropertiesList()
       {
          List<String[]> result = new ArrayList<String[]>();
          try {
             if( emf == null || emf.getProperties() == null ) { return null; }
             addImplementationVersionFromPackage( "JPA-EntityManager", EntityManager.class.getPackage(), result );
             addImplementationVersionFromPackage( "JPA-Provider", emf.getClass().getPackage(), result );
             for( Map.Entry<String,Object> entry : emf.getProperties().entrySet() ) {
                addKeyValue( entry.getKey(), entry.getValue(), MAX_LEN / 2, result );
             }
          } catch( Exception ex ) {
             addKeyValue( ex.getClass().toString(), ex.getMessage(), result );
          }
          return result;
       }
    
       private static void addSpecificationVersionFromPackage( String key, String pckg, List<String[]> result )
       {
          addSpecificationVersionFromPackage( key, Package.getPackage( pckg ), result );
       }
    
       private static void addSpecificationVersionFromPackage( String key, Package pckg, List<String[]> result )
       {
          if( pckg == null ) { return; }
          String val  = pckg.getSpecificationTitle();
          if( val == null ) { val = pckg.getName(); }
          String vers = pckg.getSpecificationVersion();
          if( vers != null && !val.contains( vers ) ) { val += " "  + pckg.getSpecificationVersion(); }
          if( pckg.getSpecificationVendor() != null ) { val += ", " + pckg.getSpecificationVendor(); }
          addKeyValue( key, val, result );
       }
    
       private static void addImplementationVersionFromPackage( String key, String pckg, List<String[]> result )
       {
          addImplementationVersionFromPackage( key, Package.getPackage( pckg ), result );
       }
    
       private static void addImplementationVersionFromPackage( String key, Package pckg, List<String[]> result )
       {
          if( pckg == null ) { return; }
          String val = pckg.getImplementationTitle();
          if( val == null ) { val = pckg.getName(); }
          if( pckg.getImplementationVersion() != null ) { val += " "  + pckg.getImplementationVersion(); }
          if( pckg.getImplementationVendor()  != null ) { val += ", " + pckg.getImplementationVendor(); }
          addKeyValue( key, val, result );
       }
    
       private static void addClassName( String key, Class<?> clss, List<String[]> result )
       {
          if( clss == null ) { return; }
          String val = clss.toString();
          if( val.startsWith( "class " ) ) { val = val.substring( 6 ); }
          addKeyValue( key, val, result );
       }
    
       private static void addKeyValue( String key, Object value, List<String[]> result )
       {
          addKeyValue( key, value, MAX_LEN, result );
       }
    
       private static void addKeyValue( String key, Object value, int maxlen, List<String[]> result )
       {
          if( key == null || value == null ) { return; }
          String val = value.toString();
          if( val == null || val.trim().length() == 0 || "{}".equals( val ) ) { return; }
          int maxlen2 = maxlen/2 - 6;
          String key2 = ( key.length() <= maxlen ) ? key : (key.substring( 0, maxlen2 ) + "... ... ..." + key.substring( key.length() - maxlen2 ) );
          String val2 = ( val.length() <= maxlen ) ? val : (val.substring( 0, maxlen2 ) + "... ... ..." + val.substring( val.length() - maxlen2 ) );
          result.add( new String[] { key2, val2 } );
       }
    
       public void setAusgewaehlteLib( String ausgewaehlteLib )
       {
          inspectLibsAndClasses.setAusgewaehlteLib( ausgewaehlteLib );
       }
    }
    
  11. Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Hilfsdatei: InspectLibsAndClasses.java

    package de.meinefirma.meinprojekt.backingbeans;
    
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.util.jar.*;
    
    /** Inspiziere Libs und Klassen.
        Ermittelt im Classpath und im Web-Inf-Lib-Pfad vorhandene Libs, und in diesen Libs mehrfach vorhandene Klassen.
        Damit diese Utility-Klasse sowohl in einer JSF-Webapp als auch in einem Stand-alone-Kommandozeilentool
        verwendet werden kann, hat sie keine direkte Abhaengigkeit zu ServletContext,
        und zwei Konstruktoren fuer die beiden Einsatzzwecke. */
    public class InspectLibsAndClasses
    {
       // Die Eintraege im folgenden String-Array muessen an die gewuenschten Ausschluesse angepasst werden:
       private static final String[] EXCLD_ARR = new String[] {
          "about.html", "about.ini", "about.mappings", "about.properties", ".api_description", "build.properties",
          "eclipse32.gif", "eclipse32.png", "modeling32.png", ".options", "overview.html", "plugin.properties", "plugin.xml", "source_tips" };
       private static final Set<String> EXCLD_SET = new HashSet<String>( Arrays.asList( EXCLD_ARR ) );
       private static final String RESOURCE_LIB_PATH = "/WEB-INF/lib";
       private Map<String,Set<String>> classesToJars = null;
       private List<String[]> libsAndMulClassCnt = null;
       private String ausgewaehlteLib = null;
       private ServletContextStellvertreter servletContext = null;
    
       /** Konstruktor falls ohne ServletContext (z.B. im Stand-alone-Kommandozeilentool) */
       public InspectLibsAndClasses()
       {
       }
    
       /** Konstruktor falls mit ServletContext (z.B. in JSF-Webapp, Beispiel siehe InspectImplementationsVersionsBean.java) */
       public InspectLibsAndClasses( ServletContextStellvertreter servletContext )
       {
          this.servletContext = servletContext;
       }
    
       /** Nur falls Stand-alone-Kommandozeilentool (nicht fuer Webapp) */
       public static void main( String[] args )
       {
          InspectLibsAndClasses inspectLibsAndClasses = new InspectLibsAndClasses();
          List<String[]> libsAndMulClassCnt = inspectLibsAndClasses.getLibsAndMulClassCnt();
          for( String[] libAndCount : libsAndMulClassCnt ) {
             if( libAndCount[1] != null && libAndCount[1].length() > 0 ) {
                Set<String> libsSet = new HashSet<String>();
                inspectLibsAndClasses.setAusgewaehlteLib( libAndCount[0] );
                List<String[]> classAndLibsList = inspectLibsAndClasses.getMultipleClassesList();
                for( String[] classAndLibs : classAndLibsList ) {
                   for( String lb : classAndLibs[1].split( "," ) ) {
                      libsSet.add( lb.trim() );
                   }
                }
                int anz = inspectLibsAndClasses.anzahlKlassen( libAndCount[0] );
                String s = "ClasspathDublettenCheck: " + libAndCount[0] + " enthaelt " +
                           (( anz > 0 ) ? (anz + " relevante Dateien, davon ") : "") +
                           libAndCount[1] + " in mehreren Libs vorhandene Dateien " + libsSet + ".";
                System.out.println( s );
             }
          }
       }
    
       /** Liste aller Libs, und falls es in der jeweiligen Lib Klassen gibt, die es auch in anderen Libs gibt,
           jeweils die Anzahl solcher mehrfach vorkommenden Klassen
           (falls JsfInspectImplementationsVersions-JSF-Tool: verwendet in: inspectImplementationsVersions.xhtml) */
       public List<String[]> getLibsAndMulClassCnt()
       {
          if( libsAndMulClassCnt == null ) {
             Set<String> result = new TreeSet<String>();
             classesToJars = new HashMap<String,Set<String>>();
             List<URL> urls = getClassLoaderUrlList();
             for( URL url : urls ) {
                result.add( url.getPath() );
             }
             if( servletContext != null ) {
                Set<String> pathes = new TreeSet<String>( servletContext.getResourcePaths( RESOURCE_LIB_PATH ) );
                result.addAll( pathes );
             }
             for( String path : result ) {
                visitFile( path, path );
             }
             libsAndMulClassCnt = collectMultipleClasses( result );
          }
          return libsAndMulClassCnt;
       }
    
       /** Zu einer einzelnen Lib die Liste der Klassen, welche auch in anderen Libs vorkommen,
           sowie die Auflistung der Libs, in der die Klasse vorkommt
           (falls JsfInspectImplementationsVersions-JSF-Tool: verwendet in: multipleClasses.xhtml) */
       public List<String[]> getMultipleClassesList()
       {
          if( ausgewaehlteLib == null ) { return null; }
          List<String[]> multipleClasses = new ArrayList<String[]>();
          for( Map.Entry<String,Set<String>> me : classesToJars.entrySet() ) {
             Set<String> libs = me.getValue();
             if( libs.size() <= 1 || !libs.contains( extractLibName( ausgewaehlteLib ) ) ) { continue; }
             String lbStr = null;
             for( String lbi : libs ) {
                String lb = ( lbi.startsWith( "/" ) && (lbi.endsWith( ".jar" ) || lbi.endsWith( ".zip" )) )
                            ? lbi.substring( 1 ) : lbi;
                lbStr = concatWithComma( lbStr, "", lb );
             }
             multipleClasses.add( new String[] { me.getKey(), lbStr } );
          }
          return multipleClasses;
       }
    
       // Alternativen fuer Classloader bzw. Classpath (mit teilweise unterschiedlichen Ergebnissen):
       // ClassLoader cl = Thread.currentThread().getContextClassLoader();
       // ClassLoader cl = getClass().getClassLoader();
       // ClassLoader cl = ClassLoader.getSystemClassLoader();
       // String classpath = System.getProperty( "java.class.path" );
       private static List<URL> getClassLoaderUrlList()
       {
          List<URL> result = new ArrayList<URL>();
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          while( cl != null ) {
             if( cl instanceof URLClassLoader ) {
                URL[] urls = ((URLClassLoader) cl).getURLs();
                result.addAll( Arrays.asList( urls ) );
             }
             cl = cl.getParent();
          }
          return result;
       }
    
       private void visitFile( String rootPath, String fileOrDir )
       {
          boolean isRpDir = rootPath != null && (new File( rootPath )).isDirectory();
          File pf = new File( fileOrDir );
          if( pf.isDirectory() ) {
             File[] ff = pf.listFiles();
             if( ff != null ) {
                for( File f : ff ) {
                   if( !isRpDir || !f.isDirectory() ) {
                      visitFile( rootPath, f.toString() );
                   }
                }
             }
          } else if( fileOrDir.toLowerCase().endsWith( ".jar" ) || fileOrDir.toLowerCase().endsWith( ".zip" ) ) {
             if( !fileOrDir.endsWith( "jre/lib/rt.jar" ) ) {
                if( servletContext != null && fileOrDir.startsWith( RESOURCE_LIB_PATH ) ) {
                   try( InputStream fin = servletContext.getResourceAsStream( fileOrDir ) ) {
                      visitJar( rootPath, fin );
                   } catch( IOException e ) { /* ok */ }
                } else {
                   try( InputStream fin = new FileInputStream( fileOrDir ) ) {
                      visitJar( rootPath, fin );
                   } catch( IOException e ) { /* ok */ }
                }
             }
          } else {
             verarbeiteEinzelneDatei( rootPath, fileOrDir );
          }
       }
    
       private void visitJar( String rootPath, InputStream fin ) throws IOException
       {
          try( JarInputStream jin = new JarInputStream( fin ) ) {
             JarEntry jarEntry;
             while( (jarEntry = jin.getNextJarEntry()) != null ) {
                if( !jarEntry.isDirectory() && isDateiRelevant( jarEntry.getName() ) ) {
                   verarbeiteEinzelneDatei( rootPath, jarEntry.getName() );
                }
             }
          }
       }
    
       private void verarbeiteEinzelneDatei( String rootPath, String datei )
       {
          Set<String> jarsToThisClass = classesToJars.get( datei );
          if( jarsToThisClass == null ) {
             jarsToThisClass = new HashSet<String>();
          }
          jarsToThisClass.add( extractLibName( rootPath ) );
          classesToJars.put( datei, jarsToThisClass );
       }
    
       protected static boolean isDateiRelevant( String name )
       {
          String nam = name.toLowerCase( Locale.GERMAN );
          return !EXCLD_SET.contains( nam ) && !nam.startsWith( "about_files" ) &&
                 !nam.startsWith( "icons/" ) && !nam.startsWith( "meta-inf" ) && !nam.endsWith( "/" );
       }
    
       private List<String[]> collectMultipleClasses( Set<String> libs )
       {
          Map<String,Integer> libToMulClsCnt = new HashMap<String,Integer>();
          for( Map.Entry<String,Set<String>> me : classesToJars.entrySet() ) {
             if( me.getValue().size() <= 1 ) { continue; }
             for( String lb : me.getValue() ) {
                if( libToMulClsCnt.containsKey( lb ) ) {
                   libToMulClsCnt.put( lb, Integer.valueOf( libToMulClsCnt.get( lb ).intValue() + 1 ) );
                } else {
                   libToMulClsCnt.put( lb, Integer.valueOf( 1 ) );
                }
             }
          }
          List<String[]> result = new ArrayList<String[]>();
          for( String lb : libs ) {
             Integer c = libToMulClsCnt.get( extractLibName( lb ) );
             if( c == null ) {
                result.add( new String[] { lb, "", "" } );
             } else {
                int anz = anzahlKlassen( lb );
                result.add( new String[] { lb, c.toString(), ( anz > 0 ) ? (" (von " + anz + ")") : "" } );
             }
          }
          return result;
       }
    
       private static String extractLibName( String path ) {
          if( path.toLowerCase().endsWith( ".jar" ) || path.toLowerCase().endsWith( ".zip" ) ) {
             int i = path.lastIndexOf( '/' );
             if( i >= 0 ) { return path.substring( i ); }
          }
          return path;
       }
    
       public int anzahlKlassen( String lib )
       {
          int result = 0;
          if( servletContext != null && lib.startsWith( RESOURCE_LIB_PATH ) ) {
             try( InputStream fin = servletContext.getResourceAsStream( lib ) ) {
                result += anzahlKlassen( fin );
             } catch( IOException e ) { /* ok */ }
          } else {
             try( InputStream fin = new FileInputStream( lib ) ) {
                result += anzahlKlassen( fin );
             } catch( IOException e ) { /* ok */ }
          }
          return result;
       }
    
       private int anzahlKlassen( InputStream fin )
       {
          int result = 0;
          try( JarInputStream jin = new JarInputStream( fin ) ) {
             JarEntry jarEntry;
             while( (jarEntry = jin.getNextJarEntry()) != null ) {
                if( !jarEntry.isDirectory() && isDateiRelevant( jarEntry.getName() ) ) {
                   result++;
                }
             }
          } catch( IOException e ) { /* ok */ }
          return result;
       }
    
       public static String concatWithComma( String sPrev, String prefixForS2, Object s2 )
       {
          if( s2 == null || s2.toString().trim().length() == 0 ) { return sPrev; }
          return (( sPrev == null || sPrev.trim().length() == 0 ) ? "" : (sPrev + ", ")) + prefixForS2 + s2;
       }
    
       /** Falls JsfInspectImplementationsVersions-JSF-Tool:
           In inspectImplementationsVersions.xhtml wird hierueber die Lib fuer multipleClasses.xhtml ausgewaehlt */
       public void setAusgewaehlteLib( String ausgewaehlteLib )
       {
          this.ausgewaehlteLib = ausgewaehlteLib;
       }
    
       /** Interface um eine Abhaengigkeit zu javax.servlet.ServletContext zu vermeiden */
       interface ServletContextStellvertreter
       {
          InputStream getResourceAsStream( String resourcePath );
          Set<String> getResourcePaths( String resourceLibPath );
       }
    }
    
  12. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfInspectImplementationsVersions

    tree /F

    Sie erhalten:

    [\MeinWorkspace\JsfInspectImplementationsVersions]
     |- [src]
     |   '- [main]
     |       |- [java]
     |       |   '- [de]
     |       |       '- [meinefirma]
     |       |           '- [meinprojekt]
     |       |               '- [backingbeans]
     |       |                   |- InspectImplementationsVersionsBean.java
     |       |                   '- InspectLibsAndClasses.java
     |       |- [jetty]
     |       |   |- override-mojarra-web.xml
     |       |   '- override-myfaces-web.xml
     |       |- [resources]
     |       |   '- [META-INF]
     |       |       '- persistence.xml
     |       '- [webapp]
     |           |- [WEB-INF]
     |           |   '- web.xml
     |           |- inspectImplementationsVersions.xhtml
     |           |- multipleClasses.xhtml
     |           '- stylesTbl.css
     '- pom.xml
    
  13. Führen Sie im Kommandozeilenfenster aus:

    cd \MeinWorkspace\JsfInspectImplementationsVersions

    mvn jetty:run

    start http://localhost:8080/JsfInspectImplementationsVersions/inspectImplementationsVersions.xhtml

  14. Falls Sie folgende Exception erhalten:

    com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Ungültiges Byte 1 von 1-Byte-UTF-8-Sequenz.

    Dann ist wahrscheinlich die Datei multipleClasses.xhtml nicht korrekt im UTF-8-Character-Encoding gespeichert, und die Button-Beschriftung zurück enthält ein ungültiges Zeichen. Speichern Sie die Datei neu und achten Sie darauf, dass korrekt in UTF-8 gespeichert wird.

  15. Die angezeigte Webseite beginnt mit der Anzeige von Implementierungen und Versionen:

    JsfInspectImplementationsVersions-Webseite

    Weiter unten finden Sie eine Auflistung der Libs im Classpath, und falls es darin doppelte Klassen gibt, zusätzlich rechts einen Button, dessen Beschriftung die Anzahl der doppelten Klassen anzeigt:

    JsfInspectImplementationsVersions-Webseite

    Wenn Sie einen dieser Buttons anklicken, erhalten Sie eine Liste dieser doppelten Klassen, und in welchen Libs es die jeweilige Klasse gibt:

    JsfInspectImplementationsVersions-Webseite
  16. Bitte beachten Sie, dass bei der hier verwendeten speziellen Ausführung direkt aus dem Sourcecode-Verzeichnis viele Libs doppelt angezeigt werden, einmal aus dem Maven-Repository, und einmal aus dem /WEB-INF/lib-Verzeichnis. Dies passiert nicht, wenn Sie die WAR-Datei korrekt in einem Java EE Application Server deployen.

  17. Falls Sie die WAR-Datei in bestimmte WebLogic-Versionen deployen, und falls Sie beim Aufruf der zweiten Seite eine Exception erhalten ähnlich zu:

    javax.faces.application.ViewExpiredException: viewId:/... - Ansicht ... konnte nicht wiederhergestellt werden.

    javax.faces.application.ViewExpiredException: viewId:/... - View ... could not be restored.

    Dann versuchen Sie, ob folgende Ergänzung in der src\main\webapp\WEB-INF\web.xml hilft:

      <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
      </context-param>
    



Weitere Themen: andere TechDocs
© 2014 Torsten Horn, Aachen