Die JEE-Generatoren sind in der Lage, eine komplette JEE6- oder JEE7-Web-Applikation aus einem einfachen Modell zu generieren. Das Framework wurde mit Xtext realisiert und die beiden Generatoren sind als Eclipse-Plugin verfügbar.
Ein neues JEE-Projekt kann mit der JEE-Distribution einfach angelegt werden. Dazu muss der Menüpunkt File -> New -> Project… aufgerufen werden. Im sich öffnenden Dialog wählt man unter dem Punkt Xtext den Punkt JEE6 Generator Project aus:
Danach wählt man einen Projektnamen aus:
Es wird danach ein Projekt namens “beispiel” angelegt. In diesem Projekt sind alle zum Bau notwendigen Dateien enthalten. Die JEE-Distribution enthält auch schon die JBoss-Tools, mit denen die JBoss-Laufzeitumgebung definiert werden kann. Diese Laufzeitumgebung muss als Library in den Build Path mit aufgenommen werden. Will man das Modell später mit ant bauen, empfiehlt sich das Anpassen der Datei $HOME/.jee6.properties. Hier muss eingetragen werden, wo sich die JEE6-Distribution und der benutzte Application Server befindet. Die Variablen lauten:
Dies muss nur ein einziges Mal für alle JEE-Projekte durchgeführt werden. Die letzten beiden Anmerkungen können in den Dateien $PROJECT_HOME/LiesMich.txt bzw. $PROJECT_HOME/ReadMe.txt nachgelesen werden.
Die Datei $PROJECT_HOME/model/beispiel.jee6 ist das Modell für unsere Web-Anwendung. Hier sollte die erste Befehlszeile den eigenen Bedürfnissen angepasst werden. Aus der Befehlszeile
application "beispiel" context "/beispiel" package org.example.jee6.beispiel development strict;
...
kann z.B. die Package-Definition und der Klartextname der Applikation angepasst werden.
application "beispiel" context "/beispiel" package org.example.jee6.beispiel development strict;
...
Danach kann durch Aufrufen der ant-Targets generate und package die Applikation generiert und die WAR-Datei gebaut werden. Diese kann mit ant deploy in den passenden Application Server per Hot Deployment installiert werden. Der Generatorlauf erzeugt dabei folgende Artefakte:
XHTML Presentation Layer inkl. Logos und CSS
Die Dateien für den XHTML-Presentation Layer werden nur generiert, wenn die dazu benötigte Dateien noch nicht vorhanden sind. Bestehende Dateien werden durch den Generator nicht überschrieben. Die Action Handler und DAOs werden gemäß dem sog. Generation Gap Pattern generiert. Die abstrakten Basisklassen werden bei jedem Generatorlauf neu generiert, während die konkreten Klassen für die Implementierung der Business Logik nur einmal bei Fehlen generiert werden. In der abstrakten Basisklasse der DAOs werden z.B. die konfigurierten Persistenzkontexte untergebracht. Die generierten Dateien werden in folgende Verzeichnisse hinterlegt:
Hinweis! Die Inhalte der Verzeichnisse src/main/java, src/main/resources und src/main/webapp sollten der Verseinsverwaltung der Wahl zugeführt werden, da dort auch eigene Implementierungen untergebracht sein können. Die Inhalte der Verzeichnisse src/generated/java und src/generated/resources sollten nicht einer Versionsverwaltung zugeführt werden, da sie bei jedem JEE-Generatorlauf neu generiert werden.
In der Modelldatei wird als erstes Kommando die Applikation beschrieben. Die Syntax lautet:
application <Name> context <Context-Path> package <Package-Id> (timeout <Timeout-Minutes>) (<Project-State>) (strict);
Der Name ist ein String, der im Klartext die Applikation beschreibt. Der Context-Path ist ein String, der den Applikationskontext festlegt. Dieser muss zwingend mit einem Schrägstrich “/” beginnen. Es ist der erste Teil der URI.
Optional kann der Session Timeout in Minuten festgelegt werden. Wird dieser
nicht definiert, wird der Default des Application Servers verwendet.
Üblicherweise beträgt dieser Wert 30 Minuten. Als project-state können die
drei Schlüsselwörter development
, integration
und productive
dienen und beschreiben den Zustand des Projektes. Abhängig davon wird z.B.
das Logging entsprechend verschärft.
Datei | development | integration | productive | |
---|---|---|---|---|
Logging Level | src/generated/java/<package>/log4j.properties | DEBUG | DEBUG | INFO |
Property hibernate.show_sql | src/generated/resources/WEB-INF/classes/META-INF/persistence.xml | true | false | false |
Property eclipselink.logging.level | src/generated/resources/WEB-INF/classes/META-INF/persistence.xml | DEBUG | INFO | INFO |
src/generated/resources/WEB-INF/web.xml | Development | SystemTest | Productive |
Als letztes optionales Schlüsselwort dient strict
. Es entscheidet, ob in
den Basisklassen der Action Handler die Standardmethoden abstrakt
vordefiniert werden und somit in den konkreten Klassen vorhanden sein
müssen. Dadurch wird einerseits der Code besser, allerdings kann es
vorkommen, dass die geforderten Methoden tatsächlich nicht gebraucht werden.
Das kann aber nur bei starker Anpassung der XHTML-Masken passieren.
Nach dem application
-Kommando werden Optionen festgelegt, die das
Verhalten der Web-Applikation näher beschreiben. Danach folgen die
Beschreibungen der Entity Beans, welche auf einer eigenen Seite genauer
beschrieben werden und zuletzt die Aufgaben- bzw.
Prozess-Umgebungen, in denen die Entity Beans benutzt werden sollen.
Die Applikationsoptionen beschreiben die Web Applikation näher. Sie beeinflussen folgende Eigenschaften:
Der JEE-Generator unterstützt mehrere Persistenz Kontexte in einer Applikation. Das setzt voraus, dass in diesem Falle die Datasources als XA-Datasource im Application Server konfiguriert sind. Die Syntax lautet:
persistence unit <Unit-Name> jndi <Jndi-Name> (cacheable) (MySQL|DB2|Oracle);
Der unit-name wird in der persistence.xml als Referenz innerhalb der Web
Applikation verwendet. Der JNDI-Name definiert, wie die Datasource im
Application Server wiederzufinden ist. Eine eingehende Beschreibung, wie
der JDNI-Name lauten sollte, findet sich auf diesen Seiten. Wird das
optionale Schlüsselwort cacheable
verwendet, wird die Persistence Unit als
Second Level Cache konfiguriert.
Hinweis! Es reicht nicht, nur das cacheable
Schlüsselwort zu setzen, um im
Application Server Second Level Caching zu aktivieren. Es müssen meistens
noch am Application Server selbst noch Konfigurationen vorgenommen werden.
Es werden die Datenbanken
unterstützt. Die Angabe wird nötig, um in der generierten Datei persistence.xml den SQL-Sprachdialekt festzulegen.
In mehrsprachigen Umgebungen ist die Lokalisierung der Applikation immer wünschenswert. Der JEE-Generator hat für alle Komponenten eine entsprechende Unterstützung parat. Die Auswahl der Sprache selbst wird typischerweise im Browser eingestellt. Die Web Applikation erfährt das dadurch, dass die im Browser eingestellte Sprache im Request mitgeschickt wird. Die Lokalisierung wird über sog. Resource Bundles vorgenommen. Diese werden in den XHTML-Seiten eingebunden. Für jedes Attribut, Entity Bean, etc. werden die dafür benötigten Einträge in die Resource Bundles nachgetragen, sofern sie noch nicht enthalten sind. Bereits bestehende Einträge werden nicht verändert. An dieser Stelle wird beschrieben, wie in der Modelldatei konfiguriert wird, welche Sprachen unterstützt werden sollen. Die Syntax lautet:
locale <Language> (<Country>) (default);
Die language
ist der ISO-Code der zu verwendenden Sprache und hat
typischerweise kleine Buchstaben. Der optionale Wert country
bestimmt
die ISO-Länderkennung typischerweise in Großbuchstaben. Dadurch werden
landestypische Sprachvarianten unterschieden.
Hinweis! Es muss mindestens eine Locale-Definition vorhanden sein und genau eine
braucht das ansonsten optionale Schlüsselwort default
. Diese Sprache wird
verwendet, falls die im Browser eingestellte Sprache in den Resource Bundles
nicht gefunden werden konnte.
Will die Web Applikation E-Mail verschicken, muss einerseits der Mail-Versand im Application Server konfiguriert sein, und andererseits hier im Model eingetragen werden, unter welchem JDNI-Namen der Mailservice erreichbar ist. Dazu dient die einfache Syntax:
smtp <Jndi-Name>;
Die in den genötigten XML-Deskriptoren werden vom JEE-Generator automatisch erzeugt. Die Resource muss manuell in dem DAO eingetragen werden, in dem Mailing verwendet werden soll. Das dazugehörige Code-Schnipsel sieht folgendermaßen aus:
@Resource(name="<jndi-name>")
private javax.mail.Session mailSession;
Wird für jndi-name “mail/Default” verwendet, muss im JBoss Application Server nichts mehr konfiguriert werden.
Sollen nur bestimmte User für bestimmte Bereiche (in der JEE-Generator-Nomenklatur “Prozesse”) Zugang haben, so muss eine sog. Security Domain über einen JNDI-Namen referenziert werden. Dieser muss dementsprechen wie die E-Mail im Application Server konfiguriert sein und benutzt den JAAS-Standard. Die Syntax lautet:
security domain <Jndi-Name> (clustered);
Das optionale Schlüsselwort clustered
bestimt, ob die Security Domain in
einer geclusterten Umgebung funktionsfähig sein muss.
Mit Web-Parametern kann man der Web Applikation in der web.xml Parameter übergeben. Die Syntax lautet:
param <Key> = <Value> (description <Description>);
Die Einträge können beliebig häufig im Modell eingetragen werden. Aus folgender Modellzeile:
param org.jeegen.purchasing.EMPLOYEES = "employees" description "LDAP-Gruppe aller Angestellten";
wird in der web.xml:
<context-param>
<description>LDAP-Gruppe aller Angestellten</description>
<param-name>org.jeegen.purchasing.EMPLOYEES</param-name>
<param-value>employees</param-value>
</context-param>
Mit der Methode String getInitParameter(final String key)
in einem Action
Handler kann auf diesen Wert zugegriffen werden. Die Werte sind aus Sicht
des Application Servers und der Applikation selbst nicht veränderlich.
In der Modelldatei können Entity Beans über die Schlüsselwörter entity
und options
definiert werden. Options sind spezielle Entity Beans, mit denen man Auswahllisten definieren kann. Die Auswahllisten können entweder editierbar sein, oder als Enumeration festgelegt werden.
Eine einfache Entity Bean wird mit dem Schlüsselwort entity
eingeleitet. In ihr können beliebig viele Attribute benutzt werden. Es können Text- und EMail-Felder als ID-Felder definiert werden. Wird kein ID-Feld definiert, wird automatisch ein Integer-Attribut mit Namen id generiert. Eine einfache Definition sieht folgendermaßen aus:
entity Address
{
Text street;
Text plz;
Text location;
}
process User
{
Address
}
Nach einem Generatorlauf kann die Applikation deployed werden. Die Maske sieht dann wie folgt aus:
Will man noch den Adresstyp zwischen privat und geschäftlich angeben, kann
eine nicht editierbare option benutzt werden. Diese option
wird als
Enumeration generiert. Als Werte können nur Textschlüssel verwendet werden.
Diese Schlüssel werden automatisch im Resource Bundle aller definierter
Sprachen angelegt, falls sie noch nicht vorhanden sind. In der Address
Entity Bean wird der Adresstyp AddressOption als Attributtyp Option
eingebunden. In der Datenbank entsteht dadurch eine 1:1-Relation.
options AddressOption
{
"address.work",
"address.home"
}
entity Address
{
Text street;
Text plz;
Text location;
Option AddressOption addressOption;
}
In der Maske wird dadurch eine Combobox generiert. Passt man noch die Resource Bundles unter $PROJECT_HOME/src/main/java/<package>/messages.properties an, sieht dann die Maske folgendermaßen aus:
Die Generierung einer Combobox erfordert noch weitere Dinge im Hintergrund:
equals()
und hash()
so überschreiben, dass Entity Beans mit denselben IDs als identisch angesehen werden, sonst funktioniert der Value Converter nicht.Es macht natürlich Sinn, dass es Personen gibt, in denen mehrere Adressen gespeichert werden. Damit wird das Modell um die Entity Bean Person ergänzt:
entity Person
{
Text id login;
Text name;
Entity Address [] addresses;
}
In dieser Entity Bean wird ein Textfeld als ID-Feld markiert. Dadurch gibt
es kein automatisches Integer-ID-Feld mehr. Es darf nur ein Feld als
ID-Feld innerhalb einer Entity Bean gesetzt werden. Ferner wird mit dem
Schlüsselwort Entity
eine 1:n-Relation eingeführt, um mehrere Adressen an
die Entity Bean binden zu können. Dabei muss der Typ - in diesem Falle
Address - mit den []-Zeichen markiert werden, sonst wäre die Relation nur
1:1. Die Maske für die Person Entity Bean sieht folgendermaßen aus:
Klickt man auf den “Edit addresses”-Button, gelangt man in die schon bekannte Maske der Address Entity Bean.
Um Daten in einer Datenbank zu speichern, machen diverse Datentypen Sinn.
Jedes Attribut besteht aus der Kombination Datentyp, ergänzende Optionen
Attributname und Transient-Flag. Wird ein Attribut abschließend mit dem
transient
-Schlüsselwort markiert, so wird dieses Attribut nicht in der
Datenbank gespeichert. In diesem Fall werden zwei Klassen für die Entity
Bean gemäß den Generation Gap
Pattern
generiert. Eine abstrakte Basisklasse enthält die Attribute der Entity
Bean, die konkrete Klasse enthält Getter- und Setter-Methoden für die
transienten Attribute. Man kann die transienten Methoden dazu verwenden, um
aus anderen Attributen Werte zusammen zu bauen. Aus Vor- und Nachnamen kann
man den gesamten Namen als transientes Attribut herleiten.
entity Person
{
Text id login;
Text forename;
Text surename;
Text name transient;
Entity Address [] addresses;
}
Daraus wird in der Datei $PROJECT_HOME/src/main/java/<package>/entites/Person.java:
/*
* Generated by Xtext/JEE6 Generator.
* Copyright (C) 2016 Steffen A. Mork, Dominik Pieper
* $Id$
*/
package org.jeegen.jee6.beispiel.entities;
import javax.persistence.*;
/**
* This class implements the Person entity bean,
*/
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "Person")
public class Person extends AbstractPerson {
private static final long serialVersionUID = 1L;
/**
* This method is a transient getter of the virtual property name.
*
* @return The computed value for property name.
*/
@Transient
@Override
public String getName()
{
return getSurename() + ", " + getForename();
}
}
Aus Gründen der Übersichtlichkeit wurde auf Prüfen von Null-Pointern verzichtet. Im Folgenden werden die Attribute aufgelistet und erklärt.
Das Textattribut wurde schon ausführlich erklärt. Es hat die Syntax:
Text (id) <Name> (transient);
In einer XHTML-Seite wird aus der Modellzeile
Text subject;
im Formular folgendes Schnipsel generiert:
<tr>
<td class="mid">
<h:outputLabel for="subject" value="#{msg['info.startup.subject']}"/>
</td>
<td>
<h:inputText id="subject" label="#{msg['info.startup.subject']}"
maxlength="255" value="#{infoHandler.startup.subject}"/>
</td>
</tr>
Eine Besonderheit ist die Benennung eines Text
-Attributes unter JEE 7.
Wenn der Name url heißt, wird das XHTML ein wenig anders generiert:
<tr>
<td class="mid">
<h:outputLabel for="subject" value="#{msg['info.startup.subject']}"/>
</td>
<td>
<h:inputText p:type="url" id="subject" label="#{msg['info.startup.subject']}"
maxlength="255" value="#{infoHandler.startup.subject}"/>
</td>
</tr>
Der Aplication Server erzeugt daraus HTML5, das auf Mobile Devices eine andere Tastatur bei der Eingabe einblendet. Dadurch kann die Eingabe von URLs vereinfacht werden.
Integer-Datentypen werden durch das Schlüsselwort Int
gefolgt von einem
Variablennamen eingeleitet. Im XHTML-Formular wird dabei automatisch ein
entsprechender Value Converter generiert, der den Inhalt des Eingabefeldes
automatisch in einen Integer umwandelt. Die Syntax lautet:
Int <Name> (transient);
Aus der Modellzeile
Int integerEntry;
wird folgendes Schnipsel im XHTML generiert:
<tr>
<td class="mid">
<h:outputLabel for="integerEntry" value="#{msg['info.startup.integerentry']}"/>
</td>
<td>
<h:inputText id="integerEntry" label="#{msg['info.startup.integerentry']}" size="10"
value="#{infoHandler.startup.integerEntry}"/>
</td>
</tr>
Unter JEE 7 wird der XHTML-Schnipsel ein wenig anders generiert. durch
p:type="number"
wird nur noch die Eingabe von Ziffern möglich:
<tr>
<td class="mid">
<h:outputLabel for="integerEntry" value="#{msg['info.startup.integerentry']}"/>
</td>
<td>
<h:inputText p:type="number" id="integerEntry" label="#{msg['info.startup.integerentry']}"
size="10" value="#{infoHandler.startup.integerEntry}"/>
</td>
</tr>
Number-Datentypen werden durch das Schlüsselwort Number
gefolgt von einem Variablennamen eingeleitet. Java-seitig wird dafür ein double generiert Im XHTML-Formular wird dabei automatisch ein entsprechender Value Converter generiert, der den Inhalt des Eingabefeldes automatisch in eine Kommazahl umwandelt. Die Syntax lautet:
Number <Name> (transient);
Aus der Modellzeile
Number numberEntry;
wird folgendes Schnipsel im XHTML generiert:
<tr>
<td class="mid">
<h:outputLabel for="numberEntry" value="#{msg['info.startup.numberentry']}"/>
</td>
<td>
<h:inputText id="numberEntry" label="#{msg['info.startup.numberentry']}"
maxlength="10" value="#{infoHandler.startup.numberEntry}"/>
</td>
</tr>
Unter JEE 7 wird das XHTML so generiert, dass nur noch erlaubte Zeichen eingegeben werden können. Der Schnipsel sieht dann so aus:
<tr>
<td class="mid">
<h:outputLabel for="numberEntry" value="#{msg['info.startup.numberentry']}"/>
</td>
<td>
<h:inputText p:pattern="[-+]?\d+(?:[\.,]\d+)?" id="numberEntry"
label="#{msg['info.startup.numberentry']}"
maxlength="10" value="#{infoHandler.startup.numberEntry}"/>
</td>
</tr>
Ein E-Mail-Datentyp kann als ID geführt werden und ist im Prinzip ein Textfeld mit dem Unterschied, dass ein Validator die Eingabe auf das Format einer gültigen EMail-Adresse prüft. Die Syntax lautet:
Email (id) <Name> (transient);
In einer XHTML-Seite wird aus der Modellzeile
Email mail;
im Formular folgendes Schnipsel generiert:
<tr>
<td class="mid">
<h:outputLabel for="mail" value="#{msg['info.startup.mail']}"/>
</td>
<td>
<h:inputText id="mail" label="#{msg['info.startup.mail']}" maxlength="255"
value="#{infoHandler.startup.mail}">
<f:validator validatorId="mailValidator"/>
</h:inputText>
</td>
</tr>
Man beachte, dass automatisch der mailValidator
eingebunden ist, der
sich in der JEE-Utils-Bibliothek befindet.
Unter JEE 7 wird das XHTML durch das Attribut p:type="email"
ergänzt.
Dadurch wird auf Mobile Devices eine andere Tastatur für die Eingabe
eingeblendet und ermöglicht dem Benutzer eine vereinfachte Eingabe:
<tr>
<td class="mid">
<h:outputLabel for="mail" value="#{msg['info.startup.mail']}"/>
</td>
<td>
<h:inputText p:type="email" id="mail" label="#{msg['info.startup.mail']}"
maxlength="255" value="#{infoHandler.startup.mail}">
<f:validator validatorId="mailValidator"/>
</h:inputText>
</td>
</tr>
Alle Textfelder werden in der Datenbank als Varchar angelegt, welche eine Längenbegrenzung auf 255 Zeichen beinhaltet. Wenn man mehr braucht, muss ein sog. Character Large Object benutzt werden. Im XHTML-Formular wird daraus eine Textarea generiert. Java-seitig ist dieser Datentyp wie Text und E-Mail ein java.lang.String. Die Syntax lautet:
Clob <Name> (transient);
In einer XHTML-Seite wird aus der Modellzeile
Clob clobEntry;
im Formular folgendes Schnipsel generiert:
<tr>
<td class="top">
<h:outputLabel for="clobEntry" value="#{msg['info.startup.clobentry']}"/>
</td>
<td>
<h:inputTextarea cols="32" id="clobEntry" label="#{msg['info.startup.clobentry']}"
rows="7" value="#{infoHandler.startup.clobEntry}"/>
</td>
</tr>
Unter JEE 7 können leicht Forms gebaut werden, mit denen man Daten hochladen
kann. Das entsprechende XHTML-Tag lautet <h:inputFile .../>
. Die Syntax
in der DSL wurde dementsprechend erweitert:
Clob <Name> (upload <mime type>) (transient);
In einer XHTML-Seite wird aus der Modellzeile
Clob clobEntry upload "text/txt";
im Formular folgendes Schnipsel generiert:
<tr>
<td class="top">
<h:outputLabel for="clobEntry" value="#{msg['info.startup.clobentry']}"/>
</td>
<td>
<h:inputFile id="clobEntry" name="clobEntry"
value="#{infoHandler.startup.partStartupClobEntry}" accept="text/txt"/>
</td>
</tr>
Die umschließende Form ist jetzt eine sog. Multipart-Form und erzwingt die
HTTP-Methode POST. So können die hochzuladenden Dateien quasi als
Attachment dem Submit-Request beigelegt werden. Der Handler wird in der
dazugehörenden save()
-Methode entsprechend erweitert, sodass der die als
Part bezeichneten Attachments auswertet und gegebenenfalls in der
Entity Bean speichert. Für Clobs ist der Datentyp grundsätzlich String
.
Die save()
-Methode kann grundsätzlich angepasst werden, sodass sie z.B.
durch Syntaxprüfungen ergänzt werden können. Es ist auch zu beachten, dass
die Entity Bean nicht verändert wird, wenn kein Upload erfolgt. Es werden
also keine Clobs gelöscht. Auch dieses Verhalten kann in der save()
-Methode
angepasst werden.
Die save()
-Methode hat dann folgendes Aussehen:
public String saveStartup()
{
try
{
final javax.servlet.http.Part clobEntryPart = getPartStartupClobEntry();
// clobEntry
if ((clobEntryPart != null) && (clobEntryPart.getSize() > 0))
{
final byte data[] = Download.read(clobEntryPart.getInputStream(),
(int) clobEntryPart.getSize());
final String clob = new String(data, Charset.defaultCharset());
getStartup().setClobEntry(clob);
}
dao.updateStartup(getStartup());
setStartup(new Startup());
}
catch (IOException e)
{
log.log(Level.SEVERE, e.getMessage(), e);
}
return NAV_INFO_STARTUP;
}
Will man Binärdaten in der Datenbank speichern, muss man den Datentyp Blob
verwenden, der mit dem Schlüsselwort Blob
benutzt wird. Java-seitig wird
daraus ein byte[]
-Array. In einer XHTML-Maske erscheinen Attribute dieses
Typs nicht. Die Syntax lautet:
Blob <Name> (transient);
Hinweis! Aus Gründen der Performance sollten nicht zu große Binärdaten in einer Datenbank gespeichert werden. Große Datensätze bringt man besser im Dateisystem unter.
Unter JEE 7 können leicht Forms gebaut werden, mit denen man Daten hochladen
kann. Das entsprechende XHTML-Tag lautet <h:inputFile .../>
. Die Syntax
in der DSL wurde dementsprechend erweitert:
Blob <Name> (upload <mime type>) (transient);
In einer XHTML-Seite wird aus der Modellzeile
Blob blobEntry upload "image/jpg";
im Formular folgendes Schnipsel generiert:
<tr>
<td class="top">
<h:outputLabel for="blobEntry" value="#{msg['info.startup.blobentry']}"/>
</td>
<td>
<h:inputFile id="blobEntry" name="blobEntry"
value="#{infoHandler.startup.partStartupBlobEntry}" accept="image/jpg"/>
</td>
</tr>
Die umschließende Form ist jetzt eine sog. Multipart-Form und erzwingt die
HTTP-Methode POST. So können die hochzuladenden Dateien quasi als
Attachment dem Submit-Request beigelegt werden. Der Handler wird in der
dazugehörenden save()
-Methode entsprechend erweitert, sodass der die als
Part bezeichneten Attachments auswertet und gegebenenfalls in der
Entity Bean speichert. Für Blobs ist der Datentyp grundsätzlich byte[]
.
Die save()
-Methode kann grundsätzlich angepasst werden, sodass sie z.B.
durch Syntaxprüfungen ergänzt werden können. Es ist auch zu beachten, dass
die Entity Bean nicht verändert wird, wenn kein Upload erfolgt. Es werden
also keine Blobs gelöscht. Auch dieses Verhalten kann in der save()
-Methode
angepasst werden.
Die save()
-Methode hat dann folgendes Aussehen:
public String saveStartup()
{
try
{
final javax.servlet.http.Part blobEntryPart = getPartStartupBlobEntry();
// blobEntry
if ((blobEntryPart != null) && (blobEntryPart.getSize() > 0))
{
final byte data[] = Download.read(blobEntryPart.getInputStream(),
(int) blobEntryPart.getSize());
getStartup().setBlobEntry(data);
}
dao.updateStartup(getStartup());
setStartup(new Startup());
}
catch (IOException e)
{
log.log(Level.SEVERE, e.getMessage(), e);
}
return NAV_INFO_STARTUP;
}
Einen einfachen Booleschen Datentypen führt man mit dem Schlüsselwort
Boolean
ein. Sollte der Attributname active lauten, wird noch weitere
Funktionalität generiert. In der XHTML-Maske wird in der Liste ein
Kommandolink ergänzt, mit dem der Aktivierungsstatus dieses Attributes
gewechselt werden kann. Das setzt weitere Methoden im Action Handler und im
DAO voraus, die automatisch mit generiert werden.
<tr>
<td class="mid">
<h:outputLabel for="active" value="#{msg['info.startup.active']}"/>
</td>
<td>
<h:selectBooleanCheckbox id="active" label="#{msg['info.startup.active']}"
value="#{infoHandler.startup.active}"/>
</td>
</tr>
Mit diesem Datentypen kann ein Zeitstempel bestehend aus Uhrzeit und
Kalenderdatum in der Datenbank gespeichert werden. In der Entity Bean wird
hierfür der Datentyp Date
verwendet. Über die Verwendung von Datumsangaben
über Prepared Statements in EQL wird in diesem Artikel berichtet. Wird dem
Schlüsselwort Timestamp
noch ein auto
beigegeben, so wird automatisch beim
erstmaligem Speichern der Entity Bean das Erzeugungsdatum in dieses Attribut
gespeichert. Benutzt man stattdessen oder zusätzlich noch das Schlüsselwort
update
, wird bei jeder Änderung der Entity Bean in der Datenbank dieses
Attribut auf den aktuellen Zeitstempel gebracht. Die Syntax lautet:
Timestamp (auto) (update) <Name> (transient);
Für folgende Modellzeile
Timestamp timestamp1;
wird in der XHTML folgendes Schnipsel generiert:
<tr>
<td class="mid">
<h:outputLabel for="timestamp1" value="#{msg['info.startup.timestamp1']}"/>
</td>
<td>
<h:inputText id="timestamp1" label="#{msg['info.startup.timestamp1']}" maxlength="10"
styleClass="date" value="#{infoHandler.startup.timestamp1}">
<f:convertDateTime pattern="dd.MM.yyyy" type="date"/>
</h:inputText>
</td>
</tr>
Wird beim Timestamp das Schlüsselwort auto
oder update
ergänzt, wird kein
Formulareinstrag im XHTML generiert. Stattdessen werd in der Entity Bean
entsprechende Methoden ergänzt:
@PrePersist
public void prePersist() {
if (getCreation() == null) {
setCreation(new Date());
}
}
Durch den Test auf den Null Pointer kann vor der Persistierung der Entity Bean schon ein anderes Datum als Erzeugungsdatum angegeben werden.
@PreUpdate
public void preUpdate() {
setChanged(new Date());
}
Die Annotationen @PrePersist
und @PreUpdate
sind Bestandteile des JEE-Frameworks.
Mit diesem Datentypen kann ein Kalenderdatum in der Datenbank untergebracht
werden. In der Entity Bean wird hierfür der Datentyp Date
verwendet. Über
die Verwendung von Datumsangaben über Prepared Statements in EQL wird in
diesem Artikel berichtet. Die Syntax lautet:
Date <Name> (transient);
Eine automatische Aktualisierung bei Anlegen und Ändern dieses Attributtyps wie beim Timestamp existiert bei diesem Attributtypen nicht! Aus der Modellzeile
Date dateElement;
wird folgendes XHTML-Schnipsel generiert:
<tr>
<td class="mid">
<h:outputLabel for="dateElement" value="#{msg['info.startup.dateelement']}"/>
</td>
<td>
<h:inputText id="dateElement" label="#{msg['info.startup.dateelement']}" maxlength="10"
styleClass="date" value="#{infoHandler.startup.dateElement}">
<f:convertDateTime pattern="dd.MM.yyyy" type="date"/>
</h:inputText>
</td>
</tr>
Mit diesem Attributtyp kann eine Relation auf eine andere Entity Bean modelliert werden. Die Syntax lautet:
Entity <Typ> ([]) <Name>;
Es wird zwischen einer 1:1- und einer 1:n-Relation unterschieden, indem dem
Entity Typen das Symbol [] beigestellt wird. Die 1:1-Relation wird in einem
XHTML-Formular nicht dargestellt. Soll für diesen Fall eine Combobox zur
Auswahl dargestellt werden, muss das Attribut als Option
(s.u.) deklariert
werden. Für eine 1:n-Relation wird ein Button bereitgestellt, in der die
Liste der Entity Beans bearbeitet werden kann.
Soll in einer Entity Bean eine 1:1-Relation in einer Combobox ausgewählt
werden können, muss der entsprechende Attributtyp Option
lauten. Die Syntax
ist ähnlich dem Entity
-Attributtyp mit dem Unterschied, dass keine
1:n-Relation benutzt werden kann:
Option <Typ> <Name>;
Die als Option
referenzierte Entity Bean kann sowohl eine Enumeration sein,
als auch als editierbar gekennzeichnet sein. Im Modell wird aus der Zeile
Option UserInfo owner;
Der XHTML-Codeschnipsel:
<tr>
<td class="mid">
<h:outputLabel for="owner" value="#{msg['ordering.orderposition.owner']}"/>
</td>
<td>
<h:selectOneMenu converter="#{orderingHandler.userInfoConverter}" id="owner"
label="#{msg['ordering.orderposition.owner']}"
value="#{orderingHandler.orderPosition.owner}">
<f:selectItem itemLabel="#{msg.no_selection}" itemValue="[NULL]"/>
<f:selectItems itemLabel="#{owner.name}" itemValue="#{owner}"
value="#{orderingHandler.userInfoList}" var="owner"/>
</h:selectOneMenu>
</td>
</tr>
Der Action Handler stellt die Liste der möglichen Auswahlelemente bereit. In
diesem Falle muss die Klasse OrderingHandler die Methode getUserInfoList()
bereitstellen. In dem Beispiel darf die 1:1-Relation den Wert null
annehmen. Soll das nicht möglich sein, muss das <f:selectItem>
-Tag entfernt
werden. Zusätzlich werden noch an der Entity Bean die Methoden hashCode()
und equals()
überladen. Die von den generierten Action Handlern beinhalten
den dazu passenden Value Converter. In diesem Beispiel stellt die Klasse
OrderingHandler über die Methode getUserInfoConverter()
den Converter als
innere Klasse UserInfoConverter zur Verfügung.
Eine History ist eine spezielle Form der 1:n-Relation. Es können Einträge in diese Liste hinzugefügt werden, allerdings keine gelöscht werden. Dadurch kann ein zeitlicher Verlauf zu einer Entity Bean nachgehalten werden.
Achtung! In einer Entity Bean kann nur eine Historie verwendet werden. Auch in darunterliegenden Entity Beans darf die Historie nicht mehr verwendet werden. Besonderheiten der generierten Entity Beans
Die generierten Entity Beans bieten noch einige Eigenschaften, die den
Umgang mit den Entity Beans vereinfachen. So wird die toString()
-Methode
überladen, um alle Attribute der Entity Bean auf einfache Weise ausgeben zu
können. Das ist für Logging-Zwecke besonders sinnvoll.
Sämtliche Attribute und Methoden werden mit Javadoc-Kommentaren dokumentiert.
Wird in keinen Attribut das Schlüsselwort id
verwendet, wird automatisch
eine ID-Spalte generiert, die die IDs aus einer ID-Tabelle beziehen. Diese
Form der ID-Generierung ist die kompatibelste Variante zwischen den
Application Servern und den verwendeten Datenbanken. Jede Tabelle erhält in
der IDs-Tabelle eine eigene Zeile, in denen die ID-Ranges verwaltet werden.
private int id;
/**
* This getter returns the ID of this entity bean. The ID of this entity bean is automatically
* generated using the {@link TableGenerator} feature of the container.
*
* @return The ID of this entity bean.
*/
@Id
@TableGenerator(name = "StartupIDs", table = "IDs", pkColumnName = "id",
valueColumnName = "value", pkColumnValue = "Startup", initialValue = 1, allocationSize = 10)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "StartupIDs")
public int getId() {
return id;
}
/**
* This setter sets the ID of this entity bean. Generally its only used by the JEE6 container.
*
* @param id The ID to set.
*/
public void setId(final int id) {
this.id = id;
}
Den Entity Beans können durch weitere Schlüsselwörter noch zusätzliche Eigenschaften hinzugefügt werden. Zu diesem Zweck empfiehlt sich die Auflistung der Syntax:
entity <Name> (filterable) (cloneable) { <Attributes>+ } (persistence unit <Persistence-Unit>);
Wie die Attribute attributes aussehen müssen wurde ja schon beschrieben. Werden für Auswahllisten Optionen verwendet, sieht die Syntax leicht erweitert aus. Hier lautet die Syntax:
options <Name> (filterable) (cloneable) (editable { <Attributes>+ }) | ( { <Resource-Key>+ })
(persistence unit <Persistence-Unit>);
Hier werden im Wesentlichen zwei Varianten unterschieden:
Die nicht editierbaren Enumerations wurden weiter oben schon beispielhaft
beschrieben. Um in einer XHTML eine andere Entity Bean als 1:1-Relation in
einer Auswahlbox auswählen zu können, muss die Referenz auf diese Entity
Bean als Option
-Attribut benutzt werden. Sie unterscheiden sich ansonsten
nicht von den üblichen Entity Beans, die mit dem entity-Schlüsselwort
beschrieben werden.
Für alle Varianten gilt, dass man die erzeugte Entity Bean in eine andere Persistenz Unit hinzufügen kann. Es ist dabei zu beachten, dass alle rekursiv enthaltenen Relationen auf andere Entity Beans auch in derselben Persistenz Unit sein müssen. Der Eclipse-Editor quittiert das entsprechend mit einer Fehlermarkirung im Editor udn der Generatorlauf schlägt dementsprechend fehl. Lässt man die Definition auf die Persistenz Unit weg, wird automatisch die erste aufgelistete gewählt.
Speziell für die Suche von Entity Beans aus Ergebnislisten kann das
Filterable-Interface benutzt werden. Dieses Interface erfordert die
Implementierung der Methode public boolean filter(String pattern, Locale
locale)
. Wird das Schlüsselwort filterable
gesetzt, wird die Entity Bean in
eine abstrakte Klasse und eine konkrete Klasse generiert. Die konkrete
Klasse muss dann die besagte Methode filter()
implementieren. Da Suchen in
Java schneller vonstatten geht, als in der Datenbank, macht das java-seitige
Filtern bei relativ kleinen Datenmengen Sinn. Bei großen Datenmengen sollte
nach wie vor auf Seiten der Datenbank gefiltert werden. Ein weiterer
Vorteil der filter()
-Methode ist, dass auf transienten Attributen gesucht
werden kann. Um die Möglichkeit zu haben, sprachabhängig zu vergleichen
oder zu suchen, wird der Methode filter()
die entsprechende Locale
mitgegeben. Ein Beispiel für eine filter()
-Implementierung könnte so
aussehen:
@Override
public boolean filter(String pattern, Locale locale)
{
return getPosition().toLowerCase(locale).contains(pattern.toLowerCase(locale));
}
In diesem Beispiel wird abhängig von der übergebenen Locale nach einem
Suchmuster pattern in einer Bestellposition gesucht. Die Bestellposition
ist ein transientes Attribut. Bestellposition und Suchmuster werden gemäß
des verwendeten Locales in Kleinbuchstaben umgewandelt. Die Implementierung
der filter()
-Methode reicht natürlich nicht aus. Es muss natürlich über
eine Datenmenge gefiltert werden. Das geschieht sinnvollerweise in einem
Action Handler. Dort wird über das DAO eine Datenmenge bezogen, danach
gefiltert und an die übergeordnete XHTML-Seite übergeben. Eine solche
getList()
-Methode könnte folgendes Ausssehen haben:
/**
* This method returns a {@link List} of filtered {@link OrderPosition} of the last orders
* done with the selected {@link Distributor}.
*
* @return The {@link List} of filtered {@link OrderPosition} beans of the last orders done.
*/
public List<OrderPosition> lastOrderList()
{
List<OrderPosition> lastOrderList = dao.getLastOrderList();
FilteredList<OrderPosition> filtered = new FilteredList<OrderPosition>();
filtered.addAll(lastOrderList, pattern, getExternalContext().getRequestLocale());
return filtered;
}
Die Klasse FilteredList erweitert die Klasse ArrayList und ist in der
JEE-Util-Bibliothek enthalten. Sie kann nur Elemente aufnehmen, die das
Filterable-Interface implementieren. Die Klasse überlädt die Methoden
add()
und addAll()
, in denen das Filtern stattfindet. Es werden nur
Elemente der Liste hinzugefügt, die bei Aufruf der Elementmethode filter()
true zurückliefern. Das Locale wird aus dem Request ermittelt und
entspricht damit der im Browser eingestellten Sprache.
Unter Umständen kann es nötig sein, dass eine Entity Bean geklont wird. Ein
Beispiel hierfür ist, wenn Bestellpositionen kopiert werden sollen. In
diesem Fall muss das Schlüsselwort clonable
nach dem Namen der Entity Bean
mitgegeben werden. Dabei wird die Methode clone()
überladen. Damit es mit
der Datenbank keine Probleme gibt, wird das ID-Attribut gelöscht, um die
geklonte Entity Bean später neu persistieren zu können.
Neben der allgemeinen Beschreibung der Web Applikation und die Definition der Entity Beans im Modell gibt es als weiteren großen Block die Definition der Prozesse. Über Prozesse kann definiert werden, für welche Entity Beans Dialogmasken generiert werden sollen und bei Bedarf, welche Rollen darauf Zugriff haben darf. Die Syntax lautet:
process <Process-Name> (roles <Roles>+) { <Properties>* <Entities>+ } ;
Der Name eines Prozesses taucht in der URI auf. Die URL ist dann wie folgt
aufgebaut: http://<host>/<context>/<process-name>/<entity>.xhtml.
Mit dem Schlüsselwort roles
kann festgelegt werden, welche Rollen darauf
Zugriff haben. Im Prozess können beliebig viele Properties untergebracht
werden. Es handelt sich dabei um Werte, die im Application Server
konfiguriert werden und per JDNI referenziert werden. Die Auflistung der
Entity Beans führt dazu, dass die dafür nötigen XHTML-Dateien generiert
werden.
Für jeden Prozess wird ein eigener Action Handler und ein eigenes DAO generiert. Hier werden die benötigten Zugriffsmethoden auf die konfigurierten Entity Beans und evtl. die benötigten Value Converter generiert. Die Ableitungshierarchie ist dreistufig:
In den Action Handlern werden für die XHTML-Dateien die Zugriffsmethoden bereitgestellt, die wiederum auf die dazugehörigen DAOs zugreifen. Veranschaulicht wird das anhand folgenden Beispielmodells, dass teilweise schon bei den Entity Beans beschrieben wurde:
application "Eine Beispiel-Applikation" context "/beispiel" package org.jeegen.jee6.beispiel
development strict;
persistence unit"beispielDS" jndi "jdbc/exampleDS";
locale "de" default;
locale "el";
options AddressOption
{
"address.work",
"address.home"
}
entity Address
{
Text street;
Text plz;
Text location;
Option AddressOption addressOption;
}
entity Person
{
Text id login;
Text forename;
Text surename;
Text name transient;
Entity Address [] addresses;
}
process User
{
Text "ldap/baseDN" ref "java:global/ldap/baseDN";
Integer "build" ref "java:global/build";
Boolean default false "productive" ref "java:global/productive";
Person
}
Die Klasse AbstractUserHandler liefert für die Zugriffskontrolle die
Methode isAllowed()
, die in der XHTML verwendet werden kann. Da in diesem
Falle keine Zugriffsberechtigungen auf Rollen beschränkt wurden, liefert
diese Methode immer true zurück. Ferner wird in diese Klasse eine Referenz
auf das DAO UserDaoBean mit Namen dao vom Application Server injiziert:
@EJB
protected UserDaoBean dao;
Für die Entity Bean Person soll ein XHTML-Formular generiert werden. Da in dieser Entity Bean eine 1:n-Relation auf die Entity Bean Address enthalten ist, werden die entsprechenden Zugriffsmethoden dafür gleich mit generiert. Die Methoden für die Klasse Person lauten:
abstract public List<Person> getPersonList();
abstract String addPerson(final Person person);
abstract String changePerson(final Person person);
abstract String removePerson(final Person person);
abstract String savePerson();
abstract String backFromPerson();
Der Action Handler PersonHandler ist session scoped. Dadurch lässt sich der Zustand der Entity Bean Person speichern. Dadurch brauchen wir hierfür auch Zugriffsmethoden:
public Person getPerson();
public void setPerson(final Person person);
public boolean isPersonEmpty(final Person person);
Auf die Implementierung wurde der Übersichtlichkeit halber verzichtet. Die
Methode isPersonEmpty()
wurde nur generiert, weil in der Entity Bean Person
eine 1:n-Relation enthalten ist. Für die Klasse Address lauten die
Zugriffsmethoden:
public Address getAddress();
public void setAddress(final Address address);
public boolean isAddressEmpty(final Address address);
public List<Address> getAddressList() {
return dao.getAddressList(getPerson());
}
abstract String editAddress(final Person person);
abstract String changeAddress(final Address address);
abstract String removeAddress(final Address address);
abstract String saveAddress();
abstract String backFromAddress();
Die entsprechenden Methoden werden im konkreten Action Handler implementiert
und können nach Bedarf angepasst werden. Wäre im Modell für die
1:n-Relation eine History vermerkt, würde die Methode removeAddress()
fehlen.
Hinweis! Die Methoden- und Klassennamen werden aus den Namen der entsprechenden Attribute berechnet, nicht aus deren Typnamen.
Der Action Handler nimmt von den XHTML-Seiten die Events entgegen. Das können Klick-Events, Links, Submits oder Validations sein. Für den Zugriff auf die Datenbank ist aber eine weitere Komponente notwendig. Diese DAOs sind als Stateless Session Beans implementiert. In das DAO werden Zugriffsmethoden auf die Datenbank generiert. Da jeder Prozess seinen eigenen Action Handler sowie sein eigenes DAO hat, kann das zum Action Handler passende DAO direkt in den Action Handler injiziert werden.
public void addPerson(final Person person);
public Person updatePerson(final Person person);
public void deletePerson(Person person);
public List<Person> getPersonList();
Da die Entity Bean Person eine 1:n-Relation enthält, sind auch entsprechende Methoden für das Hinzufügen und Entfernen aus der Relation vorhanden:
public Address addToPerson(Person person, final Address address);
public Person deleteFromPerson(Address address);
public List<Address> getAddressList(final Person person);
Die Methoden für die Entity Bean Address wird auch der Vollständigkeit halber aufgelistet:
public void addAddress(final Address address);
public Address updateAddress(final Address address);
public void deleteAddress(Address address);
public List<Address> getAddressList();
Werden Rollen verwendet, muss auch eine Security Domain konfiguriert werden. Die entsprechenden Zugriffsrechte werden in die web.xml reingeneriert. Je nach verwendetem Application Server müssen noch weitere Deskriptoren mit weiteren Roll Mappings konfiguriert werden. Da sich diese Deskriptoren nicht gegenseitig beeinflussen, werden sie schon prophylaktisch vorgeneriert.
Die Action Handler enthalten Methoden, um programmatisch die
Rollenzugehörigkeit abzufragen. Das kann im XHTML dazu verwendet werden, um
mittels des <ui:fragment>
-Tags Blöcke nur bei Berechtigung sichbar zu
machen. Da ist zum Einen die Methode boolean isAllowed()
. Diese Methode
zeigt an, dass der Zugriff für den eingeloggten Benutzer erlaubt ist. Ein
Beispiel dafür lautet:
<ui:fragment rendered="#{userHandler.allowed}">
<!-- nur bei Berechtigung sichtbar -->
</ui:fragment>
Sollte keine Rolle für den Prozess definiert worden sein, liefert diese Methode immer true zurück. Man kann sich also auf die Existenz dieser Methode verlassen.
Die andere Methode heißt boolean isLoggedIn()
. Mit dieser Methode wird
überprüft, ob ein Benutzer überhaupt eingeloggt ist.
Mittels Properties können auf Daten, die im Application Server konfiguriert sind mittels JNDI zugegriffen werden. So ist es möglich ein und dieselbe Applikation ohne Neubauen in unterschiedliche Laufzeitumgebungen zu bringen und dabei das Verhalten zu beeinflussen. Wenn beispielsweise Ein Produktiv- und ein Staging-System vorhanden ist, die jeweils über WebService ein anders Produktiv- und Staging-System aufrufen wollen, kann über diese Properties die dazu passende URL konfiguriert werden und die Applikation benutzt den zu ihrer Umgebung korrekten WebService. Als Datentypen können für Properties verwendet werden:
Text (java.lang.String)
Integer (int)
Boolean (boolean)
Freier Typ
Die Properties werden ihren Datentypen entsprechend in das abstrakte DAO generiert.
Mit Text-Properties kann man Texte wie z.B. URLs in einem Application Server konfigurieren und per JNDI referenzieren. Die Syntax im Modell lautet:
Text (default <Value>) <Jndi> (ref <Original-Jndi>);
Aus dem Beispiel oben wird im DAO folgender Eintrag generiert:
@Resource(mappedName = "build")
private int build;
Mit Boolean-Properties kann man Texte wie z.B. URLs in einem Application Server konfigurieren und per JNDI referenzieren. Die Syntax im Modell lautet:
Boolean (default <true|false) <Jndi> (ref <Original-Jndi>);
Aus dem Beispiel oben wird im DAO folgender Eintrag generiert:
@Resource(mappedName = "build")
private boolean build = false;
In diesem Beispiel wurde ein Default mit angegeben, der mit in den Quellcode generiert wird.
Es ist nicht nur möglich, einfache Datentypen über JNDI zu referenzieren. Bei der Syntax muss voll qualifiziert die Klasse angegeben werden:
Type <class-name> <Jndi> (ref <Original-Jndi);
Aus dem Beispiel oben wird im DAO folgender Eintrag generiert:
@Resource(mappedName="ldap/organization")
private DirContext ldap;
Obwohl die Syntax des Java EE 6-Generators sich nicht von dem des Java EE 7-Generators unterscheidet, gibt es doch zum Teil erhebliche Unterschiede im Generat. Zuerst muss das Projekt an die veränderte Laufzeitumgebung angepasst werden. Dazu sollte die Java Version von Java 6 auf mindestens Java 7 gebracht werden. Als Nächstes sollte man die jee6util.jar durch die jee7util.jar ersetzen und dabei den Build Path entsprechend anpassen. Ferner sollten die Plugin-Abhängigkeiten im Eclipse von org.jeegen.jee6* auf org.jeegen.jee7* angepasst werden. Dazu müssen auch in der build.xml alle jee6-Referenzen durch jee7-Referenzen ersetzt werden.
Es sollte in der build.xml darauf geachtet werden, dass ggf. ein anderer Application Server verwendet wird. Dazu muss dann der entsprechende Pfad für das Deployment angepasst werden.
Als letzte Änderung vor der Anpassung des Quellcodes muss der Generator von
JEE 6 auf JEE 7 umgestellt werden. In der entsprechenden Datei
src/main/java/Generator.mwe2 wird das Package der Generatorkomponente auf
@org.jeegen.jee7.generator.DslGeneratorMWE
angepasst.
Der Logger wird unter Java EE 7 injiziert und nicht mehr als statische Variable angelegt. Der entsprechende Code-Abschnitt sieht folgendermaßen aus:
@Inject
private Logger log;
Der Logger wird aus dem Paket java.util.logging
entnommen. Somit ergeben
sich auch andere Logging Level:
Bedeutung | Log Level JEE 6 | Log Level JEE7 |
---|---|---|
Exczessives Debugging | TRACE | FINER |
Konfiguration | CONFIG | |
Debugging | DEBUG | FINE |
Information | INFO | INFO |
Warnung | WARN | WARNING |
Fehler | ERROR | SEVERE |
Die in den JEE7-Utils verwendeten Logging-Methoden behalten ihre Methodennamen, haben allerdings für den Logger eine angepasste Signatur.
Hinweis! Es ist nicht mehr möglich, mit dem Logger direkt eine Exception zu übergeben. Um einen Stack Trace auszugeben, muss folgender Code verwendet werden:
import java.util.logging.Level;
import java.util.logging.Logger;
...
catch (Exception e)
{
log.log(Level.SEVERE, e.getMessage(), e);
}
Die Action Handler sind jetzt keine Managed Beans im Sinne von JSF, sondern CDI-Beans. Das hat den Vorteil, dass diese auch im transaktionalen Kontext laufen können. Somit ändern sich auch die Annotationen auf:
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import org.jeegen.jee7.util.LogUtil;
import org.jeegen.jee7.util.Profiled;
@Named
@SessionScoped
@Transactional(value = TxType.REQUIRED)
@Profiled
public class XyzHandler extends AbstractXyzHandler
{
...
}
Die Annotation @Profiled
ergänzt einen Interceptor, wie er schon bei den
EJBs des JEE6-Generators Verwendung fand.
Hinweis! Der im JEE6-Generator schon erzeugte ApplicationController
wird im JEE7-Generator ebenfalls noch als Application Scoped Action
Handler generiert, da es unter CDI keine Entsprechung für einen “eager
started” Bean gibt.
Die Data Access Objects (kurz DAOs) ändern sich nur wenig, außer dass für den Profile Interceptor die schon erwähnte Annotation Anwendung findet:
@Stateless
@Profiled
public class XyzDaoBean extends AbstractXyzDaoBean
{
...
}
Der File Upload wird neuerdings vom JEE7-Generator berücksichtigt, denn es gibt unter Java EE 7 eine nicht unbedeutende Vereinfachung. Es muss erstens keine externe Library wie z.B. die Apache Commons Fileupload verwendet werden und ferner muss kein sog. Request Wrapper implementiert werden. Beides entfällt ersatzlos.
Das XHTML des Formulars muss nur dahingehend angepasst werden, dass das neue
JSF-Tag <h:inputFile ... />
verwendet werden muss. Dieses muss um das
value-Tag ergänzt werden, das auf ein entsprechendes Property im Action
Handler vom Typ javax.servlet.http.Part
verweist.
Beim Submit des Formulars muss im Action Handler aus dem InputStream
des
javax.servlet.http.Part
der entsprechende Datenstrom extrahiert und für
eigene Zwecke konvertiert werden. Wenn es sich z.B. um einen Bild-Upload
handelt, kann folgender Code-Abschnitt verwendet werden:
try
{
// image ist vom Typ javax.servlet.http.Part
if ((image != null) && (image.getSize() > 0))
{
// Konvertierung in Blob
try(final DataInputStream dis = new DataInputStream(is))
{
final byte buffer[] = new byte[len];
dis.readFully(buffer);
user.setImage(buffer);
}
}
}
catch (IOException e)
{
log.log(Level.SEVERE, e.getMessage(), e);
}
Hinweis! Die DSL-Syntax wurde für Clob
und
Blob
dahingehend verändert, dass daraus direkt der Upload
generiert werden kann. Da meist der Handler schon mit Business Logik
angereichert wurde, muss dieser nach wie vor von Hand erweitert werden.
Die JEE-Generatoren erzeugen den Code mittels des sog. Generation Gap Patterns. Alle bisherigen Punkte müssen auf bereits existierendem Code angewendet werden, damit sie dem Java EE 6-Generat rechnung tragen. Alle folgenden Änderungen werden unabhängig vom bestehenden Code bei jedem Generatorlauf neu generiert und werden hier nur der Vollständigkeit halber aufgelistet.
beans.xml
Die beans.xml wird generiert und in ihr der bean-discovery-mode="all"
eingestellt. Nur so kann z.B. der Profile Interceptor aus den JEE7-Utils
gefunden und verwendet werden.