WebApps– mit JSF 2.2, Primefaces 5, REST, JPA 2, JEE7, JBoss WildFly 10

Die Motivation

In einem Blog-Eintrag wurde bereits eine Beispiel-Applikation mit der JSF 2.2-Implementierung Apache MyFaces besprochen, weshalb hier eine weitere JSF 2-Implementierung, nämlich die Primefaces, besprochen werden sollen und die Migration der Beispielapplikation employeetimetracker von MyFaces auf Primefaces erklärt wird. Über die Faces Technologie gibt es bereits einen Blog-Eintrag, worin die Generierung einer JSF 1.2-Applikation auf Basis der JPA-Entities für den JPA-Provider EclipseLink mittels der NetBeans IDE für das Deployment auf dem  GlassFish Application Server beschrieben wurde. Die in diesem Blog-Eintrag mittels JBoss Forge Eclipse PlugIn erstellte und migrierte Applikation employeetimetracker-primefaces wird auf dem JBoss WildFly 10 Final deployt und der Vollständigkeit wegen auch auf dem JBoss WildFly 9 Final und kann ebenso leicht auf weiteren WildFly Servern deployt werden, wie z. B. dem JBoss WildFly 11 Final und JBoss WildFly 12 Final. Dabei ist die Verwendung der beiden JBoss WildFly JEE7-Application Server in einem Blog-Eintrag bereits beschrieben worden und unterstützt den Einsatz der robusten, aber stets eleganten, etablierten Primefaces Frontend-Technologie optimal.

Migration der Beispiel Applikation auf Primefaces

Wie in der pom.xml erkennbar ist, werden hier die folgenden JEE7 Artefakte für den JBoss WildFly verwendet und importiert:

WildFly JBoss Java EE 7 Specification APIs with Tools:
- jboss-javaee-7.0-with-tools

WildFly JBoss Java EE 7 Specification APIs with Resteasy:
- jboss-javaee-7.0-with-resteasy

WildFly JBoss Java EE 7 Specification APIs with Hibernate:
- jboss-javaee-7.0-with-hibernate

Weiterhin werden die folgenden JEE Dependencies verwendet:

- jboss-annotations-api_1.1_spec
- jboss-jaxrs-api_2.0_spec
- resteasy-jackson2-provider

- hibernate-jpa-2.1-api
- jboss-ejb-api_3.2_spec
- hibernate-jpamodelgen
- jboss-servlet-api_3.1_spec

Und ebenfalls die CDI 1.1 Dependency:

- cdi-api_1.1

Die Aktivierung von CDI erfolgt bekanntlich mittels beans.xml im WEB-INF Verzeichnis.

Primefaces:

Die Einbindung der erforderlichen Primefaces-UI-Bibliothek primefaces-5.2.jar erfolgt ganz simpel über die entsprechende Maven Primefaces Dependency in der pom.xml. Es ist also im Gegensatz zu einem Ant-Build hier kein extra /lib-Verzeichnis und keine build.xml für einen Ant Build-Prozess mehr erforderlich, denn es wird einfach der folgende Eintrag zur pom.xml für den Maven Build hinzugefügt:

    <dependency>
      <groupId>org.primefaces</groupId>
      <artifactId>primefaces</artifactId>
      <version>5.2</version>
    </dependency>

Allein durch Änderung dieses Eintrags in der pom.xml kann jederzeit auch auf die gewünschte Primefaces-Version umgestellt werden. Bei der Umstellung der ca. 40 XHTML-Views von MyFaces auf Primefaces sind der Primefaces Showcase sehr hilfreich. Ebenfalls wurde das sehr gute bei DZone erhältliche PDF zur JSTL hier verwendet. Durch die Verwendung von CDI 1.1 haben die JSF ManagedBeans stattdessen die Annotation @Named und bleiben, bis auf ein paar Helper-Methoden zur Ausgabeformatierung, nahezu identisch zu den ManagedBeans der  MyFaces-Applikation employeetimetracker. Auch gibt es noch ein paar zusätzliche toString()-Methoden auf  einigen JPA-Entities, die bei Bedarf eine geänderte String-Repräsentation der Attribute liefern können.

