JavaServer Faces (JSF) ist ein komponentenbasiertes 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 Webframeworks, wie beispielsweise:
Die folgenden Erläuterungen stellen nur kurze Erinnerungsstützen und keine exakten Definitionen dar.
Servlet:
Servlets sind spezielle Java-Klassen, welche insbesondere für Webanwendungen verwendet werden. Sie werden häufig mit "@WebServlet" annotiert, implementieren meistens "HttpServlet" und überschreiben beispielsweise die Methoden "doGet()" und "doPost()" für die HTTP-Kommunikation.
Servlets gibt es seit 1997. Sie sind die Basistechnologie für Java-Webanwendungen sowie JSP und JSF.
Siehe JSR 340: Java Servlet 3.1, Oracle: Java Servlet und Wikipedia: Servlet.
JSP:
"JavaServer Pages": Statt wie bei Servlets HTML-Code in einer Java-Klasse zu generieren, werden bei JSP normale HTML-Seiten um JSP-Scriptlets erweitert, welche Java-Code enthalten (z.B. "<%= meineBean.meineMethode() %>").
JSP kann als Vorgänger von JSF gesehen werden.
Siehe JSR 245: JavaServer Pages 2.1, Oracle: JavaServer Pages und Wikipedia: JavaServer Pages.
JSF:
"JavaServer Faces": Web Application Framework, typischerweise im Wesentlichen basierend auf XHTML-Dokumenten ("Facelets"), Managed Beans und dem FacesServlet. Weitere Bestandteile sind: APIs, Tag Libraries, Expression Language, Scopes, Events, Validation, Navigation etc.
JSF entstand 2004. Seit 2009 gibt es JSF in der Version 2.0 (als Teil von Java EE 6) und seit 2013 in der Version 2.2 (als Teil von Java EE 7).
Siehe JSR 344: JavaServer Faces 2.2, Oracle: JavaServer Faces und Wikipedia: JavaServer Faces.
MVC:
"Model View Controller" (= "Modell, Präsentation und Steuerung"): Allgemeines Architekturmuster zur Trennung von Datenhaltung, interaktiver GUI-Präsentation und Verwaltung der Views und Models.
Die Umsetzung und Zuordnung erfolgt nicht immer einheitlich und stringent. Bei JSF gilt in der Regel: Das Model wird durch POJOs, Java Beans oder Managed Beans realisiert, die View durch Facelets-XHTML-Dokumente und als Controller fungiert das FacesServlet ("Front-Controller-Pattern").
Siehe Model View Controller.
JSF Runtime Environment (Laufzeitumgebung):
JSF benötigt als Laufzeitumgebung einen Servlet Container, also einen Java-Webserver oder Java EE Application Server, der Java-Servlets ausführen kann. Für JSF 2.2 muss mindestens Servlet 3.0 unterstützt werden.
Bekannte Servlet Container bzw. Java EE Application Server sind beispielsweise: Jetty, Tomcat, GlassFish, WildFly, JBoss, Oracle WebLogic, IBM WebSphere und Apache Geronimo.
Genauer: "JSF Request Processing Lifecycle". Requests durchlaufen normalerweise sechs Phasen:
Siehe Tutorial: The Lifecycle of a JavaServer Faces Application, Wikipedia: JSF-Lebenszyklus und Chetty: JSF Lifecycle.
In bestimmten Situationen soll der Request Lifecycle nicht komplett durchlaufen werden. Beispielsweise soll ein Cancel-Button auch bei invaliden Eingaben funktionieren, ohne dass der JSF-Validator dies blockiert. Lifecycle-Verkürzungen können auf verschiedene Arten erreicht werden, z.B. im Facelets-XHTML mit dem immediate="true"-Attribut oder mit dem <f:ajax>-Tag, oder im Java-Code (z.B. in einem Listener) per FacesContext.getCurrentInstance().renderResponse(), wie die später folgenden Beispiele zeigen.
JSF RI:
"JSF Reference Implementation", zurzeit Mojarra JavaServer Faces. Alternativ kann auch Apache MyFaces verwendet werden.
Innerhalb der JSF-Anwendung können Sie folgendermaßen die Implementierung und Version abfragen:
FacesContext.class.getPackage().getImplementationTitle() + " " + FacesContext.class.getPackage().getImplementationVersion() + ", " + FacesContext.class.getPackage().getImplementationVendor();
JSF Renderer:
Der JSF Renderer wandelt in das gewünschte Ausgabeformat um, also normalerweise in eine für übliche Webbrowser verständliche HTML-Seite.
JSF API:
Siehe JavaServer Faces API und Java EE 7 APIs.
Facelets stellen seit JSF 2.0 die default "View Declaration Language" ("VDL") dar. Facelets sind XHTML-Dokumente, welche Facelet Tags enthalten, und meistens auch Ausdrücke in der "Expression Language (EL)" beinhalten.
Siehe Tutorial: Introduction to Facelets und Wikipedia: Facelets.
Facelet Tags und Tag Libraries:
Über Tags werden HTML-Komponenten und Funktionen in die Facelets-XHTML-Seite eingefügt.
Die wichtigsten Tag Libraries sind:
API-Doku: | Tutorial: | ||
f: | http://xmlns.jcp.org/jsf/core | Using Core Tags | |
h: | http://xmlns.jcp.org/jsf/html | Using HTML Tag Library Tags | |
ui: | http://xmlns.jcp.org/jsf/facelets | Using Facelets Templating Tags | |
cc: | http://xmlns.jcp.org/jsf/composite | Composite Components | |
jsf: | http://xmlns.jcp.org/jsf | Using Pass-Through Elements (HTML5) | |
p: | http://xmlns.jcp.org/jsf/passthrough | Using Pass-Through Attributes (HTML5) (Achtung: "p:" wird häufig auch für PrimeFaces verwendet) |
Anwendungsbeispiel:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"> ... <h:form> <h:outputText value="Welcome, #{loggedInUser.name}" disabled="#{empty loggedInUser}" /> <h:inputText value="#{meineBackingBean.property}" /> <h:commandButton value="OK" action="#{meineBackingBean.doSomething}" /> </h:form> ...
Siehe Tag Library Documentation.
HTML Basic RenderKit:
HTML-Basiselemente, siehe HTML_BASIC RenderKit.
Facelets Templating und Composition:
Ein Facelets Template ist eine Facelets-XHTML-Seite mit Platzhaltern, welche von verschiedenen Template Clients mit Werten und Inhalten vervollständigt werden können. Seiten können so schablonenartig zusammengesetzt werden, um ein einheitliches Erscheinungsbild der Anwendung über alle Seiten zu erreichen. Facelets Templates können mehrstufig geschachtelt werden.
Das Facelets Template kann verschiedene Platzhalter enthalten:
Die Template Clients verwenden das Template und müssen die Platzhalter füllen:
Siehe API: Facelets Templating, Tutorial: Using Facelets Templates, Wikipedia: Facelets Templating und Programmierbeispiel: Templating und Composition.
Composite Components (Kompositkomponenten):
Composite Components sind XHTML-Beschreibungen eigener Komponenten. Dabei können Komponenten zu neuen Komponenten zusammengesetzt werden. Darüberhinaus können auch per @FacesComponent annotierte Java-Klassen per <cc:interface componentType> eingebunden werden.
Composite Components bestehen aus einem <cc:interface>-Teil für Übergabeattribute und einem <cc:implementation>-Teil für die eigentliche Implementierung.
Siehe API: Composite Components, Tutorial: Composite Components, Wikipedia: Facelets Composite components und mehrere Programmierbeispiele: Composite Component ....
Component Libraries (Komponentenbibliotheken):
Da der JSF-Standard nur relativ wenige GUI-Komponenten enthält, werden meistens zusätzliche Fremdbibliotheken hinzugenommen, beispielsweise: PrimeFaces, RichFaces, ICEfaces, Tobago, Tomahawk, Trinidad.
"Expression Language" bzw. "Unified Expression Language": Über die EL erfolgt die Verbindung zwischen Model und View. EL definiert die Syntax, um innerhalb der Facelets-XHTML-Dateien
arithmetische Berechnungen auszuführen, z.B. "#{1+2}",
logische Bedingungen auszuwerten, z.B. "#{empty loggedInUser}" oder style="#{grid.displayed ? 'display:inline;' : 'display:none;'}",
um Parameter zu lesen und zu schreiben ("Value Expression" / "Wertebindung"), z.B. "#{meineBackingBean.meinAttribut}" oder "#{meineBackingBean.meinModel.meinAttribut}" oder "#{meineBackingBean.meinModel.meineMap['index']}",
und um Methoden auszuführen ("Method Expression" / "Methodenbindung"), z.B. "#{meineBackingBean.meineMethode}" oder "#{meineBackingBean.meineMethode(andereBean.getProp)}" oder "#{meineBackingBean.meineListe.size()}" oder "#{meineBackingBean.meinText.replaceAll(':','_')}".
Siehe Expression Language.
"Asynchronous JavaScript and XML". Informationen zu Ajax finden Sie unter API: Tag ajax, Tutorial: Using Ajax with JavaServer Faces Technology und Wikipedia: Ajax.
Ajax wird innerhalb einer <h:form> per <f:ajax> in das Facelets-XHTML eingebunden und ermöglicht:
Der bei execute="..." und render="..." übergebene Wert kann eine einzelne id einer GUI-Komponente sein, oder es können per Leerzeichen getrennt mehrere id-Werte sein ("id1 :id2:id3"). Die Ids können relativ ("meine-id"), absolut (":meine-id") oder geschachtelt (":id1:id2:id3") sein. Außerdem sind die speziellen Werte "@none" (keine Komponente), "@all" (alle Komponenten), "@form" (alle Komponenten der <h:form>) und "@this" (auslösende Komponente) möglich.
Weiter unten finden Sie ein Programmierbeispiel zu Ajax.
Java Bean:
Java Beans (im engeren Sinne) sind Java-Klassen, die einen öffentlichen Standardkonstruktor (ohne Parameter) besitzen und deren Attribute (Properties) über Getter- und Setter-Methoden gelesen und geschrieben werden.
Managed Beans sind Java Beans, deren Erzeugung, Lifecycle und Scope von einem Container gesteuert wird. Außer bei JSF gibt es Managed Beans beispielsweise per CDI oder bei EJB. Bei JSF werden die Managed Beans auch Backing Beans genannt und verknüpfen die Daten und die Geschäftslogik mit der JSF-View.
Bitte "Managed Bean" nicht mit "JMX Management Bean" verwechseln.
Achtung: Es gibt verschiedene Möglichkeiten zur Annotation als Managed Bean:
Normalerweise sollte die CDI-Annotation @javax.inject.Named bevorzugt verwendet werden. Siehe hierzu auch: Managed Beans and EL access und You should not have any other @ManagedBean beside @Named.
In der Managed Bean wird auch der Scope der Bean definiert.
"Gültigkeitsbereich" oder "Lebensdauer", siehe Using Managed Bean Scopes und Built-in scopes.
Achtung: Die Scope-Definitionen gibt es doppelt, leider teilweise mit demselben Namen, so dass leicht ungewollt die falsche Scope-Annotation verwendet wird.
JSF-Scope-Definitionen im javax.faces.bean-Package:
CDI-Scope-Definitionen, die meisten im javax.enterprise.context-Package:
Navigation:
Der Seitenwechsel, also der Wechsel zu einer anderen Webseite bzw. zu einer anderen Facelets-XHTML-Datei, kann auf verschiedene Arten ausgelöst werden. Einige der wichtigsten sind:
In der faces-config.xml-Datei über <navigation-rule>...
In der Facelets-XHTML-Datei, z.B. im action-Attribut, z.B. action="neueSeite.xhtml".
Im Java-Code über den Returnwert der Action-Methoden, z.B. return "neueSeite.xhtml".
Auch in anderen Methoden, z.B. in Listener-Methoden, ist Navigation möglich, z.B. so:
FacesContext facesContext = FacesContext.getCurrentInstance(); NavigationHandler navigationHandler = facesContext.getApplication().getNavigationHandler(); navigationHandler.handleNavigation( facesContext, null, "neueSeite.xhtml" );
Statt der konkreten Angabe des Dateinamens der nächsten Seite als Navigationsziel können auch logische "View Identifier" definiert und verwendet werden.
Siehe Navigation Model.
Bean Validation (JSR 303):
Zur Validierung gibt es verschiedene Konzepte. Nach Möglichkeit sollte nur noch die universelle "Bean Validation" (JSR 303) verwendet werden. Sie funktioniert nicht nur in JSF, sondern auch in JPA etc. und ist besonders einfach anwendbar, beispielsweise indem die Attribute in der Entity-Datei mit @NotNull, @Size, @Min, @Max, @Pattern etc. annotiert werden. Nur so ist sichergestellt, dass sowohl in JPA als auch in JSF dieselben Constraints gelten. Bean-Validator-Meldungen werden automatisch in FacesMessages konvertiert.
Message-Bundle, Resource-Bundle:
Um Internationalisierung und Lokalisierung zu erleichtern, gibt es in Java und JSF das Bundle-Konzept. Ein Bundle stellt eine Menge von Property-Dateien mit gleichem Basisnamen dar. Die Property-Dateien eines Bundles enthalten im Dateinamen hinter dem Basisnamen und vor der Dateiendung Kürzel für die Sprache und optional für das Land, beispielsweise: MeinBundle_de.properties, MeinBundle_de_DE.properties etc. Die Texte können Platzhalter {0}, {1} etc. enthalten.
Wichtig ist folgende Unterscheidung:
Resource Bundle: Enthalten beliebige in der Anwendung verwendete Texte, z.B. Überschriften, Labeltexte, Tooltips, Pfade zu internationalisierten Grafikdateien, eigene Fehlermeldungen etc. Pro Anwendung kann es mehrere Resource Bundles geben.
Verwendung eines Resource Bundles im Java-Code:
FacesContext facesContext = FacesContext.getCurrentInstance(); ResourceBundle bundle = facesContext.getApplication().getResourceBundle( facesContext, "meinbundle" ); String s = bundle.getString( "meinTextKey" );
Verwendung eines Resource Bundles per EL in der Facelets-XHTML-Datei:
<h:outputText value="#{meinbundle.meinTextKey}" /> <h:outputFormat value="#{meinbundle.meinKeyZumTextMitPlatzhaltern}"> <f:param value="#{meineBackingBean.meinTextFuerPlatzhalter0}"/> <f:param value="#{meineBackingBean.meinTextFuerPlatzhalter1}"/> </h:outputFormat>
Die Konfiguration zu Bundles erfolgt in der faces-config.xml:
... <application> <message-bundle>[package1.package2.]MeineCustomMessages</message-bundle> <resource-bundle> <base-name>resources</base-name> <var>meinbundle</var> </resource-bundle> <locale-config> <default-locale>de_DE</default-locale> <supported-locale>de</supported-locale> <supported-locale>en</supported-locale> </locale-config> </application> ...
Gruppierung, Grid und Tabelle:
Verwechseln Sie nicht folgende HTML-Tags:
HttpServletRequest und HTTP-Header-Informationen:
Folgendermaßen erreichen Sie die HttpServletRequest-Daten, die HTTP-Header-Informationen und beispielsweise den Namen der Referrer-Webseite, von der der Request kommt:
HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); String referer = req.getHeader( "Referer" );
Achtung: "Referrer" wird eigentlich mit zwei 'r' geschrieben, aber im HTTP-Header nur mit einem 'r'.
"Post/Redirect/Get-Pattern" mit "faces-redirect=true":
Folgendermaßen erreichen Sie, dass nicht die letzte, sondern die aktuelle Seite in der Webbrowser-Adresszeile eingetragen wird. Allerdings gehen dabei die Request-Parameter verloren, da so ein neuer Request entsteht.
Im Facelets-XHTML:
<h:form> <h:commandButton value="Nächste Seite" action="nextPage.xhtml?faces-redirect=true" /> </h:form>
In der Backing-Bean-Action-Methode:
public String action() { ... return "nextPage.xhtml" + "?faces-redirect=true"; }
Falls View-Parameter ("f:viewParam") übertragen werden sollen, wird "includeViewParams" auf true gesetzt oder "includeViewParams=true" angehängt:
... <f:metadata> <f:viewParam name="param1" value="#{bean.prop1}"/> <f:viewParam name="param2" value="#{bean.prop2}"/> </f:metadata> <h:head>...</h:head> <h:body> ... <h:link value=Nächste Seite" outcome="nextPage.xhtml?faces-redirect=true" includeViewParams="true" />
return "nextPage.xhtml" + "?faces-redirect=true&includeViewParams=true";
WEB-INF/web.xml:
"Web Deployment Descriptor File", Deployment Descriptor der Java-EE-Webanwendung.
Beispielsweise für Servlet 3.1 (neuer Namespace):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> ... </web-app>
Beispielsweise für Servlet 3.0 (alter Namespace):
<?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>
WEB-INF/faces-config.xml:
"Application Configuration Resource File", JSF-Konfigurationsdatei.
Beispielsweise für JSF 2.2 (neuer Namespace):
<?xml version="1.0"?> <faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2"> </faces-config>
Beispielsweise für JSF 2.1 (alter Namespace):
<?xml version="1.0"?> <faces-config 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-facesconfig_2_1.xsd" version="2.1"> </faces-config>
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.
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.
Voraussetzung ist ein installiertes JDK mit mindestens der Version 7 und Maven 3.x.
Führen Sie im Kommandozeilenfenster aus:
md \MeinWorkspace\JsfHelloWorld
cd \MeinWorkspace\JsfHelloWorld
md src\main\jetty
md src\main\webapp\WEB-INF
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> <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties> <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.7.0</version> <configuration> <source>1.8</source> <target>1.8</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>
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>
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.
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.
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
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:
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.)
Dieses Programmierbeispiel zeigt zwei Varianten, wie GUI-Elemente ein- und ausgeblendet werden können:
Ohne Ajax:
Mit valueChangeListener und onclick="this.form.submit()" immediate="true"
Mit Ajax:
Mit valueChangeListener und <f:ajax render="..." />
Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:
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).
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
Ersetzen Sie im JsfAusblendenOhneUndMitAjax-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfAusblendenOhneUndMitAjax".
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.
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ä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.
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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfAusblendenOhneUndMitAjax
mvn jetty:run
start http://localhost:8080/JsfAusblendenOhneUndMitAjax/ausblenden.xhtml
Im Webbrowser erscheint die Ausgabe:
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.
Dieses Programmierbeispiel zeigt zweierlei:
Es zeigt zwei Auswahllisten, wobei die Auswahl in der ersten Auswahlliste per Ajax steuert, welche Optionen in der zweiten Auswahlliste auswählbar sind.
Es zeigt eine Warnmeldung, falls Eingabefelder verändert wurden, und versucht wird, die Webseite zu verlassen, obwohl die Änderung noch nicht durch "Speichern" bestätigt oder durch "Abbrechen" verworfen wurde.
Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:
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).
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
Ersetzen Sie im JsfAuswahllisteMitAjax-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfAuswahllisteMitAjax".
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<>(); 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; } } }
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.
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
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:
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.
Ein reines JavaScript-Beispiel zu window.onbeforeunload finden Sie unter: JavaScript-Beispiel: Seitenwechsel-Warnung.
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!
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).
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
Ersetzen Sie im JsfTabelleMitSelektionsFehler-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfTabelleMitSelektionsFehler".
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()); } }
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<>(); 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 ); } } }
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." ); } else if( !meineDatenListe.remove( einzelnesElement ) ) { addFacesMessage( FacesMessage.SEVERITY_ERROR, "Fehler: Datenelement existiert nicht mehr." ); } else { 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; } }
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; }
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ö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.
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
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:
Löschen Sie einzelne Elemente. Es werden die korrekten Elemente gelöscht und die veränderte Liste wird korrekt persistiert.
Folgendermaßen passiert das Unglück:
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.
Ö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.
Löschen Sie im ersten Webbrowser-Fenster die oberste Zeile der angezeigten Tabelle, also das "Apfelsinen"-Element.
Wechseln Sie in das andere Webbrowser-Fenster und löschen Sie dort das zweite Element, also das "Birnen"-Element.
Als Ergebnis erhalten Sie:
Das "Birnen"-Element wurde nicht gelöscht.
Aber das "Tomaten"-Element wurde ungewollt gelöscht.
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>
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.
Es gibt im Wesentlichen zwei Korrekturmöglichkeiten:
Die über #{meineBackingBean.meineDatenListe} gelesene Liste der Datenelemente wird nicht bei jedem Zugriff erneut aus dem Datenspeicher (Datei oder Datenbank) ausgelesen, sondern nur einmal gelesen und dann gecacht (z.B. per @ViewScoped), so dass beim Löschen einzelner Elemente noch auf dieselben Elemente an derselben Position in der Liste zugegriffen werden kann. Dies wird im folgenden Programmierbeispiel demonstriert.
Alternativ kann beim Erzeugen der HTML-Seite zusätzlich der Primary Key (z.B. die id) der Datenelemente in der HTML-Seite in jeder Zeile hinzugefügt werden (z.B. als Hidden Field) und dann beim Löschen aus der HTML-Seite ausgelesen und statt des Datenelements an die Backing Bean gesendet werden. In der Backing Bean muss dann anhand des Primary Keys das zu löschende Datenelement gesucht werden.
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).
Voraussetzung ist das vorherige Programmierbeispiel Fehler beim Selektieren in einer JSF-Tabelle.
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace
xcopy JsfTabelleMitSelektionsFehler JsfTabelleMitKorrekterSelektion\ /S
cd JsfTabelleMitKorrekterSelektion
Ersetzen Sie im JsfTabelleMitKorrekterSelektion-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitSelektionsFehler" durch "JsfTabelleMitKorrekterSelektion".
Ä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 {
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".
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
Versuchen Sie den oben beschriebenen Fehler zu reproduzieren. Der Fehler tritt nicht mehr auf.
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.
Dieses Programmierbeispiel zeigt:
Wie beim letzten Beispiel erfolgt die Darstellung einer beliebig langen Liste von Datenelementen mit mehreren Attributen in einer JSF-Tabelle.
Anders als beim letzten Beispiel können diesmal mehrere Elemente gleichzeitig selektiert und mit einem Klick gelöscht werden.
Zusätzlich können neue Datenelemente erstellt und hinzugefügt werden
und vorhandene Datenelemente editiert und geändert werden.
Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:
Voraussetzung ist eins der beiden letzten Programmierbeispiele, beispielsweise JSF-Tabelle mit korrekter Selektion.
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace
xcopy JsfTabelleMitKorrekterSelektion JsfTabelleMitNeuUndEdit\ /S
cd JsfTabelleMitNeuUndEdit
Ersetzen Sie im JsfTabelleMitNeuUndEdit-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitKorrekterSelektion" durch "JsfTabelleMitNeuUndEdit".
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<>(); 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() && !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.
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 Änderungsmö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 Änderungsmö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 ändern" action="#{meineBackingBean.einzelnesElementEditieren}"> <f:setPropertyActionListener target="#{meineBackingBean.einzelnesElement}" value="#{loopElement}" /> </h:commandButton> </h:column> </h:dataTable> <h:outputText value="<br/>" escape="false" /> <h:commandButton value="Markierte Elemente löschen" action="#{meineBackingBean.markierteElementeLoeschen}" /> <h:outputText value=" " /> <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.
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.
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
Im Webbrowser erscheint:
Dieses Programmierbeispiel demonstriert:
Sortierung:
Durch Klick auf Tabellenspaltentitel können die Zeilen in einer Tabelle sortiert werden.
Paginierung (Pagination):
Die Elemente einer (langen) Liste werden nicht alle auf einmal angezeigt, sondern seitenweise. Dabei kann per Klick an den Anfang, auf die vorherige Seite, auf die nächste Seite, auf die letzte Seite oder auf eine bestimmte Seitenzahl gesprungen werden.
Exportfunktion:
Wahlweise der angezeigte Datenausschnitt oder die gesamte Datenliste kann exportiert werden, als Excel-, PDF-, CSV- oder XML-Datei.
PrimeFaces:
Die Verwendung der Fremdbibliothek PrimeFaces (Component Library / Komponentenbibliothek).
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:
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.
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace
xcopy JsfTabelleMitKorrekterSelektion JsfTabellePagSortExportPrimefaces\ /S
cd JsfTabellePagSortExportPrimefaces
md src\main\webapp\img
tree /F
Ersetzen Sie im neuen JsfTabellePagSortExportPrimefaces-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitKorrekterSelektion" durch "JsfTabellePagSortExportPrimefaces".
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>
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:
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.
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; }
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<>(); 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; }
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; } }
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>
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
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:
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.
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.
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.
Dieses Programmierbeispiel demonstriert:
Sortierung:
Durch Klick auf Tabellenspaltentitel können die Zeilen in einer Tabelle sortiert werden.
Paginierung (Pagination):
Die Elemente einer (langen) Liste werden nicht alle auf einmal angezeigt, sondern seitenweise. Dabei kann per Klick an den Anfang, auf die vorherige Seite, auf die nächste Seite, auf die letzte Seite oder auf eine bestimmte Seitenzahl gesprungen werden.
Tomahawk:
Die Verwendung der Fremdbibliothek Apache MyFaces Tomahawk (Component Library / Komponentenbibliothek). Doku zu Tomahawk siehe: Apache Tomahawk Tag Doc und developersbook Tomahawk Tag Doc.
MyFaces:
Die Verwendung von Apache MyFaces als JavaServer-Faces-Implementation (statt Mojarra JavaServer Faces).
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:
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.
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
Ersetzen Sie im JsfTabellePagSortTmhwk-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabelleMitKorrekterSelektion" durch "JsfTabellePagSortTmhwk".
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>
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>
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.
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:
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; }
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<>(); 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; }
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; } }
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.
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
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:
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)}").
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
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:
Nur den benötigten Teil der Datenliste auslesen:
Nur die Zeilen der Datenliste, welche für die gerade darzustellende Tabellenseite benötigt werden, werden aus dem Datenhaltungssystem (z.B. Datenbank) gelesen, weil die gesamte Liste nicht in den Arbeitsspeicher passen würde. Dies kann beispielsweise folgendermaßen in JPA erfolgen:
query.setFirstResult( (pageNumber - 1) * pageSize );
query.setMaxResults( pageSize );
Bzw. so in SQL:
SELECT * FROM ( SELECT a.*, ROWNUM rnum FROM (SELECT ... FROM ... WHERE ... ORDER BY ... ) a WHERE ROWNUM < :N2 ) WHERE rnum >= :N1;
Sortierung im Datenhaltungssystem:
Bei der Sortierung durch Klick auf die Tabellenspaltentitel soll nicht nur die gerade gezeigte Ansicht sortiert werden, sondern es muss der passende Ausschnitt aus der sortierten Gesamtmenge angezeigt werden. Angenommen es wird die erste Seite dargestellt, dann müssen bei aufsteigender Sortierung wirklich die ersten und bei absteigender Sortierung wirklich die letzten Elemente der Gesamtliste erscheinen. Da nur ein Teil der Daten ausgelesen wird, muss deshalb die Sortierung im Datenhaltungssystem erfolgen (bei Datenbanken durch "order by ... asc / desc").
Möglichst generisch:
Die im Folgenden gezeigte Lösung kapselt viel von der benötigten Funktionalität in zwei generischen Klassen:
PagedDataList.java simuliert für die JSF-Tabelle eine sehr große Liste, obwohl sie in Wirklichkeit nur eine sehr kurze Liste der Elemente für genau eine Tabellenseite enthält.
PagedDataReader.java ist ein Interface, welches z.B. von einer DAO-Klasse implementiert werden muss, um im Datenhaltungssystem die Sortierung für die gewünschte Spalte in der gewünschten Sortierrichtung durchzuführen, und um die benötigte Teilmenge der Datenelemente zu liefern.
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:
Voraussetzung ist das vorherige JSF-Tabellen-Programmierbeispiele JSF-Tabelle mit Paginierung und Sortierung mit Tomahawk.
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
Ersetzen Sie im JsfTabellePagSortTmhwkLargeDB-Projektverzeichnis in der pom.xml an zwei Stellen "JsfTabellePagSortTmhwk" durch "JsfTabellePagSortTmhwkLargeDB".
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>
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.
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="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" /> <property name="javax.persistence.jdbc.url" value="jdbc:derby:./target/Derby-DB/mydb;create=true" /> <property name="javax.persistence.jdbc.user" value="" /> <property name="javax.persistence.jdbc.password" 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.
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(); }
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" ); } }
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; }
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.isEmpty() ) { 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(); } } }
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<>( 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 ); } }
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>
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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfTabellePagSortTmhwkLargeDB
mvn jetty:run
start http://localhost:8080/JsfTabellePagSortTmhwkLargeDB/tabelle.xhtml
Im Webbrowser erscheint die Ausgabe:
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.
Dieses Programmierbeispiel demonstriert das Facelets Templating. Allgemeine Informationen hierzu finden Sie unter Facelets Templating und Composition.
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).
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
Ersetzen Sie im JsfTemplating-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfTemplating".
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>
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>
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.
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
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:
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).
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).
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
Ersetzen Sie im JsfCompositeComponent-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfCompositeComponent".
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" /> <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.
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}"
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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfCompositeComponent
mvn jetty:run
start http://localhost:8080/JsfCompositeComponent/meineCcSeite1.xhtml
Sie erhalten:
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.
Voraussetzung ist das vorherige Programmierbeispiel Composite Component minimal.
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>
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" /> <h:inputText value="Abc" id="input" /> </f:facet> <f:facet name="meinFacet2"> <h:outputLabel value="xyz" for="input" /> <h:inputText value="Xyz" id="input" /> </f:facet> </mycc:meineCompositeComponent2> </h:body> </html>
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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfCompositeComponent
mvn jetty:run
start http://localhost:8080/JsfCompositeComponent/meineCcSeite2.xhtml
Sie erhalten:
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.
Voraussetzung ist das Programmierbeispiel Composite Component minimal.
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfCompositeComponent
md src\main\java\de\meinefirma\meinprojekt\backingbeans
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; } }
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.
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=" --> #{meineBackingBean.uhrzeit}" /> </h:form> </h:body> </html>
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
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:
Dieses Programmierbeispiel erweitert das vorherige um die Möglichkeit, in einer Composite Component Java-Code aus einer FacesComponent Backing Bean auszuführen.
Voraussetzung ist das vorherige Programmierbeispiel Composite Component mit Methodenbindung.
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
mit @FacesComponent annotiert sein,
UIComponent erweitern (oder eine davon abgeleitete Klasse wie z.B. UIInput),
das Interface NamingContainer implementieren und
die Methode getFamily() überschreiben und darin "javax.faces.NamingContainer" returnieren.
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=" --> #{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.
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>
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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfCompositeComponent
mvn jetty:run
start http://localhost:8080/JsfCompositeComponent/meineCcSeite4.xhtml
Dieses Programmierbeispiel zeigt:
Wie mit dem Confirm Dialog aus der PrimeFaces Component Library (Komponentenbibliothek) vor dem Ausführen einer Aktion eine Sicherheits-Bestätigungsabfrage eingefügt werden kann ("Sind Sie sicher und wollen Sie wirklich ...").
Wie innerhalb einer Seite und eines Formulars für verschiedene Aktionen mehrere verschiedene solcher Bestätigungsabfragen eingefügt werden können.
Sie können das Programmierbeispiel downloaden oder folgendermaßen erstellen:
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).
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
Ersetzen Sie im JsfConfirmDialogPrmfcs-Projektverzeichnis in der pom.xml an zwei Stellen "JsfHelloWorld" durch "JsfConfirmDialogPrmfcs".
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>
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.
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:
Sie verwendet die PrimeFaces Component Library (Komponentenbibliothek), siehe den Namespace xmlns:pf="http://primefaces.org/ui".
Beachten Sie unbedingt die Unterschiede zwischen den PrimeFaces-Versionen, siehe MigrationGuide 4.0 to 5.0. Beispielsweise muss statt onclick="confirmation1.show()" ab PrimeFaces 5 onclick="PF('confirmation1').show()" verwendet werden.
Die zwischengeschalteten Bestätigungsabfragen werden als PrimeFaces Confirm Dialog realisiert, siehe <pf:confirmDialog....
Damit innerhalb einer Seite und eines Formulars für die beiden Aktionen doAktion1() und doAktion2() zwei verschiedene Bestätigungsabfragen eingefügt werden können, die sich nicht gegenseitig beeinflussen dürfen, ist wichtig, dass die beiden <pf:confirmDialog...-Abschnitte verschiedene IDs über widgetVar="..." erhalten.
Innerhalb der <pf:confirmDialog...-Abschnitte gibt es einen "Ja"- und einen "Nein"-Button. Der erste dieser beiden Buttons erhält beim Öffnen des Bestätigungsdialogs den Fokus und wird beim Betätigen der Return-Taste ausgeführt.
Der erste <pf:confirmDialog...-Dialog hat als ersten Button den "Ja"-Button, der somit den Start-Fokus erhält.
Das kann gefährlich sein, weil so leicht aus Versehen eine Bestätigung der Sicherheitsabfrage erfolgen kann.
Um das zu vermeiden, kann als erster Button den "Nein"-Button gewählt werden.
Um trotzdem die unter Windows übliche Reihenfolge der "Ja"- und "Nein"-Buttons beizubehalten,
wurden sie im zweiten <pf:confirmDialog...-Dialog per style="float:right"
in vertauschter Reihenfolge nach rechts verschoben.
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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfConfirmDialogPrmfcs
mvn jetty:run
start http://localhost:8080/JsfConfirmDialogPrmfcs/confirmDialogs.xhtml
Im Webbrowser erscheint die Ausgabe:
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.
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.
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.
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.
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
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> <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties> <build> <finalName>${project.artifactId}</finalName> <defaultGoal>install</defaultGoal> <plugins> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.7.v20170914</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.7.0</version> <configuration> <source>1.8</source> <target>1.8</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>6.2</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>2.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.7.Final</version> </dependency> <!-- EclipseLink: --> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.6.5</version> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <!-- oder alternativ OpenJPA: <dependency> <groupId>org.apache.openjpa</groupId> <artifactId>openjpa</artifactId> <version>2.3.0</version> </dependency> --> <!-- Nur als Beispiel: --> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.14.1.0</version> </dependency> <!-- Nur falls vorhanden: <dependency> <groupId>com.oracle.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>12.2</version> </dependency> --> </dependencies> </project>
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>
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>
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>
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"> <!-- EclipseLink: --> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <!-- oder alternativ OpenJPA: <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> --> <properties> <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" /> <property name="javax.persistence.jdbc.url" value="jdbc:derby:./target/Derby-DB/mydb;create=true" /> <property name="javax.persistence.jdbc.user" value="" /> <property name="javax.persistence.jdbc.password" value="" /> <property name="eclipselink.ddl-generation" value="create-tables" /> <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema" /> </properties> </persistence-unit> </persistence>
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; }
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>
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>
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<>(); 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 ); addKeyValue( "JPA-EntityManager", emf.createEntityManager().getDelegate().getClass().getName(), 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 ); // Die folgenden 7 Zeilen nur, falls "CpuGcMemTx" eingebunden wird: CpuGcMemTx.INSTANCE.run(); if( CpuGcMemTx.INSTANCE.getCpuGcMem() != null ) { addKeyValue( "Cpu, Gc, Mem", CpuGcMemTx.INSTANCE.getCpuGcMem(), result ); } if( CpuGcMemTx.INSTANCE.getTx() != null ) { addKeyValue( "Transaktionen", CpuGcMemTx.INSTANCE.getTx(), result ); } return result; } public List<String[]> getSessionMapList() { List<String[]> result = new ArrayList<>(); Map<String,Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap(); Map<String,Object> sessionMapSorted = new TreeMap<>( 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<>(); 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 ); } }
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<>( 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<>(); 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<>(); classesToJars = new HashMap<>(); List<URL> urls = getClassLoaderUrlList(); for( URL url : urls ) { result.add( url.getPath() ); } if( servletContext != null ) { Set<String> resourcePaths = servletContext.getResourcePaths( RESOURCE_LIB_PATH ); if( resourcePaths != null ) { Set<String> pathes = new TreeSet<>( resourcePaths ); 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<>(); 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<>(); 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<>(); } 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<>(); 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<>(); 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 ); } }
Nur falls Sie auch dynamische Informationen zu CPU, Garbage Collection, Memory und Transaktionen anzeigen wollen:
Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die Datenermittlungsklasse:
CpuGcMemTx.java
package de.meinefirma.meinprojekt.backingbeans; import javax.management.MBeanServerConnection; import java.lang.management.*; import java.text.SimpleDateFormat; import java.util.*; public enum CpuGcMemTx { INSTANCE; private static final long MILLISEK500 = 500; private final SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); private final MBeanServerConnection mbs = ManagementFactory.getPlatformMBeanServer(); private final RuntimeMXBean rtMXBean = ManagementFactory.getRuntimeMXBean(); private final OperatingSystemMXBean opSystMXBean = ManagementFactory.getOperatingSystemMXBean(); private final List<GarbageCollectorMXBean> gcMXBeans = CpuGcMemTxMBeanUtil.getGarbageCollectorMXBeans( mbs ); private long lastUpTimeMs; private long lastGcTimeMs; private long lastCpuTimeNs; private long[] lastTxCounts = new long[2]; private String resultCpuGcMem; private String resultTx; public final void run() { if( mbs == null || rtMXBean == null || gcMXBeans == null ) { return; } long cpuCount = Math.max( 1, ( opSystMXBean != null ) ? opSystMXBean.getAvailableProcessors() : 1 ); long upTimeMs = rtMXBean.getUptime(); if( upTimeMs - lastUpTimeMs < MILLISEK500 ) { return; } long gcTimeMs = CpuGcMemTxMBeanUtil.readGarbageCollectionTimeMs( gcMXBeans ); double gcTimePercent = CpuGcMemTxMBeanUtil.calculateGarbageCollectionTimePercent( gcTimeMs, lastGcTimeMs, upTimeMs, lastUpTimeMs, cpuCount ); long cpuTimeNs = CpuGcMemTxMBeanUtil.readCpuTimeNs( mbs ); int cpuTimePercent = CpuGcMemTxMBeanUtil.calculateCpuTimePercent( cpuTimeNs, lastCpuTimeNs, upTimeMs, lastUpTimeMs, cpuCount ); long[] txCounts = CpuGcMemTxMBeanUtil.readTxCountsWebLogic( mbs, "AdminServer" ); String[] txPerSec = CpuGcMemTxMBeanUtil.calculatePerSecond( txCounts, lastTxCounts, upTimeMs, lastUpTimeMs ); StringBuilder cpuGcMem = new StringBuilder(); cpuGcMem.append( yyyyMMddHHmmss.format( new Date() ) ).append( "; CPUs: " ).append( cpuCount ); System.gc(); long memTotal = Runtime.getRuntime().totalMemory(); long memFree = Runtime.getRuntime().freeMemory(); cpuGcMem.append( "; CPU: " ).append( cpuTimePercent ); cpuGcMem.append( " %; GC: " ).append( CpuGcMemTxMBeanUtil.doubleString1( gcTimePercent ) ); cpuGcMem.append( " %; MemUsed: " ).append( CpuGcMemTxMBeanUtil.doubleString1( (memTotal - memFree) / (1024. * 1024.) ) ); cpuGcMem.append( " MB; MemTotal: " ).append( CpuGcMemTxMBeanUtil.doubleString1( memTotal / (1024. * 1024.) ) ).append( " MB" ); resultCpuGcMem = cpuGcMem.toString(); resultTx = null; if( txCounts != null && txCounts.length > 1 && (txCounts[0] > 0 || txCounts[1] > 0) ) { StringBuilder tx = new StringBuilder(); tx.append( yyyyMMddHHmmss.format( new Date() ) ); tx.append( "; Tx/Sec: " ).append( txPerSec[0] ).append( "; TxRollback/Sec: " ).append( txPerSec[1] ); tx.append( "; TxSum: " ).append( txCounts[0] ).append( "; TxRollbackSum: " ).append( txCounts[1] ); lastTxCounts[0] = txCounts[0]; lastTxCounts[1] = txCounts[1]; resultTx = tx.toString(); } lastUpTimeMs = upTimeMs; lastCpuTimeNs = cpuTimeNs; lastGcTimeMs = gcTimeMs; } public final String getCpuGcMem() { return resultCpuGcMem; } public final String getTx() { return resultTx; } }
Nur falls Sie auch dynamische Informationen zu CPU, Garbage Collection, Memory und Transaktionen anzeigen wollen:
Erzeugen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt\backingbeans die MBean-Utility-Klasse:
CpuGcMemTxMBeanUtil.java
package de.meinefirma.meinprojekt.backingbeans; import java.lang.management.*; import java.text.DecimalFormat; import java.util.*; import javax.management.*; class CpuGcMemTxMBeanUtil { private static final double NANOSEK_PRO_MILLISEK = 1000000.; private static final double MILLISEK_PRO_SEK = 1000.; private static final double PROZENT_MAX_100 = 100.; private static final double SCHWELLE_NACHKOMMA = 10.; /** Konvertiere double-Wert in String mit keiner oder einer Nachkommastelle. */ static String doubleString1( double d ) { final DecimalFormat df1 = new DecimalFormat( "0.0" ); return ( d < SCHWELLE_NACHKOMMA ) ? df1.format( d ) : ("" + Math.round( d )); } /** Berechne prozentuale CPU-Zeit. */ static int calculateCpuTimePercent( long cpuTimeNs, long lastCpuTimeNs, long upTimeMs, long lastUpTimeMs, long cpuCount ) { return (int) Math.round( Math.min( PROZENT_MAX_100, (cpuTimeNs - lastCpuTimeNs) / ((upTimeMs - lastUpTimeMs) * cpuCount * (NANOSEK_PRO_MILLISEK/PROZENT_MAX_100)) ) ); } /** Berechne prozentuale Garbage-Collection-Zeit. */ static double calculateGarbageCollectionTimePercent( long gcTimeMs, long lastGcTimeMs, long upTimeMs, long lastUpTimeMs, long cpuCount ) { return Math.min( PROZENT_MAX_100, ((gcTimeMs - lastGcTimeMs) * PROZENT_MAX_100) / ((upTimeMs - lastUpTimeMs) * cpuCount) ); } /** Berechne Anzahl pro Sekunde (z.B. Anzahl Transaktionen). */ static String[] calculatePerSecond( long[] counts, long[] lastCounts, long upTimeMs, long lastUpTimeMs ) { int n = Math.min( counts.length, lastCounts.length ); String[] result = new String[n]; for( int i = 0; i < n; i++ ) { result[i] = doubleString1( MILLISEK_PRO_SEK * (counts[i] - lastCounts[i]) / (upTimeMs - lastUpTimeMs) ); } return result; } /** Lies Garbage-Collection-Zeit (Millisekunden). */ static long readGarbageCollectionTimeMs( List<GarbageCollectorMXBean> gcMXBeans ) { long gcTimeMs = 0; for( GarbageCollectorMXBean gc : gcMXBeans ) { gcTimeMs += gc.getCollectionTime(); } return gcTimeMs; } /** Lies CPU-Zeit (Nanosekunden). */ static long readCpuTimeNs( MBeanServerConnection mbs ) { return readLongFromMBean( mbs, "java.lang:type=OperatingSystem", "ProcessCpuTime" ); } /** Falls WebLogic: Lies Anzahl Transaktionen. */ static long[] readTxCountsWebLogic( MBeanServerConnection mbs, String serverName ) { long[] txValues = new long[2]; txValues[0] = readLongFromMBean( mbs, "com.bea:ServerRuntime=" + serverName + ",Name=JTARuntime,Type=JTARuntime", "TransactionTotalCount" ); txValues[1] = readLongFromMBean( mbs, "com.bea:ServerRuntime=" + serverName + ",Name=JTARuntime,Type=JTARuntime", "TransactionRolledBackTotalCount" ); return txValues; } /** Lies Long-Attribut von MBean. */ private static long readLongFromMBean( MBeanServerConnection mbs, String objectName, String attributeName ) { Object attrObj = readAttrFromMBean( mbs, objectName, attributeName ); if( attrObj instanceof Long ) { return ((Long) attrObj).longValue(); } return -1; } /** Lies Double-Attribut von MBean. */ private static double readDoubleFromMBean( MBeanServerConnection mbs, String objectName, String attributeName ) { Object attrObj = readAttrFromMBean( mbs, objectName, attributeName ); if( attrObj instanceof Double ) { return ((Double) attrObj).doubleValue(); } return -1; } /** Lies Attribut von MBean. */ private static Object readAttrFromMBean( MBeanServerConnection mbs, String objectName, String attributeName ) { List<Object> lst = new ArrayList<>(); try { Set<ObjectName> objectNames = mbs.queryNames( new ObjectName( objectName ), null ); for( ObjectName on : objectNames ) { Object attr = mbs.getAttribute( on, attributeName ); if( objectNames.size() == 1 ) { return attr; } lst.add( attr ); } return lst; } catch( Exception ex ) { return null; } } /** Ermittle Garbage-Collector-MXBeans. */ static List<GarbageCollectorMXBean> getGarbageCollectorMXBeans( MBeanServerConnection mbs ) { try { List<GarbageCollectorMXBean> gcMXBeans = new ArrayList<>(); ObjectName gcAllObjectName = new ObjectName( ManagementFactory.GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*" ); Set<ObjectName> gcMXBeanObjectNames = mbs.queryNames( gcAllObjectName, null ); for( ObjectName on : gcMXBeanObjectNames ) { GarbageCollectorMXBean gc = ManagementFactory.newPlatformMXBeanProxy( mbs, on.getCanonicalName(), GarbageCollectorMXBean.class ); gcMXBeans.add( gc ); } return gcMXBeans; } catch( Exception ex ) { return null; } } }
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfInspectImplementationsVersions
tree /F
Sie erhalten:
[\MeinWorkspace\JsfInspectImplementationsVersions] |- [src] | '- [main] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- [backingbeans] | | |- CpuGcMemTx.java | | |- CpuGcMemTxMBeanUtil.java | | |- 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
Führen Sie im Kommandozeilenfenster aus:
cd \MeinWorkspace\JsfInspectImplementationsVersions
mvn jetty:run
start http://localhost:8080/JsfInspectImplementationsVersions/inspectImplementationsVersions.xhtml
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.
Die angezeigte Webseite beginnt mit der Anzeige von Implementierungen und Versionen:
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:
Wenn Sie einen dieser Buttons anklicken, erhalten Sie eine Liste dieser doppelten Klassen, und in welchen Libs es die jeweilige Klasse gibt:
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.
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>