forge.taglib.xml:

Die in den Beispiel-Applikationen verwendete, moderne Forge TagLib kann sehr einfach erweitert werden, indem eine zusätzlich benötigte Methode in der ViewUtils-Klasse ergänzt wird und deren Signatur in der forge.taglib.xml im entsprechenden Verzeichnis bekanntgemacht wird. Die TagLib wird in der entsprechenden .xhtml-View über den korrekten Eintrag mit dem richtigen Prefix im Document-Header eingebunden und mittels forgetaglib-Scope mittels Expression Language (EL) an der gewünschten Stelle mit den richtigen Input-Parametern aufgerufen. Hier die forge.taglib.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib xmlns="http://java.sun.com/JSF/Facelet">
    <namespace>http://jboss.org/forge/view</namespace>

    <function>
    	<function-name>asList</function-name>
    	<function-class>de.binaris.employeetimetracker.view.ViewUtils</function-class>
    	<function-signature>
    		java.util.List asList(java.util.Collection)
    	</function-signature>
    </function>
    
    <function>
        <function-name>display</function-name>
        <function-class>de.binaris.employeetimetracker.view.ViewUtils</function-class>
        <function-signature>
            java.lang.String display(java.lang.Object)
        </function-signature>
    </function>
	
    <function>
    	<function-name>count</function-name>
    	<function-class>de.binaris.employeetimetracker.view.ViewUtils</function-class>
    	<function-signature>
    		int count(java.util.Collection)
    	</function-signature>
    </function>
	
    <function>
    	<function-name>listExcludingSelectedOne</function-name>
    	<function-class>de.binaris.employeetimetracker.view.ViewUtils</function-class>
    	<function-signature>
    		java.util.List listExcludingSelectedOne(java.util.Collection,java.lang.Object)
    	</function-signature>
    </function>    
</facelet-taglib>

Hier auch die ViewUtils-Klasse unter Verwendung generischer Typen T und Collections List<T>:

// ViewUtils:

package de.binaris.employeetimetracker.view;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.Id;

/**
 * Utilities for working with Java Server Faces views.
 */
public final class ViewUtils {

   public static <T> List<T> asList(Collection<T> collection) {

      if (collection == null) {
         return null;
      }
      return new ArrayList<T>(collection);
   }

   public static String display(Object object) {
      if (object == null) {
         return null;
      }
      try {
         // Invoke toString if declared in the class. If not found, 
         // the NoSuchMethodException is caught and handled
         object.getClass().getDeclaredMethod("toString");
         return object.toString();
      }
      catch (NoSuchMethodException noMethodEx) {
         try {
            for (Field field : object.getClass().getDeclaredFields()) {
               // Find the primary key field and display it
               if (field.getAnnotation(Id.class) != null) {
                  // Find a matching getter and invoke it to display the key
                  for (Method method : object.getClass().getDeclaredMethods()) {
                     if (method.equals(new PropertyDescriptor(field.getName(),                     
                                          object.getClass()).getReadMethod()))
                     {
                        return method.invoke(object).toString();
                     }
                  }
               }
            }
            for (Method method : object.getClass().getDeclaredMethods()) {
               // Find the primary key as a property instead of a field, and display it
               if (method.getAnnotation(Id.class) != null) {
                  return method.invoke(object).toString();
               }
            }
         } catch (Exception ex) {
            // Unlikely, but abort and stop view generation if any exception is thrown
            throw new RuntimeException(ex);
         }
      }
      return null;
   }

   public static <T> int count(Collection<T> collection) {
      if (collection == null) {
               return 0;
      }
      return collection.size();
   }

   public static <T> List<T> listExcludingSelectedOne(Collection<T> collection, T selected) {
      if (collection == null) {
         return null;
      }
      if (selected == null) {
        return new ArrayList<T>(collection);
      }
      List<T> list = new ArrayList<T>(collection);
      list.remove(selected);
      return list;
   }

   private ViewUtils() {
      // Can never be called, only by getInstance - Singleton pattern
   }
}

Über Tags, TagLibs und die Erweiterung bestehender und die Definition eigener Tags gibt es bereits einen Blog-Eintrag und durch die soeben gezeigten Beispiele ist erkennbar, wie leicht es ist, eigene Tags für die .xhtml-Views heutiger Primefaces-Applikationen zu definieren.

Die Konfiguration des JBoss WildFly Application Servers 11 Final erfolgt analog der Konfigurationen der JBoss Application Server WildFly 8.1, 8.2, 9 und 10 Final und betrifft die Datasource, die MySQL-Datenbank Inno DB 5.x, Hibernate 5, das Logging und den Connection Pool und ist, genau wie die Test-Frameworks und die Aktivierung der RESTful WebService-Schnittstelle ein wichtiges Thema, welches in diesem Blog bereits in einem früheren Blog-Eintrag beschrieben wurde. Die Anbindung an die relationale  Datenbank MySQL 5 erfolgt übrigens analog für die JBoss Application Server WildFly 8.1, 8.2, 9, 10 und WildFly 11.

Bei dieser Gelegenheit darf auch das sehr effektive, interessante und erfolgreiche Seminar

“TDD mit Java”

von Binaris Informatik erwähnt werden.

Hier der WildFly 10 Final als Zip-Archiv zum Download und Entpacken: Für Linux hier und für Windows hier. Voraussetzung ist jeweils Java 8.

Die fertig konfigurierte Version des WildFly 10.Final mit vielen Beispiel-Applikationen der vorigen Blog-Einträge hier zum Download bereit kann runtergeladen, entpackt und gestartet werden. Dabei ist nur der Pfad für JAVA_HOME in der standalone.xml anzupassen, wie in diesem Blog-Eintrag hier beschrieben wurde, damit dieser auf das tatsächliche JDK 8-Verzeichnis des Server-Systems zeigt und auch der Pfad für das log-Dateien Verzeichnis in der Datei [server]/standalone/configuration/logging.properties.

Hier auch der WildFly 11 Final als Zip-Archiv zum Download und Entpacken: Für Linux hier und für Windows hier. Minimale Voraussetzung ist jeweils Java 8. Der fertig konfigurierte WildFly 11 mit vielen weiteren Beispiel-Applikationen ist hier zum Download verfügbar, kann runtergeladen, entpackt und gestartet werden.

Auch hier ist nur der Pfad des JAVA_HOME anzupassen, damit dieser auf das tatsächliche Open JDK – Verzeichnis des Server-Systems zeigt und ebenfalls der Pfad für das log-Dateien Verzeichnis in der logging.properties.

Die Entity-Relationship Diagramme, welche als Vorlagen der Beispiel-Applikation verwendet wurden (TimeTracking, Project Tracking) können in der Primefaces Beispielapplikation hier nachgesehen werden.

Die Beispiel-Applikation kann auch selbst erneut deployt werden, ohne den bereits gestarteten WildFly Application Server überhaupt stoppen oder erneut starten zu müssen. Bei laufendem Server

– ins Server-Unterverzeichnis /standalone/deployments das Archiv employeetimetracker-primefaces.war speichern

Sofort wird die Beispiel-Applikation deployt und in wenigen Sekunden gestartet und kann entweder hier oder unter der folgenden Url aufgerufen werden:

http://localhost:8080/employeetimetracker-primefaces

Die Blog-Einträge können übrigens hier nach Stichworten und Erscheinungsmonat leicht durchsucht werden.

Somit besteht durchaus die Möglichkeit, dass noch weitere Blog-Einträge zu Webframeworks wie Primefaces 6, Angular JS und Bootstrap, sowie den die JEE7-Spezifikation oder JEE8-Spezifikation implementierenden WebService-und Backend-Technologien folgen. Allen interessierten Leserinnen und Lesern weiterhin viel Freude bei der agilen Softwareentwicklung mittels Scrum und dem Test-Driven Development mit Java, sowie einen schönen Frühling.