MOSMANN
PRAXISBUCH WICKET
michael MOSMANN
PRAXISBUCH
WICKET PROFESSIONELLE WEB-2.0ANWENDUNGEN ENTWICKELN
Mosmann Praxisbuch Wicket
v
Bleiben Sie einfach auf dem Laufenden: www.hanser.de/newsletter Sofort anmelden und Monat für Monat die neuesten Infos und Updates erhalten.
Michael Mosmann
Praxisbuch Wicket Professionelle Web-2.0-Anwendungen entwickeln
Michael Mosmann, Lübeck Kontakt:
[email protected] Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autor und Verlag übernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen – oder Teilen davon – entsteht. Ebenso übernehmen Autor und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Bibliografische Information der Deutschen Nationalbibliothek: Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
Dieses Werk ist urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) – auch nicht für Zwecke der Unterrichtsgestaltung – reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.
© 2009 Carl Hanser Verlag München, www.hanser.de Lektorat: Margarete Metzger Copy editing: Jürgen Dubau, Freiburg Herstellung: Irene Weilhart Umschlagdesign: Marc Müller-Bremer, www.rebranding.de, München Umschlagrealisation: Stephan Rönigk Datenbelichtung, Druck und Bindung: Kösel, Krugzell Ausstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702 Printed in Germany ISBN 978-3-446-41909-4
für meine Eltern
0
2
Inhalt Vorwort..............................................................................................................................XIII 1 1.1
1.2
1.3
2 2.1 2.2
2.3
Einleitung ................................................................................................................. 1 Warum Wicket? .......................................................................................................................3 1.1.1 Einfach, Konsistent, Offensichtlich ............................................................................4 1.1.2 Wiederverwendbarkeit ...............................................................................................4 1.1.3 Sauber getrennt...........................................................................................................5 1.1.4 Sicher..........................................................................................................................5 1.1.5 Effizient und skalierbar ..............................................................................................6 1.1.6 Komplett.....................................................................................................................6 1.1.7 Eine gute Wahl ...........................................................................................................6 Vorbereitung und Installation...................................................................................................7 1.2.1 Java, Maven und Eclipse ............................................................................................7 1.2.2 Versionskontrolle mit Subversion ..............................................................................7 Grundlagen einer Webanwendung ...........................................................................................8 1.3.1 Anwendungsschichten................................................................................................8 1.3.2 Verzeichnis und Paketstruktur..................................................................................11 1.3.3 Unit-Tests .................................................................................................................12 Aufsetzen der Teilprojekte .................................................................................... 15 Nomenklatur der Teilprojekte ................................................................................................15 Aufsetzen der Teilprojekte .....................................................................................................16 2.2.1 Projektbasis ParentPom ............................................................................................16 2.2.2 Teilprojekt Base .......................................................................................................20 2.2.3 Teilprojekte Datenbankkonfiguration.......................................................................20 2.2.4 Teilprojekt Persistenz ...............................................................................................22 2.2.5 Teilprojekt Applikationsschicht................................................................................24 2.2.6 Teilprojekt Webapp ..................................................................................................24 2.2.7 Teilprojekt ParentPom – Abschluss..........................................................................26 Erstellen von Eclipse-Projektdateien......................................................................................27
VII
Inhalt 3 3.1 3.2
3.3
3.4 3.5
4 4.1 4.2
4.3
4.4
5 5.1 5.2 5.3 5.4
VIII
Mit Leben füllen ..................................................................................................... 29 Konfiguration mit Spring ....................................................................................................... 29 Datenbankkonfiguration......................................................................................................... 30 3.2.1 Teilprojekt dbconfig................................................................................................. 30 3.2.2 Teilprojekt dbconfig-test .......................................................................................... 31 3.2.3 Teilprojekt dbconfig-schema-update........................................................................ 31 3.2.4 Schemagenerierung mit Hibernate ........................................................................... 32 Persistenz ............................................................................................................................... 33 3.3.1 Datenbankzugriff – Allgemeine Schnittstellendefinition ......................................... 33 3.3.2 Datenbankzugriff – Hilfsklassen .............................................................................. 34 3.3.3 Datenbankzugriff – User .......................................................................................... 35 3.3.4 Datenbankzugriff – Konfiguration ........................................................................... 37 3.3.5 Persistenz-Tests........................................................................................................ 38 3.3.6 Schema-Update ........................................................................................................ 40 Anwendungsschicht ............................................................................................................... 41 Präsentationsschicht............................................................................................................... 41 3.5.1 Hilfsklasse für Maven-Projekte................................................................................ 41 3.5.2 Wicket Web Application .......................................................................................... 42 3.5.3 Servlet-Konfiguration............................................................................................... 44 3.5.4 Spring-Konfiguration ............................................................................................... 46 3.5.5 Start der Anwendung................................................................................................ 46 Die Wicket-Architektur .......................................................................................... 49 Wicket und das HTTP-Protokoll............................................................................................ 49 Struktur .................................................................................................................................. 49 4.2.1 WebApplication ....................................................................................................... 50 4.2.2 Session ..................................................................................................................... 50 4.2.3 PageMap .................................................................................................................. 50 4.2.4 Page.......................................................................................................................... 50 4.2.5 PageStore ................................................................................................................. 51 4.2.6 Component ............................................................................................................... 51 Request-Behandlung .............................................................................................................. 51 4.3.1 Komponentenphasen ................................................................................................ 52 4.3.2 Nebenläufigkeit – Threads ....................................................................................... 52 Komponenten, Modelle, Markup ........................................................................................... 53 4.4.1 Komponenten ........................................................................................................... 53 4.4.2 Modelle .................................................................................................................... 53 4.4.3 Markup ..................................................................................................................... 53 Modelle ................................................................................................................... 55 Konverter ............................................................................................................................... 55 Einfache Modelle ................................................................................................................... 57 5.2.1 Modelle verändern ................................................................................................... 58 Modell-Hilfsklassen............................................................................................................... 60 Modelle und Serialisierung .................................................................................................... 61 5.4.1 DetachableModel – Dynamische Modelldaten......................................................... 61
Inhalt
5.5
5.6
6 6.1
6.2
6.3 6.4
6.5
7 7.1
7.2
5.4.2 Kaskadierung von Modellen.....................................................................................62 5.4.3 Automatische Kaskadierung von Modellen..............................................................65 5.4.4 Datenbankzugriffsmodelle........................................................................................66 Komplexe Modellklassen.......................................................................................................69 5.5.1 Zugriff auf Bean-Properties......................................................................................69 5.5.2 Die Klasse PropertyModel........................................................................................72 5.5.3 CompoundPropertyModel ........................................................................................74 Ausgelagerte Informationen...................................................................................................76 5.6.1 Einfacher Zugriff auf Ressourcen.............................................................................76 5.6.2 ResourceModel.........................................................................................................76 5.6.3 StringResourceModel ...............................................................................................78 Komponenten......................................................................................................... 81 Basisklasse Component..........................................................................................................81 6.1.1 Komponentenbaum ..................................................................................................81 6.1.2 Darstellungsphasen...................................................................................................83 6.1.3 Page, Session und Application .................................................................................84 6.1.4 Komponentenpfad ....................................................................................................84 6.1.5 Modelle ....................................................................................................................84 6.1.6 Feedback ..................................................................................................................85 Grundlagen der Vererbung.....................................................................................................85 6.2.1 Eine Seite mit eigenen Komponenten.......................................................................85 6.2.2 Vererbung für Fortgeschrittene................................................................................91 Style, Locale und Variation....................................................................................................94 6.3.1 Markup-Variationen .................................................................................................94 Sichtbarkeit ............................................................................................................................99 6.4.1 wicket:enclosure.....................................................................................................100 6.4.2 Empfehlung zur Anwendung..................................................................................101 Ajax......................................................................................................................................102 6.5.1 Ajax-Events............................................................................................................103 6.5.2 Einfache Event-Behandlung ...................................................................................104 6.5.3 Automatische Event-Behandlung ...........................................................................105 Basiskomponenten.............................................................................................. 109 Gruppierende Komponenten ................................................................................................109 7.1.1 Seiten......................................................................................................................109 7.1.2 Panel.......................................................................................................................117 7.1.3 Fragment ................................................................................................................119 7.1.4 Border.....................................................................................................................120 7.1.5 ComponentBorder ..................................................................................................125 7.1.6 WebMarkupContainer ............................................................................................126 Inhaltselemente ....................................................................................................................127 7.2.1 Label und MultiLineLabel......................................................................................127 7.2.2 Lokaler Konverter ..................................................................................................129 7.2.3 XML.......................................................................................................................130 7.2.4 Das wicket:message-Tag ........................................................................................131
IX
Inhalt
7.3
7.4
8 8.1
8.2
9 9.1 9.2 9.3 9.4
9.5
9.6 9.7 9.8 9.9
X
7.2.5 Image...................................................................................................................... 132 Links .................................................................................................................................... 137 7.3.1 Von A nach B......................................................................................................... 137 7.3.2 Ajax und Links....................................................................................................... 138 7.3.3 Link-Tricks............................................................................................................. 140 7.3.4 Externe Links ......................................................................................................... 141 7.3.5 Popups.................................................................................................................... 141 7.3.6 ResourceLink ......................................................................................................... 143 7.3.7 Formularlinks ......................................................................................................... 144 Behavior............................................................................................................................... 144 7.4.1 Darf es etwas JavaScript sein? ............................................................................... 144 7.4.2 Attribute anpassen .................................................................................................. 145 7.4.3 Attribute erweitern ................................................................................................. 147 7.4.4 Ajax und Formulare ............................................................................................... 148 Listen und Tabellen ............................................................................................. 149 Darstellung von Listen......................................................................................................... 149 8.1.1 RepeatingView....................................................................................................... 149 8.1.2 RefreshingView...................................................................................................... 150 8.1.3 ListView................................................................................................................. 152 8.1.4 PropertyListView ................................................................................................... 153 8.1.5 ColumnListView .................................................................................................... 154 DataProvider ........................................................................................................................ 156 8.2.1 DataView ............................................................................................................... 156 8.2.2 GridView................................................................................................................ 158 8.2.3 DataGridView ........................................................................................................ 159 8.2.4 DataTable ............................................................................................................... 161 8.2.5 DefaultDataTable ................................................................................................... 162 Formulare ............................................................................................................. 169 Voraussetzungen .................................................................................................................. 169 Feedback .............................................................................................................................. 170 Basisklasse für alle Beispiele ............................................................................................... 171 Formulare absenden ............................................................................................................. 172 9.4.1 Absenden mit Submit-Button ................................................................................. 172 9.4.2 Button-Komponente ............................................................................................... 173 9.4.3 Submit per Ajax ..................................................................................................... 174 9.4.4 POST und GET ...................................................................................................... 175 Textfelder............................................................................................................................. 176 9.5.1 Typangabe.............................................................................................................. 178 9.5.2 Automatische Typermittlung.................................................................................. 179 Label .................................................................................................................................... 181 CheckBox ............................................................................................................................ 182 RadioButton......................................................................................................................... 185 Auswahlfelder...................................................................................................................... 186 9.9.1 Select...................................................................................................................... 186
Inhalt
9.10
9.11
9.12
9.13
9.14 9.15
9.16 9.17 10 10.1
9.9.2 DropDownChoice...................................................................................................188 9.9.3 ListMultipleChoice.................................................................................................190 Dateien hochladen................................................................................................................192 9.10.1 FileUpload..............................................................................................................192 9.10.2 MultiFileUpload .....................................................................................................194 Gültigkeitsprüfung ...............................................................................................................195 9.11.1 StringValidator .......................................................................................................196 9.11.2 Minimum und Maximum .......................................................................................197 9.11.3 E-Mail ....................................................................................................................197 9.11.4 URL........................................................................................................................199 9.11.5 Eigene Validatoren .................................................................................................199 FormValidator......................................................................................................................201 9.12.1 Passwortprüfung.....................................................................................................201 9.12.2 Eigene Prüfung .......................................................................................................203 Ajax......................................................................................................................................205 9.13.1 AjaxFormSubmitBehavior......................................................................................205 9.13.2 AjaxFormValidatingBehavior ................................................................................207 9.13.3 AjaxComponentUpdatingBehavior ........................................................................207 9.13.4 OnChangeBehavior ................................................................................................209 9.13.5 AutoCompleteTextField .........................................................................................210 AjaxEditableLabel ...............................................................................................................212 Erweitertes Feedback ...........................................................................................................214 9.15.1 Feedback zum Formular .........................................................................................214 9.15.2 Feedback für die Komponente................................................................................215 9.15.3 Feedback als Rahmen .............................................................................................216 9.15.4 Feedback als Indikator............................................................................................217 9.15.5 Feedback per CSS ..................................................................................................218 Generierte Formulare ...........................................................................................................220 Verschachtelte Formulare ....................................................................................................222
10.2 10.3
Sessions und Security ........................................................................................ 225 Einfache Variante.................................................................................................................225 10.1.1 Eine eigene Session-Klasse ....................................................................................225 10.1.2 Geschützte Seiten ...................................................................................................226 10.1.3 Strategie..................................................................................................................226 10.1.4 WebApplication......................................................................................................227 10.1.5 Seiten......................................................................................................................228 Marker an Komponenten......................................................................................................231 Elemente ausblenden............................................................................................................233
11 11.1 11.2 11.3 11.4
Wicket in der Praxis............................................................................................. 235 Die Integration von Spring...................................................................................................235 Navigation............................................................................................................................237 CSS einbinden......................................................................................................................244 Eigene Basiskomponenten ...................................................................................................250
XI
Inhalt 11.5
11.6
11.7
11.8 11.9
12 12.1
Komponententausch............................................................................................................. 253 11.5.1 AjaxFallbackConfirmLink ..................................................................................... 254 11.5.2 Wizard.................................................................................................................... 256 Suchmaschinenoptimierung ................................................................................................. 258 11.6.1 Pfad für BookmarkablePages ................................................................................. 258 11.6.2 SessionTimeoutPage .............................................................................................. 262 11.6.3 SEO-Links.............................................................................................................. 264 11.6.4 Servlet-Filter .......................................................................................................... 268 11.6.5 Tracking mit Google Analytics .............................................................................. 271 Ressourcen........................................................................................................................... 274 11.7.1 Dynamisch erzeugte Grafiken ................................................................................ 274 11.7.2 Automatisch generierte Thumbnails....................................................................... 276 11.7.3 Download durch Formular ..................................................................................... 277 11.7.4 Shared Resources ................................................................................................... 278 11.7.5 RSS-Feed ............................................................................................................... 280 Links auf Seiten und Ressourcen ......................................................................................... 282 Optimierungen ..................................................................................................................... 284 11.9.1 Applikation............................................................................................................. 284 11.9.2 Konverter ............................................................................................................... 284 11.9.3 Debug..................................................................................................................... 284 11.9.4 Ressource ............................................................................................................... 285
12.2
Fehlersuche.......................................................................................................... 287 Häufige Fehlerquellen.......................................................................................................... 287 12.1.1 Komponenten fehlen .............................................................................................. 287 12.1.2 Komponente ist bereits vorhanden ......................................................................... 287 12.1.3 Ajax funktioniert nicht ........................................................................................... 288 Unit-Tests ............................................................................................................................ 288
13
Anfang oder Ende?.............................................................................................. 293
Register............................................................................................................................ 295
XII
Vorwort Ich beschäftige mich seit meiner frühen Jugend mit der Softwareentwicklung. Angefangen hat das auf einem ZX-Spektrum-Klon in Basic. Im Laufe der Jahre kamen so unterschiedliche Programmiersprachen und Betriebssysteme zusammen. Der Einstieg in die objektorientierte Programmierung kam mit C++. Die ersten Versuche, Anwendungen mit Benutzeroberflächen zu schreiben, habe ich auf einem Amiga unternommen. Die Anwendungen waren klein und der Nutzerkreis beschränkt. Im Juli 1999 fing ich bei Dr. Klein und Co. an und startete mit ersten Anwendungen, die im Internet zur Verfügung gestellt wurden. Das waren Java-Applets, die z.B. Kreditberechnungen ermöglichten. Nicht viel später entstanden die ersten Webanwendungen, die damals als Servlets und mit JavaServer Pages realisiert wurden. Die Anwendungen wurden komplexer und dieses Entwicklungsmodell stieß zunehmend an seine Grenzen. Mangels guter Alternativen entstanden so im Laufe der Jahre einige selbst entwickelte Anwendungsframeworks. Diese linderten zwar die Probleme etwas, beseitigten das grundlegende Problem aber nicht. Die Suche nach Alternativen ging weiter und so landete ich im März 2008 auf der Webseite von Wicket. Ich hatte mir bis dahin so einiges angesehen: GWT, Rails, Thinwire um nur einige zu nennen. Im Gegensatz zu diesen überzeugte mich Wicket von Anfang an. Das ist nun mehr als ein Jahr her und für mich hat sich durch Wicket einiges grundlegend verändert. Webanwendungen zu schreiben ist so einfach geworden, dass man bei neuen Features nicht darüber nachdenkt, wie man sie realisiert, sondern im ersten Moment nur, ob man sie realisiert. Wusste man bei den alten Anwendungen genau, welche Problemstellungen „Bauchschmerzen“ verursachten, sind diese Anforderungen mit Wicket keine Herausforderung und das Entwickeln mit Wicket macht einfach nur Spaß. Die Arbeit kommt vor den Vergnügen. Doch der Spaß sollte nicht lange auf sich warten lassen. In diesem Sinne: Let’s have some fun.
XIII
Vorwort
Danksagung Es gab eine Menge Menschen, die mich direkt und indirekt beim Schreiben dieses Buches unterstützt haben. Daher ist es unmöglich, nicht aus Versehen, den einen oder anderen zu vergessen. Die Reihenfolge sollte auf keinen Fall als Wertung missverstanden werden ☺. Ich danke natürlich meiner Frau, weil sie mich immer wieder angespornt und mir den Rücken frei gehalten hat. Ich danke dem Carl Hanser Verlag und da ganz besonders Frau Metzger, die immer sehr viel geduldiger und ruhiger war als ich. Vermutlich hätte ich sonst bereits das Handtuch geworfen. Dank gebührt auch Dirk Diebel, der einen Blick auf Auszüge dieses Buches werfen durfte und mir bestätigte, dass das nicht ganz unverständlich ist, was ich da zusammengeschrieben habe. Ohne Stephan Lamprecht wäre dieses Buch nie entstanden, weil er nicht nur den Kontakt zum Verlag hergestellt hat, sondern auch als erfahrender Autor immer wieder mit einem „Das ist ganz normal“ meine Seele gestreichelt hat. Vielen Dank an alle Wicket-Entwickler und die Wicket-Community für ein großartiges Framework und die großartige Unterstützung bei Problemen und Fragen. Und zum Schluss möchte ich noch allen danken, die in dieser Auflistung nicht vorkommen, aber mindestens das Gefühl haben dürfen, dass sie mir während dieser Zeit das Leben leichter gemacht haben. Michael Mosmann, August 2009
XIV
1 1 Einleitung Ich bin im Frühjahr 2008 auf Wicket aufmerksam geworden. Doch da hatte Wicket bereits eine längere Entwicklung hinter sich, denn es wird bereits seit 2005 entwickelt. 2006 wurde Wicket ein offizielles Apache-Projekt, und im Januar 2007 wurde dann das erste Release als Apache-Projekt veröffentlicht. Seit April 2008 setze ich Wicket mit viel Freude in Projekten ein. Die Idee zu diesem Buch entstand, nachdem ich ein paar Monate mit Wicket gearbeitet hatte und die Begeisterung für das Framework immer stärker zunahm. Ich hatte zu dem Zeitpunkt bereits einiges über Wicket gelesen, vermisste aber oft den Praxisbezug der Beispiele und Lösungen. Viele Wege führen nach Rom, aber nicht jeder ist kurz oder schnell. Und so habe ich am Anfang auch das eine oder andere Mal eine falsche Abbiegung genommen, die sich zwar nicht als Sackgasse entpuppte, aber teilweise einen gehörigen Umweg darstellte. Dieses Buch soll als Straßenkarte dienen, damit man sich auf den gut ausgebauten Straßen, die Wicket bietet, zurechtfindet. Wer sollte das Buch lesen? Wer Webanwendungen entwickelt oder entwickeln möchte und dabei auf Java als Programmiersprache setzt, wird nach der Lektüre dieses Buches sehr schnell Webanwendungen entwickeln können. Dabei werden alle Aspekte beleuchtet, die für das Erstellen einer Webanwendung wichtig und notwendig sind. Entwickler, die bereits Wicket einsetzen, profitieren von den praxisnahen Beispielen und bewährten Lösungen. Konventionen In diesem Buch gelten folgende Konventionen: Programmcode, Verzeichnisse und Dateinamen werden innerhalb des normalen Textes als code dargestellt. Quelltexte werden ohne besondere Überschrift als Codeblock dargestellt: public void javacode()
Bei der Auszeichnung mit Überschrift wird ein Dateiname angegeben:
1
1 Einleitung Listing 1.1 Beispiel.java public void andererCode()
Für HTML-Quelltexte, Property- und Markup-Dateien wird dieselbe Formatierung wie für Java-Quelltexte benutzt. Wenn durch die begrenzte Breite des Buches Text umbrochen werden muss und das an dieser Stelle nicht möglich ist, wird das unwillkürliche Zeilenende durch ein Leerzeichen und „\“ angegeben. Nur wenn das „\“-Zeichen an letzter Stelle steht, ist ein unwillkürlicher Zeilenumbruch gemeint. Die nächste Zeile wird dann um zwei Leerzeichen gegenüber dem Zeilenanfang eingerückt: Text=Der Text ist zu lang, als dass er auf eine Zeile passen \ würde, und beginnt auf der neuen Zeile mit zwei Leerzeichen, \ die ebenfalls ignoriert werden müssen. Ein \-Zeichen mittendrin ist in Ordnung.
Da sich viele Dinge immer wiederholen, wird im Laufe des Buches das eine oder andere gekürzt. Damit man erkennen kann, ob gekürzt wurde, erscheinen an der entsprechenden Stelle drei Punkte: ...
Wenn solche Kürzungen vorgenommen wurden, ist es ebenso wahrscheinlich, dass nur leichte Anpassungen an bereits bestehenden Quelltexten vorgenommen wurden. Diese werden der besseren Übersichtlichkeit halber entsprechend hervorgehoben: ... das stand hier schon doch das ist neu ...
Kommandos, die in der Konsole eingeben werden müssen, sind von Natur aus nie mehrzeilig. Es gelten daher dieselben Regeln zum Umbruch wie bei Quelltexten. Allerdings beginnen Kommandos immer mit einem Dollarzeichen, es sei denn, sie kommen innerhalb des Textes vor. Dann ergibt sich die Bedeutung aus dem Text: $ kommando
Java Alle Felder einer Klasse starten mit einem Unterstrich. Wenn eine Klasse als Bean benutzt wird, dann erfolgt der Zugriff auf das Feld durch eine passende set- und get-Methode. Listing 1.2 BeispielBean.java public class BeispielBean { String _titel; public String getTitel() { return _titel; } public void setTitel(String titel) { _titel=titel; } }
2
1.1 Warum Wicket? Im Folgenden wird diese ausführliche Schreibweise gekürzt. Dabei werden alle Felder mit den entsprechenden Typangaben aufgeführt und exemplarisch die Methodennamen für die ersten Felder angegeben. Listing 1.3 BeispielBean.java (gekürzt) public class BeispielBean { String _titel; getTitel(),setTitel(),... }
Importanweisungen werden nur aufgeführt, wenn die zu importierende Klasse nicht aus dem Beispielprojekt oder aus dem Wicket-Framework stammt und sich auch nicht in den Java-Standardbibliotheken befindet (z.B. import java.io.Serializable;). Die Kürzung wird durch ... gekennzeichnet. Markup Für die Darstellung von Komponenten nutzt Wicket einen Template-Mechanismus. Dabei kann einer Komponente eine HTML-Datei zugeordnet werden, in der alle nötigen Strukturen für die Darstellung enthalten sind. Diese Datei wird im folgenden Markup genannt. Version Das Buch bezieht sich auf die Wicket-Version 1.4, die mit dem Erscheinen dieses Buches in einer finalen Version verfügbar sein wird. Die Beispiele können begrenzt auch auf Wicket 1.3 übertragen werden. Wenn man nicht durch andere Abhängigkeiten gezwungen ist, Wicket noch in einer alten Version einzusetzen, sollte man spätestens jetzt den Schritt wagen und auf Wicket in der Version 1.4 migrieren. Online Da die Entwicklung von Wicket immer weiter geht, veröffentliche ich auf der Seite http://www.wicket-praxis.de/blog/ fortlaufend Erfahrungen und Tipps aus der praktischen Arbeit. Wenn Sie mit mir in Kontakt treten wollen, genügt eine E-Mail an:
[email protected].
1.1
Warum Wicket? Für das Entwickeln von Webanwendungen stehen eine ganze Reihe von Frameworks und Technologien bereit. Die Auswahl fällt schwer, da man die meisten Anforderungen mit einer ganzen Reihe von Technologien umsetzen könnte. Wenn man dann die zu verwendende Programmiersprache auf Java eingrenzt, bleiben trotzdem noch einige Frameworks übrig, die um die Gunst der Programmierer wetteifern.
3
1 Einleitung Aus der umfangreichen Liste der Möglichkeiten habe ich Wicket ausgewählt. Dabei hat Wicket zunächst mit recht einfachen Mitteln mein Interesse geweckt: Es gibt eine sehr einfache und schnelle Möglichkeit, ein Testprojekt aufzusetzen. Dieses kann sofort gestartet werden und zeigt ein minimales Grundgerüst einer WicketAnwendung. Die bestehende Anwendung kann einfach verändert werden, was zu einem sehr schnellen Erfolgserlebnis führt. Die Lernkurve für die ersten Gehversuche ist sehr gering. Nachdem ich auf diese Weise sehr schnell einen ersten Eindruck gewinnen konnte, habe ich mich eingehender mit Wicket beschäftigt. Nach einer kurzen Einarbeitungsphase und einigen kleinen Beispielanwendungen war ich mir sicher, dass ich Wicket für meine zukünftigen Webprojekte einsetzen werde. Die Kriterien, die für Wicket sprechen, habe ich an eine Übersicht auf der Webseite des Wicket-Projektes 1 angelehnt, beschränke mich aber hier auf die für mich besonders wichtigen Aspekte.
1.1.1
Einfach, Konsistent, Offensichtlich
Ich empfehle als Einstieg eine kleine Beispielanwendung, die man sich über die „Quickstart“-Funktion 2 generieren lassen kann. Anhand dieser Anwendung kann man einige der folgenden Punkte recht schnell nachvollziehen: Alles kann in Java realisiert werden. Das Grundprinzip erinnert an Swing und ist leicht zu verstehen. Es müssen keine Konfigurationsdateien erstellt werden. Wicket-Anwendungen können einfach für Suchmaschinen optimiert werden. Hervorragende Unterstützung bei der Fehlersuche durch ausführliche Informationen im Entwicklungsmodus. Wicket benötigt keine speziellen Vorbereitungen oder Vorarbeiten. Wicket ist einfach nur Java. Wicket-Anwendungen können über Unit-Tests getestet werden. Es ist für mich einer der größten Vorteile, dass alles in Java ausgedrückt werden muss. Es gibt nur eine Stelle, an der ein Fehler auftreten kann: im Code. Mit den sehr ausführlichen Fehlermeldungen kann der Fehler sehr schnell eingegrenzt und behoben werden.
1.1.2
Wiederverwendbarkeit
Wicket ist zwar nicht das einzige Framework, bei dem man auch bei Komponenten auf Vererbung zurückgreifen kann, doch das einzige, bei dem das sehr einfach umzusetzen ist. 1 2
4
http://wicket.apache.org/introduction.html http://wicket.apache.org/quickstart.html
1.1 Warum Wicket? Das ermöglicht sehr leistungsfähige und gleichzeitig effektive Komponenten. Da Komponenten ihre Funktionalität vollständig in sich kapseln, kann man sie innerhalb derselben Anwendung beliebig oft nutzen, sodass komplexe Anwendungen sehr schnell realisiert werden können. Wicket-Komponenten können auch anwendungsunabhängig entwickelt werden. Alle notwendigen Daten und Informationen können in eine Java-Bibliothek ausgelagert und dann in verschiedensten Projekten eingesetzt werden. Dazu muss die Bibliothek nur in das Projekt eingebunden werden. Auf diese Weise profitiert man sogar in allen nachfolgenden Wicket-Projekten von der eigenen Entwicklungsarbeit, da man oft fast alle allgemeinen Komponenten wiederverwenden kann. So entsteht auch hier eine Bibliothek von Lösungen, wie man sie aus anderen Bereichen der Anwendungsentwicklung kennt.
1.1.3
Sauber getrennt
Wer Wicket zum ersten Mal testet und vorher schon Erfahrungen mit JSP, JSF oder ähnlichen Ansätzen gemacht hat, wundert sich, dass im Markup (HTML-Schnipsel, die zur Komponente gehören und für die Darstellung wichtig sind) keine Code-Bestandteile zu finden sind. Dieser Punkt irritiert so stark, dass er immer wieder z.B. in Mailinglisten thematisiert wird. Während der Arbeit mit Wicket offenbart dieses Konzept seine Vorteile: Im Markup befinden sich nur Referenzen auf Komponenten, aber kein Code oder spezielle Logik. Die Komplexität und der Funktionsreichtum der Anwendung werden nur durch Java als Programmiersprache und nicht durch eine in ihrer Ausdruckstärke eingeschränkten Auszeichnungssprache begrenzt. Programmcode entsteht auf diese Weise nur an einer Stelle und in einer Sprache. Die Markup-Dateien für die Komponenten können mit einfachen Textprogrammen oder mit HTML-Editoren bearbeitet werden. Da Wicket keine besondere Notation erfordert, gehen bei der Bearbeitung keine Informationen verloren, sodass die Gestaltung der Vorlagen z.B. durch einen Webdesigner angepasst werden kann. So wird die Arbeitsteilung zwischen Entwickler und Gestalter gut unterstützt. Im Zweifelsfall muss nur die versehentlich gelöschte Bezeichnung der Komponente wiederhergestellt werden. Auf alle positiven Skalierungseffekte im Entwicklungsprozess durch die Nutzung von Java als Programmiersprache und den darauf aufsetzenden unterstützenden Werkzeugen kann bei der Entwicklung von Anwendungen mit Wicket zurückgegriffen werden. Die Komponentenarchitektur unterstützt diesen Aspekt zusätzlich.
1.1.4
Sicher
Je nach verwendetem Framework sind Webanwendungen mehr oder weniger anfällig für Code-Injection 3-, XSS 4- und andere Attacken. 3 4
http://en.wikipedia.org/wiki/Code_injection http://en.wikipedia.org/wiki/Cross-site_scripting
5
1 Einleitung Wicket-Anwendungen sind sicher. In Wicket werden nur die Parameter der Nutzerinteraktion in der URL kodiert. Der Zustand der Anwendung wird serverseitig gespeichert, sodass eine Kodierung dieser Information in URL-Parameter nicht notwendig ist. Das Einschleusen von Schadcode über manipulierte Parameter ist somit weitestgehend ausgeschlossen. Möchte man den Zustand einer Anwendung durch URL-Parameter steuerbar gestalten, muss man diese Möglichkeit explizit bereitstellen. Da Wicket-Anwendungen vollständig in Java realisiert werden, stehen alle Sicherheitsaspekte der Java-Plattform zur Verfügung.
1.1.5
Effizient und skalierbar
Jede Abstraktion oder Zwischenschicht zieht ein anderes Laufzeitverhalten nach sich. Wie stark die Einschnitte in den verschiedenen Bereichen sind, hängt von der eingesetzten Lösung ab. Wicket gehört zu den leichtgewichtigen Lösungen. Der architekturelle Überbau hat dabei kaum Auswirkungen auf die Gesamtperformance. Da Wicket vollständig in Java realisiert wurde, können zudem zusätzlich zu den klassischen Lösungsansätzen für die Lastverteilung in Webanwendungen andere, auf die Lastverteilung von Java-Anwendungen spezialisierte Produkte eingesetzt werden (z.B. Terracotta 5).
1.1.6
Komplett
Webanwendungen können vollständig mit den in Wicket enthaltenen Komponenten umgesetzt werden. Man muss also nicht in Vorleistung gehen und sich ein eigenes Anwendungsgerüst schaffen. Es ist allerdings derart einfach, eigene Anpassungen vorzunehmen, eigene Komponenten zu entwickeln oder einfach fremde Komponenten zu benutzen, dass man sich schnell an die erweiterten Möglichkeiten gewöhnt und rückblickend feststellen muss, wie reduziert bisherige Lösungen und die damit realisierten Anwendungen waren.
1.1.7
Eine gute Wahl
Mit Wicket zu arbeiten macht Spaß. Die Komplexität von mit Wicket realisierbaren Anwendungen übersteigt die Komplexität solcher Anwendungen, die mit anderen Webtechnologien umgesetzt werden können, bei Weitem. Die Komponentenarchitektur gewährleistet dennoch eine große Übersichtlichkeit, sodass der Einarbeitungsaufwand sehr viel geringer ist, was sich gerade bei der Anpassung von bestehenden Anwendungen positiv bemerkbar macht. Wer Webanwendungen entwickelt, sollte Wicket also unbedingt ausprobieren.
5
6
http://www.terracotta.org/
1.2 Vorbereitung und Installation
1.2
Vorbereitung und Installation Um Webanwendungen mit Wicket schreiben zu können, bedarf es weniger Vorraussetzungen. Im Prinzip reichen Java und ein Texteditor. Wenn man ernsthaft Webanwendungen entwickeln möchte, kommt man aber um ein etwas komplizierteres Setup nicht herum. Dafür bekommt man dann aber auch eine Menge an nützlichen Funktionen und Hilfsmitteln.
1.2.1
Java, Maven und Eclipse
Java Für Wicket benötigt man ein Java Development Kit (JDK) ab Version 5. Eine Java Runtime Environment (JRE) reicht zum Entwickeln nicht aus. Unter Windows laden Sie die Installationsdateien einfach von der Javaseite 6 von Sun herunter und starten die Installation. Unter Linux sollte man Java über das integrierte Paketmanagement installieren. Apache Maven Damit man den Java-Compiler nicht immer von Hand in der Kommandozeile starten muss, haben sich im Laufe der Zeit unterschiedliche Ansätze und Lösungen für dieses Problem entwickelt. In diesem Buch und auch sonst bevorzuge ich Maven für das Build-Management. Für die Installation lädt man das passende Archiv von der Apache-Maven-Seite 7 herunter und folgt den Installationsanweisungen auf der Seite. Eclipse Für das Buch empfehle ich als Entwicklungsumgebung Eclipse 8. Dafür gibt es zwei Gründe: Eclipse ist kostenlos. Maven-Projekte können sehr einfach in Eclipse importiert werden. Alle definierten Abhängigkeiten werden dabei in die Eclipse-Projekte übernommen. Ich empfehle für dieses Buch Eclipse IDE for Java EE Developers ab Version 3.4. Ein Plugin für Maven kann installiert werden. Das ist aber nicht notwendig, weil Maven die Eclipse-Projekte ohne fremde Hilfe erzeugt.
1.2.2
Versionskontrolle mit Subversion
Ein regelmäßiges Backup ist ein gutes Ruhekissen. Auf diese Weise kann man jederzeit einen älteren Zustand wiederherstellen. Spätestens, wenn man mit mehreren Entwicklern an einem Projekt arbeitet, funktioniert das Backup-Prinzip nicht mehr. Ein Versionsver6
http://java.sun.com/javase/downloads/index.jsp http://maven.apache.org/ 8 http://www.eclipse.org/ 7
7
1 Einleitung waltungssystem (Version Control System, VCS) wird unumgänglich. Aber auch für den einzelnen Entwickler bringt der Einsatz eines VCS erhebliche Vorteile. Man kann in der Entwicklung gefahrlos verzweigen und diesen Entwicklungszweig wieder mit dem Hauptzweig zusammenführen. Man kann Dinge ausprobieren und jederzeit wieder auf einen älteren (nicht nur den letzten) Stand zurücksetzen. Subversion hat sich als Standard etabliert und kann über Plugins recht gut in Eclipse integriert werden. Empfehlungen Für die Arbeit an einem Projekt empfiehlt es sich unter Windows, sowohl das EclipsePlugin 9 als auch das Windows Explorer-Plugin TortoiseSVN 10 zu installieren. Unter Linux kann man alle Aufgaben über die Kommandozeile durchführen und sollte daher Subversion über die Paketverwaltung installieren. Da das Eclipse-Plugin eine eigene SubversionBibliothek mitbringt, ist darauf zu achten, dass die Subversion-Client-Version in beiden Fällen dieselbe ist, da sich das Ablageformat teilweise unterscheidet und der ältere Client dann nicht mehr auf die Daten zugreifen kann, wenn der neuere Client sie erst einmal in das neuere Format konvertiert hat. Wer keinen eigenen Subversion-Server aufsetzen möchte, kann auch auf SubversionHosting-Angebote 11 zurückgreifen.
1.3
Grundlagen einer Webanwendung Wicket ist im Gegensatz zu Grails oder Rails kein Framework, das alle Aspekte einer Webanwendung, z.B. den Datenbankzugriff, in einem Paket bündelt. Wir müssen uns daher selbst um die notwendigen Bibliotheken bemühen, was uns aber vor keine großen Herausforderungen stellen wird.
1.3.1
Anwendungsschichten
Das Buch hat den Anspruch, alle wesentlichen Aspekte der Entwicklungen von Webanwendungen abzubilden. Dazu gehört neben der Präsentationsschicht, bei der Wicket zum Einsatz kommt, die Schicht der Anwendungslogik (Business Logic) und die der Datenhaltung (Persistenz) (siehe den Wikipedia-Eintrag zur Drei-Schichten-Architektur 12). Da auch die Präsentationsschicht vollständig in Java realisiert wird, ist die Anbindung an die anderen Applikationsschichten besonders einfach. Das Zusammenfügen der verschiedenen Anwendungsschichten geschieht nicht mehr durch Programmcode, sondern durch den Einsatz eines Dependency Injection 13-Frameworks. 9
http://subclipse.tigris.org/ http://tortoisesvn.tigris.org/ 11 http://www.svnhostingcomparison.com/ 12 http://de.wikipedia.org/wiki/3-Tier#Drei-Schichten-Architektur 13 http://de.wikipedia.org/wiki/Dependency_Injection 10
8
1.3 Grundlagen einer Webanwendung 1.3.1.1 Persistenz mit Hibernate Alle Anwendungen arbeiten mit Daten. Daten werden klassisch in Datenbanksystemen abgelegt. Relationale Datenbanksysteme bilden Daten in Tabellen ab, die miteinander verknüpft werden können. In den Spalten einer Tabelle werden die verschiedenen Attribute eines Datensatzes abgelegt, jede Zeile steht für einen eigenständigen Datensatz. Objektorientierte Sprachen können mit Objekten und Beziehungen von Objekten untereinander arbeiten. Die Datenhaltung in Tabellen unterscheidet sich derart von den Möglichkeiten objektorientierter Datenhaltung, dass die Modelle nicht automatisch aufeinander abbildbar sind. Es gibt Speziallösungen, die Objekte und deren Beziehung in einer Objektdatenbank 14 abbilden können. Diese Speziallösungen konnten sich bis heute aus verschiedenen Gründen nicht durchsetzen. Daher wurde für die Anbindung von relationalen Datenbanksystemen OR-Mapper 15 entwickelt, die es sowohl für verschiedene Programmiersprachen als auch für unterschiedliche Datenbanksysteme gibt. Diese OR-Mapper übernehmen die Transformation der unterschiedlichen Modelle ineinander. Dabei wird meist ein Objekt auf eine Tabelle und die Eigenschaften des Objekts auf die Spalten einer Tabelle abgebildet. Diese Methode ist bei der Abbildung von komplexen Objektbeziehungen natürlich limitiert, hat gegenüber den Speziallösungen aber den entscheidenden Vorteil, dass auf bewährte Technologien zurückgegriffen werden kann. Für die Datenbankanbindung in Java gibt es JDBC mit den entsprechenden datenbankabhängigen Treibern. Der Zugriff erfolgt in Tabellenform. Im Prinzip sind damit alle Anforderungen an eine Datenbankschnittstelle erfüllt. Die Anbindung an eigene Applikationen ist dennoch recht kompliziert und mit vielen Fallstricken versehen. Eine objektorientierte Darstellung der Daten und deren Transformation in die Tabellenstruktur der Datenbank hat auf Seiten der Anwendungsentwicklung zu wesentlich schnelleren Ergebnissen geführt. Außerdem kann der Zugriff auf die Daten in Abhängigkeit von der Datenbank optimiert werden. OR-Mapper stellen daher neben der Transformation der Daten meist grundlegende Datenbankoperationen zur Verfügung. Warum Hibernate? In diesem Buch und auch sonst empfehle ich Hibernate als Persistenz-Framework. Die Wahl fiel auf Hibernate, weil es einfach zu benutzen und am Markt etabliert ist. Es zeichnet sich ähnlich wie Wicket durch eine gute Java-Integration aus. Natürlich kann man jedes andere Persistenz-Framework mit Wicket benutzen. Wicket bietet keine besondere Hibernate-Integration, sodass die aufgezeigten Lösungsvorschläge auch auf andere Frameworks abgebildet werden können.
14 15
http://de.wikipedia.org/wiki/Objektorientierte_Datenbank http://de.wikipedia.org/wiki/Objektrelationale_Abbildung
9
1 Einleitung Funktionsweise Mit Hibernate kann man Klassen und deren Attribute auf Datenbanktabellen abbilden. Beziehungen untereinander können ebenso abgebildet werden. Hibernate sorgt dabei für die Transformation der Daten und Abhängigkeiten in ein für die Datenbank verständliches Format. Die Informationen, auf welche Tabelle und auf welchen Spalten die Objekte abgebildet werden, können sowohl in der Klasse selbst (per Annotation) als auch in gesonderten Konfigurationsdateien abgelegt werden. Der Zugriff auf die Daten kann über eine objektorientierte Alternative zu SQL (HQL) oder über eine Schnittstelle erfolgen. Die Transformation in Objekte erfolgt transparent. 1.3.1.2 Dependency Injection mit dem Spring-Framework In klassischen objektorientierten Anwendungen ist jedes Objekt selbst dafür verantwortlich, die Abhängigkeiten aufzulösen sowie die notwendigen Ressourcen zu erzeugen und zu verwalten. Dazu muss jedes Objekt über Informationen seiner Umgebung verfügen. Dependency Injection verlagert die Verantwortung für das Finden und Verwalten von Ressourcen und Abhängigkeiten aus dem Objekt in ein Framework. Das Framework erzeugt über Konfigurationsdateien oder Metainformationen in den Klassen die Anwendungsmodule und löst die definierten Abhängigkeiten auf. Dieses Vorgehen reduziert die Abhängigkeit der Klasse zur Umgebung und zu konkreten Umsetzungen der abhängigen Module. Warum Spring? Das Spring-Framework wurde aus ähnlichen Gründen wie Hibernate ausgewählt. Und ähnlich wie bei Hibernate kann auch dieser Aspekt durch ein anderes Framework abgebildet werden. Wie gut die Ergebnisse in diesem Buch dann übertragbar sind, hängt sehr stark von dem verwendeten Framework ab. Spring benutzt für die Konfiguration XML-Dateien. In diesen Dateien werden die Objekte mit Namen versehen und die Konfiguration der Attribute vorgenommen. Die Abhängigkeiten von Objekten können über Referenzen und deren Übergabe in Attribute vorgenommen werden. Eine Anwendung lädt über Funktionen des Frameworks eine Spring-Konfiguration und kann dann über den Namen auf die Objekte bekannten Typs zugreifen. Spring ist somit einfach in jede Java-Anwendung integrierbar. 1.3.1.3 Anwendungslogik und Präsentation mit Wicket Die Architektur von Wicket folgt dem Model-View-Controller 16-Ansatz (Modell-Präsentation-Steuerung). Das Modell beinhaltet die darzustellenden Daten, der Controller übernimmt die Steuerung der Interaktionsmöglichkeiten, und in der Präsentationsschicht werden diese Daten dann dargestellt.
16
10
http://de.wikipedia.org/wiki/MVC
1.3 Grundlagen einer Webanwendung
1.3.2
Verzeichnis und Paketstruktur
Es zeigt sich, dass man nie zu früh anfangen kann, seine Softwareprojekte gut zu strukturieren. Ich lege daher für jede Anwendungsschicht mindestens ein eigenständiges Teilprojekt an. Das verkürzt den Entwicklungszyklus und verbessert die Übersichtlichkeit. 1.3.2.1 Projektverwaltung mit Maven Für das Erstellen der Teilprojekte und die Abhängigkeitsverwaltung setze ich Maven ein. Maven ist dabei ein Build-Tool mit vielfältigen Möglichkeiten. Alternativen zu Maven sind z.B. Ant 17 mit Ivy 18 oder Buildr 19. Build-Tools sind hauptsächlich für das Erstellen der ausführbaren Programme aus vorhandenen Quelltexten zuständig. Das Aufgabenspektrum hat sich spätestens seit Maven in der Version 1.0 grundlegend erweitert und umfasst seither vor allem auch die Abhängigkeitsverwaltung. Weitere Funktionalitäten können fast beliebig durch Plugins erweitert werden und reichen vom Erstellen von Dokumentationsseiten bis zum Deployment der Anwendung auf Produktivsystemen. Das Grundprinzip von Maven ist einfach. In einem Verzeichnis gibt es ein vorgeschriebenes Verzeichnislayout für ein Projekt: pom.xml src main java resources test java resources
Dazu benötigt Maven neben einer sehr einfach gehaltenen Projektdefinitionsdatei (pom.xml), die minimale Informationen zum Projekt enthält, die notwendigen JavaQuelltexte in standardisierten Verzeichnissen. Maven sucht dann für das Erstellen des Projekts z.B. im Verzeichnis src/main/java nach Java-Dateien und kompiliert diese. In src/test/java befinden sich Unit-Tests, die beim vollständigen Erstellen der Anwendung ausgeführt werden. In den jeweiligen resource-Verzeichnissen befinden sich alle anderen Dateien (Grafiken, Texte etc.). In der Projektdefinitionsdatei können auch die Abhängigkeiten zu den anderen Teilprojekten oder zu anderen Bibliotheken eingestellt werden, wobei Maven diese Abhängigkeiten selbstständig auflöst und die referenzierten Bibliotheken automatisch aus einem Verzeichnis herunterlädt und lokal vorhält. Wurde eine Bibliothek bereits einmal heruntergeladen, greift Maven auf die in einem lokalen Verzeichnis abgelegte Version zurück. Über Plugins ist es außerdem möglich, die Webanwendung direkt aus dem Projektverzeichnis heraus starten zu können. Auf diese Weise können sogar Anpassungen an der laufenden Anwendung vorgenommen werden. 17
http://ant.apache.org/ http://ant.apache.org/ivy/ 19 http://incubator.apache.org/buildr/ 18
11
1 Einleitung 1.3.2.2 Besser als Quickstart Wicket bietet auf der eigenen Webseite eine „Quickstart“-Funktion, die ein einfaches Wicket-Anwendungsgerüst erstellt, das sofort lauffähig ist. Für einen ersten Test sollte man sich mit „Quickstart“ ein Testprojekt erstellen lassen. Allerdings empfehle ich, dieses Projekt für eigene Entwicklungen nicht weiter zu verwenden. Dafür gibt es verschiedene Gründe. Der Wichtigste für mich: Das Projekt benutzt angepasste Einstellungen, die so nicht nötig sind und vom Standard abweichen. Außerdem ist es hilfreich, wenn man alle Anpassungen, die man im Laufe des Entwicklungsprozesses vorgenommen hat, selbst durchführt. Dann kann man z.B. dokumentieren, was die Anpassung bewirkt, was sich neben der besseren Dokumentation des eigenen Projekts positiv auf die Fehlersuche auswirkt.
1.3.3
Unit-Tests
Irren ist menschlich. Doch Fehler in Anwendungen sind ärgerlich. Fehler in der eigenen Anwendung kann man aber vermeiden. Denn je komplexer die Anwendungen werden, desto mehr Zeit benötigt man, um selbst triviale Fehler einzugrenzen. Eine Methode, die Anzahl der Fehler mit jedem Entwicklungsschritt nachhaltig einzudämmen, nennt sich „Test Driven Development“. Dabei geht es im Prinzip darum, für jede Zeile Programm wieder ein Programm zu schreiben, das die Funktionsfähigkeit und das Ergebnis überprüft. Was sich im ersten Moment etwas „verrückt“ anhört, stellt sich nach kurzer Zeit als unglaublich entlastendes Instrument dar. Wenn das Teilprojekt mit den automatisch ablaufenden Tests erstellt werden konnte, kann man sich ziemlich sicher sein, dass es genauso stabil läuft wie vor der letzten Anpassung. Unit-Tests (Modul- bzw. Komponententests) unterscheiden sich vom normalen Programmcode nur dadurch, dass die Namen der Klassen meist mit „Test“ anfangen und die Methoden der Klasse z.B. „testIrgendetwas“ heißen. Wenn man mit Maven ein Projekt kompiliert, dann werden die nach dieser Nomenklatur erstellten Klassen und Funktionen ausgeführt. Wenn dann in so einem Test ein Fehler auftritt und der Test somit fehlschlägt, kann das ganze Projekt nicht gebaut werden und wird daher auch nicht für andere Projekte, die dieses Teilprojekt eingebunden haben, zur Verfügung gestellt. So kann sichergestellt werden, dass alle Abhängigkeiten in einem fehlerfreien Zustand sind. Wenn in einem Test Fehler auftreten, kann man die Suche nach der Ursache sehr stark einschränken. Denselben Fehler innerhalb einer laufenden Anwendung zu isolieren, ist sehr viel aufwendiger. Außerdem bietet ein gut formulierter Test einen hervorragenden Sicherheitsfallschirm für den Fall, das man z.B. Optimierungen vornimmt oder die Funktionalität erweitert. Wenn die Tests nach einer solchen Anpassung immer noch funktionieren, kann man fast davon ausgehen, dass auch die Anwendung, die auf diese Funktionen aufbaut, immer noch funktioniert.
12
1.3 Grundlagen einer Webanwendung 1.3.3.1 Persistenz-Tests Auch mit Hibernate können Unit-Tests durchgeführt werden. Dabei kann eine temporäre Datenbank benutzt werden, die nur zur Laufzeit des Tests vorhanden ist und keinerlei Daten enthält. Das bedeutet, dass Nebeneffekte durch bereits bestehende Daten ausgeschlossen werden können. Somit kann man alle Datenbankoperationen (z.B. Löschen) ohne die Gefährdung von Produktivsystemen testen. 1.3.3.2 User-Interface-Tests Mit Wicket können Teile von Wicket-Anwendungen und -Komponenten getestet werden. Auch wenn die Testbarkeit sicher mit Einschränkungen behaftet ist, geht die Testabdeckung und die Einfachheit beim Erstellen eigener Test weit über das hinaus, was mit anderen Frameworks realisierbar ist. Das macht Wicket in diesem Bereich einmalig. Zusammenfassung Interessanterweise unterscheidet sich unsere Auswahl nur unwesentlich von den Bibliotheken, die auch Grails benutzt, sicherlich auch, weil wir ebenso wie das Grails-Projekt auf etablierte Standards setzen. Auch wenn der Aufwand für das Aufsetzen eines Projektes ungleich höher ist, bewahren wir so die Flexibilität, die es uns erlauben würde, z.B. ein anderes Persistence-Framework einzusetzen. Allerdings ist zu beachten, dass diese Aufwände nur einmal in einem Projekt anfallen und damit selten eine große Rolle spielen.
13
1 Einleitung
14
2 2 Aufsetzen der Teilprojekte Wir schreiben endlich unsere erste eigene Wicket-Anwendung. Die Strukturen, die im Folgenden erstellt werden, sollten sich durch leichte Modifikationen auf andere Projekte anwenden lassen. In den folgenden Abschnitten erstellen wir zuerst das notwendige Grundgerüst mit allen Abhängigkeiten zu externen Bibliotheken und eigenen Teilprojekten. Im zweiten Schritt hauchen wir dem Ganzen Leben ein. Als Erstes sollte man sich für dieses Projekt ein Verzeichnis anlegen, in dem künftig alle Teilprojekte und sonstige Daten beheimatet sind. Es empfiehlt sich, auf Leerzeichen in den Verzeichnisnamen zu verzichten, sonst kann es zu unnötigen Komplikationen kommen.
2.1
Nomenklatur der Teilprojekte Maven benutzt für das Auflösen von Abhängigkeiten drei Informationen, die in einem Projekt angegeben werden müssen: groupId: Bezeichnet eine Gruppe, in der die Artefakte zusammengefasst werden. Die GruppenID hat eher organisatorische Gründe und ist leider ungeeignet, Namenskollisionen zu vermeiden. Im Dateinamen der fertigen Bibliothek kommt dieser Bezeichner nicht vor, sodass es zu Dateinamenskollisionen kommen kann, wenn aus unterschiedlichen Gruppen Artefakte mit derselben Versionsnummer und artifactId benutzt werden. artifactId: Diese ID gibt dem Projekt seinen Namen. Diese ID findet sich später auch im Dateinamen der erzeugten Bibliothek und im Projektnamen in Eclipse wieder. version:
Version des Projekts. Eine gute Versionierung sollte eigentlich bei 0.1 anfangen. Ich persönlich finde es akzeptabel, in diesem Buch bei Version 1.0 anzufangen. Allerdings wird bis zur Fertigstellung
15
2 Aufsetzen der Teilprojekte die Versionsbezeichnung 1.0-SNAPSHOT lauten (das ist die Grundeinstellung von Maven). Dieses Versionierungsschema empfiehlt sich aus zwei Gründen. Zum einen wird auf diese Weise das Teilprojekt als „in Arbeit“ gekennzeichnet und sorgt zum anderen bei Maven dafür, dass beim Auflösen dieser Abhängigkeiten regelmäßig geprüft wird, ob eine Version mit einem neueren Zeitstempel existiert. Wenn man allein an einem Projekt entwickelt, wird sich da kein Unterschied bemerkbar machen. Wenn man aber im Team entwickelt und ein Continuous-Build-System 1 zum Einsatz kommt, ist nur so gewährleistet, dass man die aktuellste Version eines Teilprojektes benutzt. Da die groupId aus oben genannten Gründen nicht zur Strukturierung von Projekten herangezogen werden kann, handhabe ich die Bezeichnung der Projekte wie folgt: In groupId schreibe ich den Domainnamen des Projekts in umgekehrter Reihenfolge. Wenn es ein Unterprojekt für eine existierende Domain ist, hänge ich ein „projekt.“ an. Der Wert in artifactId fängt mit dem Wert aus groupId an und wird, gefolgt von „--“, um einen Namen für dieses Teilprojekt ergänzt. Der Verzeichnisname für dieses Teilprojekt entspricht dem Wert in artifactId. Für dieses Beispiel benutze ich als groupId den Wert „de.wicketpraxis“. Ein Teilprojekt hat demzufolge als artifactId den Wert „de.wicketpraxis--teilprojekt“. Das Verzeichnis, in dem alle Daten dieses Teilprojektes enthalten sind, hat ebenso diesen Namen.
2.2
Aufsetzen der Teilprojekte Beim Erstellen der Teilprojekte arbeiten wir uns von der Basis an aufwärts, wobei die höchste Schicht die Präsentationsschicht ist, in diesem Fall also Wicket. Ziel ist es, am Ende ein vollständiges Projekt mit allen notwendigen Teilprojekten zu erhalten. Im zweiten Schritt wird für jede Schicht eine beispielhafte Funktionalität realisiert, die das Funktionieren und das Zusammenspiel der verschiedenen Schichten veranschaulicht.
2.2.1
Projektbasis ParentPom
Als erstes benötigen wir ein Maven-Projekt, in dem alle teilprojektübergreifenden Einstellungen und Abhängigkeiten abgelegt werden. Dazu legen wir ein Projekt unter de.wicketpraxis–-parentPom an. Der Name parentPom wurde gewählt, weil in diesem Teilprojekt übergreifende Einstellungen vorgenommen werden können. Außerdem kann zum Schluss die ganze Anwendung über dieses Projekt erstellt werden, weil die Teilprojekte entsprechend konfiguriert wurden.
1
16
http://www.theasolutions.com/tutorials/scrum_agile.jsp
2.2 Aufsetzen der Teilprojekte Auf der Kommandozeile erstellt folgende Befehlssequenz ein passendes Projektverzeichnis (alles gehört in eine Eingabezeile und wurde nur zur besseren Übersicht mehrzeilig geschrieben): $ mvn archetype:create \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DgroupId=de.wicketpraxis \ -DartifactId=de.wicketpraxis--parentPom
Dabei legt Maven erfreulicher Weise gleich ein Verzeichnis passend zur artifactId an. In diesem Verzeichnis finden sich folgende Verzeichnisse: src main java resources test java resources
In main/java finden sich die Quelltexte der Anwendung. In main/resources wird alles abgelegt, was in diesem Projekt sonst noch an Daten benutzt wird. Dazu gehören z.B. Bilder oder Konfigurationsdateien. Unter test/java werden die Quelltexte für die Unit-Tests abgelegt, die in dem Test auf die Ressourcen unter test/resources zurückgreifen können. Programmcode und Daten aus dem test-Ordner werden nicht in der Bibliotheksdatei weitergegeben. Jetzt kann man mit $ mvn install
auf der Kommandozeile das Projekt erstellen lassen. Wenn alles richtig konfiguriert wurde und alle Tests erfolgreich abgeschlossen werden konnten, erscheint am Ende die Ausgabe: ... [INFO] ---------------------------------------------------------------[INFO] BUILD SUCCESSFUL [INFO] ---------------------------------------------------------------...
In dem generierten Projektverzeichnis befindet sich außerdem die Projektdatei pom.xml mit folgendem Inhalt: 4.0.0 de.wicketpraxis de.wicketpraxis--parentPom jar 1.0-SNAPSHOT de.wicketpraxis--parentPom http://maven.apache.org junit junit 3.8.1 test
17
2 Aufsetzen der Teilprojekte Wie man erkennen kann, finden sich dort unsere Werte für groupId und artifactId wieder. Außerdem sind noch zwei Bereiche von besonderem Interesse. Der Parameter „packaging“ ist auf dem Wert „jar“ gesetzt, was bedeutet, dass aus diesem Projekt eine normale Bibliothek in einer JAR-Datei erzeugt wird. Der zweite interessante Aspekt ist im Bereich dependencies zu finden. Dort wird eine Bibliothek als Abhängigkeit definiert, die für das Ausführen der Unit-Tests vorhanden sein muss. Man kann auch hier wieder die drei relevanten Parameter für die Adressierung dieser Abhängigkeit erkennen. Zusätzlich ist der Parameter scope aufgeführt, bei dem der Wert test darauf hinweist, dass diese Bibliothek nur für die Unit-Tests benutzt wird. Jetzt passen wir die Projektbeschreibungsdatei wie folgt an: Listing 2.1 pom.xml 4.0.0 de.wicketpraxis de.wicketpraxis--parentPom 1.0-SNAPSHOT pom ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ Parent Pom) http://wicketpraxis.de maven-compiler-plugin 1.5 1.5 UTF-8 org.apache.maven.plugins maven-resources-plugin UTF-8 org.apache.maven.plugins maven-source-plugin attach-sources verify jar junit junit
18
2.2 Aufsetzen der Teilprojekte ${junit.version} test org.slf4j slf4j-jdk14 ${slf4j.version} 3.8.1 1.4.2
Diese Projektdefinition wird nachher von allen anderen Teilprojekten referenziert und definiert somit die Grundeinstellung für alle Teilprojekte. Folgende Anpassung habe ich vorgenommen: An der Stelle, wo steht, habe ich die Darstellung aus Platzgründen gekürzt. Die Angaben an dieser Stelle entsprechen den automatisch generierten. Der Parameter packaging muss für dieses Teilprojekt auf pom gesetzt werden. Die Platzhalter wie z.B. ${pom.groupId} werden durch die aktuell definierten Inhalte ersetzt. Damit erspart man sich einerseits Schreibaufwand und kann andererseits bestimmte Informationen an einer Stelle bündeln (z.B. die Versionen für die Abhängigkeiten im Bereich properties). Mit dem Compiler-Plugin (maven-compiler-plugin) wird die Java-Version auf 1.5 gesetzt. Damit benötigt die Anwendung mindestens ein Java 5 oder Java 6. Außerdem wird die Kodierung für die Quellcodedateien auf UTF-8 gesetzt. Das Maven-Resource-Plugin (maven-resources-plugin) sorgt dafür, dass alle Ressourcendateien mit UTF-8-Kodierung eingebunden werden. Es empfiehlt sich, mithilfe des Maven-Source-Plugins (maven-source-plugin) auch die Quelltexte zum Projekt in ein Archiv packen zu lassen. So kann man später in der Entwicklungsumgebung sehr einfach auf die passenden Quelltexte zugreifen, ohne dass man das Projekt geöffnet haben muss. Für dieses Projekt benutzen wir junit für die Unit-Tests. Es muss mindestens eine Bibliothek für Unit-Tests definiert sein, sonst können Projekte nicht gebaut werden. Java bietet ein integriertes Logging-Framework. Außerdem gibt es noch von Apache das log4j-Logging-Framework. Das slf4j-Projekt bietet die Möglichkeit, jederzeit das verwendete Framework austauschen zu können, ohne dass man Anwendungscode anpassen muss. Außerdem bietet slf4j eine praktische Schnittstelle, um parametrisierte Log-Ausgaben generieren zu können. Damit ist dieses Teilprojekt fertig gestellt. Jetzt müssen wir dieses Projekt als Basisprojekt in alle weiteren Teilprojekte einbinden. Davor sollten wir es kurz mit mvn install erstellen.
19
2 Aufsetzen der Teilprojekte
2.2.2
Teilprojekt Base
Im Laufe einer langjährigen Entwicklung sammeln sich immer mehr Funktionen, die keinem Projekt direkt zugerechnet werden können, sich aber einer ungemeinen Nützlichkeit im Projektalltag erfreuen. Damit diese Funktionen sich gar nicht erst in den Untiefen eines Projekts verlieren, empfehle ich, ein Teilprojekt anzulegen, in dem solche allgemeinen Funktionen dann eine passende Heimat haben. Dieses Teilprojekt erstellen wir wieder mit: $ mvn archetype:create \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DgroupId=de.wicketpraxis \ -DartifactId=de.wicketpraxis--base
Die generierte Projektdatei wird vollständig ersetzt, da wir viele Einstellungen bereits in dem übergeordneten Projekt getroffen haben und diese deshalb nicht noch einmal angegeben werden müssen. Listing 2.2 pom.xml 4.0.0
de.wicketpraxis de.wicketpraxis--parentPom 1.0-SNAPSHOT ${pom.groupId}--base ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ Base)
Damit die nötigen Informationen vom Basisprojekt übernommen werden können, muss es wie im Bereich parent referenziert werden.
2.2.3
Teilprojekte Datenbankkonfiguration
Für die Datenbankanbindung werden drei Teilprojekte angelegt. Auch wenn der Aufwand im ersten Moment übertrieben wirkt, ist nur durch die Teilung eine saubere Abhängigkeitsstruktur erreichbar. Das erklärt sich sehr viel besser, wenn wir nachher diese Projekte einbinden und nutzen. 2.2.3.1 Konfigurationsprojekt für die Produktivdatenbank Wir legen ein Teilprojekt mit der artifactId de.wicketpraxis--dbconfig an. Wir passen die Projektdatei an und fügen die Abhängigkeiten für den Datenbanktreiber ein. Als Produktivdatenbank soll eine MySQL-Datenbank zum Einsatz kommen. Daher wird die Bibliothek für diesen Datenbanktreiber eingebunden. Listing 2.3 pom.xml 4.0.0
20
2.2 Aufsetzen der Teilprojekte ... ${pom.groupId}--dbconfig ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ DB Config) mysql mysql-connector-java ${mysql.version} 5.1.6
2.2.3.2 Konfigurationsprojekt Schema-Update Hibernate bietet die Möglichkeit, aus der konfigurierten Datenbankzugriffschicht automatisch das Datenbankschema zu erzeugen. Dabei nimmt Hibernate ebenfalls datenbankspezifische Optimierungen vor, auf die man durchaus zurückgreifen sollte. Wenn man nicht gerade über umfangreiches datenbankspezifisches Wissen verfügt, dürfte das Know-how aus dem Hibernate-Projekt zu den besseren Ergebnissen führen. Auf die automatische Schemagenerierung greifen wir bei der Testdatenbank ebenfalls zurück. Dieses Projekt dient nur dazu, andere Zugangsdaten für die Datenbank bereitzustellen, die mit anderen Rechten ausgestattet die entsprechenden Tabellen anlegen kann. Außerdem können wir so verhindern, dass diese Zugangsdaten auf Produktivsystemen auftauchen. Wir erstellen das Teilprojekt mit der artifactId de.wicketpraxis--dbconfigschema-update. Wir definieren eine Abhängigkeit zum Projekt mit der Produktdaten-
bankkonfiguration, sodass derselbe Datenbanktreiber benutzt wird. Listing 2.4 pom.xml 4.0.0 ... ${pom.groupId}--dbconfig-schema-update ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ DB Config Schema Update) ${pom.groupId} ${pom.groupId}--dbconfig ${pom.version}
Wenn sich der Datenbanktreiber ändern sollte, muss natürlich in beiden Fällen die noch zu erstellende Konfiguration angepasst werden.
21
2 Aufsetzen der Teilprojekte 2.2.3.3 Konfigurationsprojekt für die Testdatenbank Für den Datenbanktest wird auf eine Datenbank zurückgegriffen, die ohne Installation auskommt und innerhalb einer beliebigen Java-Anwendung gestartet werden kann. Es ist aber ebenso praktikabel, auch während der Entwicklung auf diese Datenbank statt auf eine lokale MySQL-Installation zurückzugreifen. Damit erspart man sich Installations- und Konfigurationsaufwand. Wir erstellen das Teilprojekt mit der artifactId de.wicketpraxis– -dbconfig-test und passen die Projektdatei an. Listing 2.5 pom.xml 4.0.0 ... ${pom.groupId}--dbconfig-test ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ DB Config Test) hsqldb hsqldb ${hsqldb.version} 1.8.0.1
Ich benutze die HSQL-Datenbank für die Testumgebung. Es können natürlich beliebige andere Datenbanken benutzt werden, für die Hibernate Unterstützung anbietet.
2.2.4
Teilprojekt Persistenz
Nachdem wir nun alle Datenbankkonfigurationsprojekte angelegt (aber noch nicht konfiguriert) haben, legen wir nun das Teilprojekt (artifactId de.wicketpraxis-persistence) an, das den Datenbankzugriff regelt. Diese Projektdatei wird etwas umfangreicher, denn wir müssen die anderen Teilprojekte einbinden. Listing 2.6 pom.xml 4.0.0 ... ${pom.groupId}--persistence ${pom.groupId}--${pom.artifactId} (Wicket Praxis - \ Persistence) ${pom.groupId}
22
2.2 Aufsetzen der Teilprojekte ${pom.groupId}--dbconfig ${pom.version} ${pom.groupId} ${pom.groupId}--dbconfig-test ${pom.version} test ${pom.groupId} ${pom.groupId}--dbconfig-schema-update ${pom.version} test ${pom.groupId} ${pom.groupId}--base ${pom.version} org.springframework spring ${springframework.version} org.springframework spring-test ${springframework.version} test org.springframework spring-jdbc ${springframework.version} test org.hibernate hibernate ${hibernate.version} org.hibernate hibernate-annotations ${hibernate-annotations.version} 2.5.6 3.2.6.ga 3.3.1.GA
Im ersten Abschnitt der Abhängigkeitsverwaltung (dependencies) werden die Datenbankkonfigurationsprojekte eingebunden. Dabei werden die Konfigurationen für die Datenbanktests und die Schemaaktualisierung nur für die Unit-Tests eingebunden (test). Die Schemaaktualisierung wird wie ein Unit-Test gestartet, sodass dann auf diese Konfiguration zurückgegriffen werden kann.
23
2 Aufsetzen der Teilprojekte Im zweiten Abschnitt binden wir unsere Basisbibliothek ein. Diese Abhängigkeit wird weitervererbt, sodass wir in anderen Teilprojekten, die dieses Teilprojekt einbinden, nicht noch einmal diese Abhängigkeit definieren müssen. Im dritten Abschnitt wird Spring als Dependency Injection-Framework eingebunden. Außerdem werden Hilfsbibliotheken für die Unit-Tests eingebunden. Im vierten Abschnitt werden alle notwendigen Hibernate-Bibliotheken definiert.
2.2.5
Teilprojekt Applikationsschicht
Die Businesslogik bekommt ein eigenes Projekt (artifactId de.wicketpraxis--app). Die starke Unterteilung hilft gerade in diesem Bereich, die Grenzen der Anwendungsschichten einzuhalten. Das Verschwimmen und Durchmischen von Anwendungs- und Persistenzlogik führt in den meisten Fällen zu schwer wartbarem Code. Listing 2.7 pom.xml 4.0.0 ... ${pom.groupId}--app ${pom.groupId}--${pom.artifactId} (Wicket Praxis - App) ${pom.groupId} ${pom.groupId}--persistence ${pom.version}
Im Moment benötigt die Anwendungsschicht nur die Abhängigkeit zur Persistenzschicht. Wenn im Laufe des Projekts Funktionen hinzukommen, werden diese meist in der Anwendungsschicht realisiert. Dann werden auch die notwendigen Abhängigkeiten an dieser Stelle hinzugefügt.
2.2.6
Teilprojekt Webapp
Das vorletzte Teilprojekt (artifactId de.wicketpraxis-webapp) ist die Präsentationsschicht. Dieses Projekt erstellt dann final auch das Anwendungsarchiv (war-Datei), das anschließend in einem Servlet-Container wie z.B. Tomcat 2 oder Jetty 3 deployt werden kann.
2 3
24
http://tomcat.apache.org/ http://www.mortbay.org/jetty/
2.2 Aufsetzen der Teilprojekte Listing 2.8 pom.xml 4.0.0 de.wicketpraxis de.wicketpraxis--parentPom 1.0-SNAPSHOT ${pom.groupId}--webapp war ${pom.groupId}--${pom.artifactId} (Wicket Praxis - Webapp) org.mortbay.jetty maven-jetty-plugin 6.1.12.rc3 ${pom.groupId} ${pom.groupId}--app ${pom.version} org.apache.wicket wicket ${wicket.version} org.apache.wicket wicket-spring ${wicket.version} org.apache.wicket wicket-extensions ${wicket.version} javax.servlet servlet-api 2.4 provided jsp-api javax.servlet 2.0 provided 1.4
25
2 Aufsetzen der Teilprojekte Folgende Anpassungen sind in die Projektdatei eingeflossen: Das Jetty-Plugin (maven-jetty-plugin) startet die Webanwendung direkt aus dem Projektverzeichnis. Der Zwischenschritt, die Webanwendung in einen lokalen ServletContainer zu deployen, entfällt. Die Anwendungsschicht wird als Abhängigkeit eingebunden. Das Wicket-Framework wird hinzugefügt. Ebenso wird eine Integrationsbibliothek für Spring eingebunden. Wenn man innerhalb von Wicket auf die Klassen ServletRequest und ServletResponse zugreifen muss, bindet man die Bibliotheken mit dem Attribut scope=provided ein. Dadurch werden diese Bibliotheken nur zum Erstellen der Klassen und beim Durchführen der Tests eingebunden. Da die Webanwendung in einem Servlet-Container ausgeführt wird, würde es sonst zu Kollisionen kommen, da der Servlet-Container eigene Versionen der Bibliotheken mitbringt. Die Besonderheit eines Maven-Projekts vom Typ Webarchiv (war) liegt in dem Ergebnis des Build-Prozesses. Es muss eine lauffähige Webanwendung erstellt werden. Dafür sind allerdings noch ein paar Voraussetzungen zu erfüllen. An dieser Stelle ist es erst einmal nur notwendig, ein Verzeichnis src/main/webapp/ WEB-INF/ innerhalb des Projektverzeichnisses anzulegen. In diesem Verzeichnis muss man dann eine leere Datei web.xml erstellen. Erst jetzt kann dieses Teilprojekt erfolgreich erstellt werden.
2.2.7
Teilprojekt ParentPom – Abschluss
Die Projektdatei im Teilprojekt „parentPom“ ändern wir wie folgt: ... ../de.wicketpraxis--base ../de.wicketpraxis--dbconfig ../de.wicketpraxis--dbconfig-test ../de.wicketpraxis--dbconfig-schema-update ../de.wicketpraxis--persistence ../de.wicketpraxis--app ../de.wicketpraxis--webapp
Die Teilprojekte müssen in einer definierten Reihenfolge als Module referenziert werden. Wenn ich in diesem Projekt jetzt ein mvn install aufrufe, wird diese Funktion für jedes definierte Modul aufgerufen. Auf diese Weise kann ich das vollständige Projekt und alle Teilprojekte auf einmal erstellen lassen. In der Kombination mit mvn clean oder besser noch mvn clean install erhalte ich eine fast fertige Webanwendung, in der alle Änderungen aus den Teilprojekten enthalten sind. Jetzt wäre ein guter Zeitpunkt, die erstellten Dateien und Verzeichnisse in einer Versionsverwaltung abzuspeichern. Wer sich nicht sicher ist, welche Dateien in einer Versionsverwaltung verwaltet werden sollten, kann alle temporären Dateien mit mvn clean löschen.
26
2.3 Erstellen von Eclipse-Projektdateien
2.3
Erstellen von Eclipse-Projektdateien Alle Projekte sind angelegt. Alle weiteren Arbeiten sollten in einer entsprechenden Entwicklungsumgebung stattfinden. Um mit Eclipse auf die Maven-Projekte zuzugreifen, kann man aus dem fertigen Maven-Projekt eine entsprechende Eclipse-Projektdatei generieren lassen. Dazu wechselt man in das entsprechende Projektverzeichnis und ruft Maven mit folgenden Parametern auf: $ mvn eclipse:eclipse –DdownloadSources=true Hinweis
Unter Linux kann es notwendig sein, den Aufruf in mvn eclipse\:eclipse zu ändern, da der Doppelpunkt eine Funktion hat.
Im Verzeichnis werden dann die Eclipse-Projektdateien erstellt, die man dann in Eclipse öffnet. Man kann diesen Befehl auch im parentPom-Projektverzeichnis ausführen, und die Projektdateien werden in jedem Teilprojekt erstellt. Wenn neue Abhängigkeiten hinzugekommen sind oder Einstellungen verändert wurden, kann es notwendig sein, die Eclipse-Projektdateien noch einmal zu erstellen. Dazu sollte das Projekt in Eclipse aber geschlossen sein.
27
2 Aufsetzen der Teilprojekte
28
3 3 Mit Leben füllen Nachdem wir nun die Grundstruktur der Anwendung erstellt und alle Teilprojekte mit allen Abhängigkeiten definiert haben, füllen wir das Ganze mit Inhalt.
3.1
Konfiguration mit Spring Wie in Abschnitt 1.3.1.2 beschrieben, greifen wir für die Konfiguration der Anwendung auf das Spring-Framework zurück. Dazu erstellen wir in den verschiedenen Schichten die notwendigen Konfigurationsdateien und verknüpfen diese zu einer großen Anwendungskonfiguration. Die Konfiguration wird durch XML-Dateien beschrieben. In diesen Dateien werden Objekte () definiert, indem einem Objekt eine ID zugewiesen wird und die Attribute des Objekts mit definierten Werten belegt werden. Die verschiedenen Objekte können dann über die ID verknüpft werden, indem z.B. das Attribut des einen Objekts mit einem anderen Objekt belegt wird. Für ein besseres Verständnis sollte man sich unbedingt die Dokumentation 1 des Frameworks ansehen. Das Spring-Framework (kurz: Spring) stellt neben dem reinen Dependency InjectionMechanismus außerdem noch Hilfsfunktionen bereit, die über entsprechende XML-Tags benutzt werden können. Dazu muss man die notwendigen Definitionen in der Konfigurationsdatei einbinden. In der Dokumentation des Frameworks gibt es einen Abschnitt, der diesem Thema gewidmet ist (XML-Schema based configuration 2).
1 2
http://www.springsource.org/documentation http://static.springframework.org/spring/docs/2.0.x/reference/xsd-config.html
29
3 Mit Leben füllen
3.2
Datenbankkonfiguration In allen Datenbankkonfigurationsteilprojekten fügen wir passende Konfigurationsdateien unterhalb des Pfades src/main/resources/de/wicketpraxis/db/config/ hinzu. In unserem Beispielprojekt benutzen wir drei verschiedene Datenbankkonfigurationen: Die erste Datenbank ist eine Datenbank auf dem Entwicklungsrechner oder die Produktivdatenbank, da in beiden Fällen derselbe Datenbanktreiber und dieselbe Einstellung benutzt wird. Die zweite Datenbank ist eine InMemory-Datenbank, die nur für die Unit-Tests benutzt und nach dem Beenden der Tests gelöscht wird. Die dritte Datenbank entspricht der ersten Datenbank, allerdings mit veränderten Einstellungen. Die Anpassungen veranlassen Hibernate, das Datenbankschema an die Struktur unserer Persistenzschicht anzupassen. Dazu werden die notwendigen Tabellen erzeugt und datenbankabhängige Optimierungen vorgenommen.
3.2.1
Teilprojekt dbconfig
Wir erstellen in dem oben erwähnten Pfad die Konfiguration für die Produktivdatenbank. Diese Einstellung wird auch benutzt, wenn die Anwendung lokal ausgeführt wird. Listing 3.1 dbconfig.xml
Es werden zwei Objekte definiert: Das Objekt datasource konfiguriert den JDBC-Datenbankzugriff. Dafür sind neben der Datenbanktreiberklasse das Login, das Passwort, der Servername sowie die JDBCURL anzugeben.
30
3.2 Datenbankkonfiguration Im Objekt hibernateProperties werden Attribute gesetzt, die von Hibernate ausgewertet werden. Durch das Setzen des Attributs hibernate.dialect kann Hibernate auf einen Tabellentyp zurückgreifen, der Transaktionen unterstützt.
3.2.2
Teilprojekt dbconfig-test
Wie eben für das Teilprojekt dbconfig erstellen wir im oben erwähnten Pfad, aber im Teilprojekt dbconfig-test die Datei dbconfig-test.xml mit einem an dbconfig.xml angelehnten Inhalt. Listing 3.2 dbconfig-test.xml
Damit wird für die Unit-Tests eine InMemory-HSQL-Datenbank konfiguriert. Außerdem wird Hibernate so konfiguriert, dass vor Beginn des Tests das Datenbankschema automatisch erzeugt und nach dem Test wieder automatisch gelöscht wird. So findet jeder Test die Datenbank immer im selben Zustand vor. Hilfreich ist außerdem, dass die resultierenden SQL-Abfragen ausgegeben werden. Wem die umfangreiche Ausgabe zu viel ist, kann die formatierte Ausgabe (format_sql=false) auch abschalten. Das Debuggen der Anwendung gestaltet sich einfacher, wenn der Reflection-Optimizer deaktiviert ist.
3.2.3
Teilprojekt dbconfig-schema-update
Wie wir in der letzten Konfiguration gesehen haben, kann Hibernate das Schema einer Datenbank automatisch erzeugen. Das machen wir uns zunutze, wenn wir jetzt eine Konfiguration erstellen, mit der wir das Erzeugen des Datenbank-Schemas erzwingen können. Wir legen die Datei dbconfig-schema-update.xml an, die sich ebenfalls an dbconfig.xml orientiert.
31
3 Mit Leben füllen Listing 3.3 dbconfig-schema-update.xml
Wie bei dbconfig-test.xml wird ein Abgleich zwischen geforderter und vorhandener Datenbankstruktur durchgeführt. Allerdings werden die alten Daten dabei nicht gelöscht (hibernate.hbm2ddl.auto=update), sondern nur angepasst. Dieses Vorgehen kann immer dann zu Problemen führen, wenn man Attribute (und damit den resultierenden Spaltennamen der Tabelle) umbenannt hat. Dann wird eine neue Spalte erzeugt. Die alte Spalte wird jedoch nicht gelöscht. In solchen Fällen sollte man die Datenstruktur nachträglich von Hand anpassen. Es ist auf diese Weise natürlich ohne Weiteres möglich, auch das Schema einer Produktivdatenbank automatisch anpassen zu lassen. Das Risiko besteht dann allerdings darin, dass das Produktivsystem dadurch in einen unbrauchbaren Zustand versetzt wird und die Anwendung nicht mehr funktioniert. Daher empfehle ich, das Ändern des Schemas der Produktivdatenbank immer von Hand oder durch die Unterstützung von darauf spezialisierten Anwendungen durchzuführen.
3.2.4
Schemagenerierung mit Hibernate
Folgende Werte sind für das Attribut hibernate.hbm2dll.auto möglich: Wert
Bedeutung
validate
prüft die Gültigkeit der Datenbanktabellen
update
erstellt und aktualisiert die Datenbanktabellen
create
erstellt die Datenbanktabellen initial
create-drop
erstellt die Datenbanktabelle beim Start und löscht sie am Ende
Für die Testdatenbank benutzen wir daher am besten create-drop. Für das SchemaUpdate auf die lokale Entwicklungsdatenbank benutzen wir update. Für den normalen Zugriff würde sich die Verwendung von validate anbieten. Es empfiehlt sich aber, auf die Angabe vollständig zu verzichten, weil Datenbanken ihrerseits die gewünschten Typen
32
3.3 Persistenz selbst noch einmal anpassen. Damit passt das von Hibernate erwartete Ergebnis nicht zu dem von der Datenbank zurückgemeldeten, was dann einen Fehler in der Anwendung hervorruft.
3.3
Persistenz Nachdem wir die Datenbankkonfigurationen erstellt haben, können wir uns mit dem Datenbankzugriff beschäftigen. Damit wir das Rad nicht jedes Mal neu erfinden, erstellen wir ein paar Hilfsklassen, die allen abgeleiteten Klassen häufig benutzte Funktionen zur Verfügung stellt. Dazu wechseln wir in das entsprechende Teilprojekt persistence.
3.3.1
Datenbankzugriff – Allgemeine Schnittstellendefinition
Alle Datenbankobjekte (Do), also Objekte, die durch die Persistenzschicht in Tabellen abgelegt werden, müssen folgendes Interface implementieren: Listing 3.4 DoInterface.java package de.wicketpraxis.persistence; import java.io.Serializable; public interface DoInterface extends Serializable { public K getId(); }
Dadurch ist es möglich, die eindeutige Identifikation für ein Datenbankobjekt zu ermitteln, ohne die genaue Klasse des Objektes zu kennen. Die Datenbankzugriffsklasse (Data Access Object, DAO) muss ebenfalls ein entsprechendes Interface implementieren: Listing 3.5 DaoInterface.java package de.wicketpraxis.persistence; ... public { void void void
interface DaoInterface delete(T o); save(T o); update(T o);
T get(K id); T getNew(); public List findAll(int offset,int max); public int countAll(); }
Wenn dieses Interface vollständig implementiert ist, kann man über diese Schnittstelle neue Datenbankobjekte erstellen, diese speichern, verändern, über eine ID suchen und letztendlich auch löschen. Außerdem können alle Objekte in einer Liste dargestellt werden.
33
3 Mit Leben füllen
3.3.2
Datenbankzugriff – Hilfsklassen
Der Datenbankzugriff, der über die eben definierten Schnittstellen möglich ist, muss nicht für jede Klasse neu implementiert werden. Es bietet sich an, diese Funktionen in eine abstrakte Klasse auszulagern, von der dann die konkreten Klassen abgeleitet werden. Außerdem können wir an dieser Stelle die Integration mit Hibernate über Spring realisieren, sodass in der abgeleiteten Klasse nur noch sehr wenige Anpassungen notwendig sind. Listing 3.6 AbstractDao.java package de.wicketpraxis.persistence; ... import org.hibernate.*; import org.springframework.transaction.annotation.Transactional; public abstract class AbstractDao implements DaoInterface { private static final Logger _logger = \ LoggerFactory.getLogger(AbstractDao.class); private Class _domainClass; private SessionFactory _sessionFactory; protected AbstractDao(Class domainClass) { _domainClass=domainClass; } public SessionFactory getSessionFactory() { return _sessionFactory; } public void setSessionFactory(SessionFactory sessionFactory) { _sessionFactory = sessionFactory; } public Session getSession() { return _sessionFactory.getCurrentSession(); } protected Class getDomainClass() { return _domainClass; } protected Criteria getCriteria() { return getSession().createCriteria(_domainClass); } @Transactional public void delete(T object) { getSession().delete(object); } @Transactional public void save(T object) { getSession().save(object); }
34
3.3 Persistenz @Transactional public void update(T object) { getSession().update(object); } public T get(K id) { return (T) getSession().get(_domainClass, id); } public T getNew() { try { return _domainClass.newInstance(); } catch (InstantiationException e) { _logger.error("newInstance failed",e); } catch (IllegalAccessException e) { _logger.error("newInstance failed",e); } return null; } public List findAll(int offset,int max) { Criteria criteria = getSession().createCriteria(_domainClass); if (offset!=0) criteria.setFirstResult(offset); if (max!=0) criteria.setMaxResults(max); return (List) criteria.list(); } public int countAll() { Criteria criteria = getSession().createCriteria(_domainClass); criteria.setProjection(Projections.rowCount()); return (Integer) criteria.uniqueResult(); } }
Wie man sieht, konnten alle Funktionen aus der Interface-Definition umgesetzt werden. Wichtig ist es, darauf hinzuweisen, dass die Annotation @Transactional für die Transaktionssteuerung benutzt wird. Spring kümmert sich bei einer so markierten Methode darum, dass vor dem Aufruf eine Transaktion gestartet und am Ende der Methode geschlossen wird. Hinweis
In abgeleiteten Klassen muss die Annotation @Transactional neu gesetzt werden, da Annotationen nicht vererbt werden.
3.3.3
Datenbankzugriff – User
Für unsere Beispielanwendung greifen wir auf eine sehr einfach gehaltene Nutzerdatenbank zurück.
35
3 Mit Leben füllen Listing 3.7 User.java package de.wicketpraxis.persistence.beans; import javax.persistence.*; ... @Entity @Table(name="Users") public class User implements DoInterface { Integer _id; String _eMail; String _name; @Id @GeneratedValue(strategy=GenerationType.AUTO) public Integer getId() { return _id; } public void setId(Integer id) { _id=id; } @Column(nullable=false,unique=true,name="email") public String getEMail() { return _eMail; } public void setEMail(String mail) { _eMail = mail; } @Column(nullable=false) public String getName() { return _name; } public void setName(String name) { _name = name; } }
Die Klasse ist jetzt noch sehr übersichtlich. Wer bereits weitere Attribute identifiziert hat, kann diese einfach hinzufügen. Der Zugriff auf die Nutzer erfolgt dann über eine eigene Klasse. Diese Klasse leitet von AbstractDao ab und erbt somit alle grundlegenden Datenbankfunktionen. Wir fügen in diesem Fall noch eine Methode hinzu, die einen Datensatz anhand der E-Mail ermittelt und als Ergebnis zurückgibt. Listing 3.8 UserDao.java package de.wicketpraxis.persistence.dao; import org.hibernate.criterion.*; ... public class UserDao extends AbstractDao { public static final String BEAN_ID="userDao";
36
3.3 Persistenz public UserDao() { super(User.class); } public User getByEMail(String email) { return (User) getCriteria().add(Property.forName("EMail").eq(email)) .uniqueResult(); } }
Damit haben wir alle Klassen zusammen, die für den Datenbankzugriff verantwortlich sind. Jetzt müssen wir die Persistenzschicht noch richtig konfigurieren.
3.3.4
Datenbankzugriff – Konfiguration
Wir legen eine Konfigurationsdatei im Verzeichnis src/main/resources/de/wicketpraxis/persistence/ an. Listing 3.9 bean-config.xml de.wicketpraxis.persistence.beans.User
Die von Spring bereitgestellte Session Factory (sessionFactory) wertet die Annotationen in der User-Klasse aus. An diese Session Factory, die für das Erzeugen einer HibernateSession zuständig ist, wird ein Transaktionsmanager angebunden. Die Anweisung veranlasst Spring dazu, die Annotationen in den Zugriffsklassen auszuwerten und für die saubere Transaktionsbehandlung Proxy-Klassen für die
37
3 Mit Leben füllen Zugriffsklassen bereitzustellen. Das Attribut proxy-target-class=true zwingt Spring dabei, auf die Verwendung von dynamischen Proxies zu verzichten und Proxies mithilfe des cglib Frameworks bereitzustellen. Dadurch ist es nicht mehr notwendig, ein spezielles Interface pro Klasse zu implementieren. Das erzeugte Zwischenobjekt ruft die Funktionen der eigentlichen Klasse auf und führt dabei vorher und nachher die notwendigen Transaktionsfunktionen aus. Wir haben jetzt die Persistenzschicht konfiguriert, aber noch keine Datenbankschnittstelle angebunden. Dazu erstellen wir eine weitere Konfigurationsdatei im selben Verzeichnis. Listing 3.10 persistence.xml
In dieser Datei greifen wir auf die Datenbankkonfiguration für die Produktivdatenbank zu und referenzieren die Konfiguration der Persistenzschicht.
3.3.5
Persistenz-Tests
Wie bereits erwähnt, kann man mit Hibernate relativ einfach Unit-Tests mit einer temporären Datenbank durchführen. Für die Tests legen wir eine entsprechende Konfiguration an. Diese Konfigurationsdatei legen wir aber im Verzeichnis src/test/resources/de/ wicketpraxis/persistence/ ab, damit diese nur für die Tests benutzt wird und nicht im fertigen Projektarchiv auftaucht. Listing 3.11 persistence-test.xml
Wir benutzen dieselbe bean-config.xml jetzt mit der Datenbankkonfiguration für die Testdatenbank. In den Unit-Tests müssen wir daher die Konfigurationsdatei persistence-test.xml verwenden. Als Basisklasse für alle weiteren Tests leiten wir eine eigene Klasse von einer Unit-Test-Hilfsklasse des Spring-Frameworks ab. Wir erweitern die Klasse ein wenig und leiten alle konkreten Testklassen von dieser Klasse ab: Listing 3.12 AbstractTest.java package de.wicketpraxis.persistence; import org.springframework.test. \
38
3.3 Persistenz AbstractTransactionalDataSourceSpringContextTests; public abstract class AbstractTest \ extends AbstractTransactionalDataSourceSpringContextTests { @Override protected String[] getConfigLocations() { return new String[] \ { "classpath:/de/wicketpraxis/persistence/persistence-test.xml" }; } protected T getBean(String name,Class requiredType) { return (T) getApplicationContext().getBean(name, requiredType); } protected T getBean(Class requiredType) { Map beansOfType = getApplicationContext(). \ getBeansOfType(requiredType); if (beansOfType.size()==1) { return (T) new ArrayList(beansOfType.values()).get(0); } return null; } }
Der Unit-Test für die User-Klasse gestaltet sich recht übersichtlich. Das liegt sicher auch daran, dass wir bisher nicht allzu viele Funktionen realisiert haben. Listing 3.13 TestUserDao.java package de.wicketpraxis.persistence.dao; import junit.framework.Assert; ... public class TestUserDao extends AbstractTest { public void testKeinNutzer() { UserDao userDao = getBean(UserDao.BEAN_ID,UserDao.class); User user = userDao.get(1); Assert.assertNull("Kein Nutzer",user); } public void testEinNutzer() { UserDao userDao = getBean(UserDao.BEAN_ID,UserDao.class); User nutzer=new User(); String email = "
[email protected]"; nutzer.setEMail(email); nutzer.setName("Klaus"); userDao.save(nutzer); nutzer=userDao.get(nutzer.getId()); Assert.assertNotNull("User",nutzer); Assert.assertEquals("Email",email,nutzer.getEMail()); nutzer=userDao.getByEMail(email); Assert.assertNotNull("User",nutzer); Assert.assertEquals("Email",email,nutzer.getEMail()); } }
39
3 Mit Leben füllen Der erste Test prüft, ob ein Nutzer mit der ID=1 bereits in der Datenbank ist. Das sollte nicht der Fall sein, weil bei jedem Test die Datenbank wieder gelöscht wird. Im zweiten Test wird geprüft, ob der Nutzer angelegt, gelesen und anhand der E-Mail gefunden werden kann. Diesen Test kann man mit mvn test ausführen. Wenn keine Fehler aufgetreten sind, kann man dieses Teilprojekt mit mvn install erstellen lassen. Die Unit-Tests werden dabei jedes Mal ausgeführt. Als einfacher Test, ob die Unit-Tests tatsächlich ausgeführt werden, kann man z.B. den Namen in setName auf einen anderen Wert ändern. Der Test sollte dann fehlschlagen.
3.3.6
Schema-Update
Hibernate prüft je nach Einstellung vor dem ersten Datenbankzugriff, ob an der in der Datenbank vorhandenen Tabellenstruktur Anpassungen notwendig sind. Je nach Einstellung führt Hibernate dann die entsprechenden Anpassungen durch. Gerade wer wenig Erfahrungen in Bezug auf Datenbanken hat, ist meist besser damit beraten, auf diese Funktion von Hibernate zurückzugreifen. Das Erzeugen und Ändern von Tabellen und Spalten sollte aber nicht jedem Datenbanknutzer erlaubt sein. Daher empfiehlt es sich, die SchemaUpdate-Funktion nicht automatisch auszuführen, und dafür einen gesonderten Datenbanknutzer mit erweiterten Rechten zu nehmen. Diese Einstellungen haben wir in die dbconfig-schema-update.xml einfließen lassen. Um die Funktion auszuführen, legen wir erst eine eigene Konfigurationsdatei im Verzeichnis src/test/resources/de/wicketpraxis/persistence/ an. Listing 3.14 persistence-schema-update.xml
Diese Datei unterscheidet sich von persistence-test.xml nur an der Stelle, an der wir die Datenbankkonfiguration auswählen. Wir benutzen für den Start von Hibernate mit der entsprechenden Konfiguration dasselbe Verfahren wie bei den Persistenz-Tests. Damit dieser „Test“ aber nur bei Bedarf ausgeführt wird, sollte im Klassenname das Wort „Test“ nicht vorkommen. Listing 3.15 SchemaUpdate.java package de.wicketpraxis.persistence; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SchemaUpdate extends AbstractTest { private static final Logger _logger = LoggerFactory.getLogger(SchemaUpdate.class);
40
3.4 Anwendungsschicht @Override protected String[] getConfigLocations() { return new String[]{ \ "classpath:/de/wicketpraxis/ \ persistence/persistence-schema-update.xml" }; } public void testSchemaUpdate() { _logger.error("Schema Update"); } }
Die Testfunktion hat dann eigentlich keine Funktion. Im Vorfeld wird Hibernate mit der referenzierten Datenbankkonfiguration gestartet und das Schema angepasst. Um den Test und damit das Schema-Update zu starten, rufen wir Maven wie folgt auf: $mvn –Dtest=de/wicketpraxis/persistence/SchemaUpdate test
Das Teilprojekt ist jetzt soweit fertig. Wir haben durch Datenbanktests die Funktion getestet, wir können per Schema-Update die Datenbanktabellen anlegen lassen und mit der Standardkonfiguration aus der Anwendung heraus dann darauf zugreifen.
3.4
Anwendungsschicht Wir wechseln in das Teilprojekt app. Da wir in dieser Schicht im Moment keine Funktionen implementieren, reicht es aus, wenn wir eine Konfigurationsdatei im Verzeichnis src/main/resources/de/wicketpraxis/app/ erstellen. Listing 3.16 app.xml
In dieser Datei wird die Konfiguration aus der Persistenzschicht eingebunden. Mehr ist an dieser Stelle nicht notwendig.
3.5
Präsentationsschicht Wir haben jetzt alle untergeordneten Anwendungsschichten erstellt. Als letztes Teilprojekt vervollständigen wir die Präsentationsschicht.
3.5.1
Hilfsklasse für Maven-Projekte
Wicket lädt im Entwicklungsmodus die Markup-Dateien für die Komponenten bei jeder Veränderung neu. Dabei wird aber auf Dateien in target zugegriffen, also auf eine Kopie
41
3 Mit Leben füllen von src/main/resources. Damit im richtigen Verzeichnis gesucht wird, müsste man Anpassungen in der Projektbeschreibung (pom.xml) vornehmen. Leider funktioniert diese Methode nicht zuverlässig, sodass ich nach Alternativen gesucht habe. Glücklicherweise bietet Wicket die Möglichkeit, die Anwendung in diesem Aspekt anzupassen. Die folgende Klasse sucht die Ressourcen, die Wicket benötigt, zuerst in dem in Maven-Projekten definierten Standard-Ressourcenpfad (src/main/resources). Listing 3.17 MavenDevResourceStreamLocator.java package de.wicketpraxis.wicket.util.resource; ... import org.apache.wicket.util.resource.*; import org.apache.wicket.util.resource.locator.ResourceStreamLocator; public class MavenDevResourceStreamLocator extends ResourceStreamLocator { String _prefix="src/main/resources/"; @Override public IResourceStream locate(Class clazz, String path) { IResourceStream located=getFileSysResourceStream(path); if (located != null) return located; return super.locate(clazz, path); } private IResourceStream getFileSysResourceStream(String path) { File f=new File(_prefix+path); if ((f.exists()) && (f.isFile())) { return new FileResourceStream(f); } return null; } }
3.5.2
Wicket Web Application
Die kleinste Wicket-Anwendung besteht aus mindestens einer Seite und einer von WebApplication abgeleiteten Klasse. Da unsere kleine Anwendung aber z.B. bereits über
eine Datenbankanbindung verfügt, wird sie an dieser Stelle etwas komplexer. Natürlich müssen wir daher auf Funktionen zurückgreifen, die bisher nicht erklärt wurden, die aber bereits einen guten Eindruck vermitteln können, wie eine Webanwendung mit Wicket funktioniert. Dieses Grundgerüst kann dann als Basis für alle folgenden Beispiele benutzt werden. Zuerst legen wir eine Seite an, die dann in der Applikationsklasse als Startseite definiert werden kann. Listing 3.18 Start.java package de.wicketpraxis.web.pages; ... public class Start extends WebPage
42
3.5 Präsentationsschicht { @SpringBean(name=UserDao.BEAN_ID) UserDao _userDao; public Start() { LoadableDetachableModel userModel= new LoadableDetachableModel() { @Override protected List load() { return _userDao.findAll(0, 10); } }; ListView userList=new ListView("userList",userModel) { @Override protected void populateItem(ListItem item) { item.add(new Label("name",item.getModelObject().getName())); } }; add(userList); } }
Die Annotation @SpringBean definiert, wie das Feld initialisiert werden soll. Dabei wird über einen optional zu definierenden Namen auf die Spring-Konfiguration zurückgegriffen und nach einer Spring-Bean vom Typ UserDao und der optionalen ID mit dem Wert aus dem Attribut name gesucht. Dieses Verhalten muss noch in der Applikation definiert werden. Das Objekt userModel sorgt dafür, dass bei Bedarf die Liste der ersten 10 Nutzer aus der Datenbank geladen werden. Das Objekt userList ist für die Anzeige zuständig und fügt für jeden Eintrag der Liste ein Label mit dem Nutzernamen hinzu. Jetzt benötigen wir noch eine passende Markup-Datei für die Seite und legen eine Datei im Verzeichnis src/main/resources/de/wicketpraxis/web/pages/ an. Listing 3.19 Start.html WicketPraxis WicketPraxis
43
3 Mit Leben füllen Die Attribute wicket:id referenzieren dabei die Komponenten in unserer Start-Klasse. Als Nächstes müssen wir unsere eigene Applikationsklasse erstellen. Listing 3.20 WicketPraxisApplication.java package de.wicketpraxis.web; ... public class WicketPraxisApplication extends WebApplication { @Override protected void init() { super.init(); addComponentInstantiationListener(new SpringComponentInjector(this)); if (DEVELOPMENT.equalsIgnoreCase(getConfigurationType())) { getResourceSettings(). \ setResourceStreamLocator(new MavenDevResourceStreamLocator()); } } @Override public Class wicketpraxis.com WicketPraxis Webapp contextConfigLocation /WEB-INF/applicationContext.xml org.springframework.web.context.ContextLoaderListener de.wicketpraxis.webapp org.apache.wicket.protocol.http.WicketFilter applicationClassName de.wicketpraxis.web.WicketPraxisApplication configuration deployment de.wicketpraxis.hibernate.osv org.springframework.orm.hibernate3.support.OpenSessionInViewFilter de.wicketpraxis.hibernate.osv /* de.wicketpraxis.webapp /*
An erster Stelle wird eine Spring-Konfiguration im selben Verzeichnis angegeben (/WEBINF/applicationContext.xml). Auf diese Konfiguration wird sowohl von Wicket als
auch durch den OpenSessionInViewFilter zugegriffen. Der ContextLoaderListener stellt einen aus der Konfiguration ermittelten ApplicationContext bereit und sorgt beim Beenden des Servers für das Herunterfahren des Context. Im WicketFilter geben wir nun unsere eigene Wicket-Anwendung an und setzen den Startmodus auf deployment. Damit wird das Anwendungsarchiv standardmäßig mit diesem Modus erstellt. Das Öffnen und Schließen der Session für den Datenbankzugriff über Hibernate übernimmt der OpenSessionInViewFilter. Die Reihenfolge der Filter im Bereich filter-mapping ist daher
45
3 Mit Leben füllen wichtig. Die Anfrage durchläuft den erstgenannten Filter vor dem nächsten. Damit ist sichergestellt, dass die Datenbankverbindung geöffnet ist, wenn die Anfrage durch Wicket behandelt wird.
3.5.4
Spring-Konfiguration
Wir erstellen jetzt im selben Verzeichnis die referenzierte Konfigurationsdatei für den ApplicationContext. Dabei könnten wir zwar direkt unsere app.xml einbinden. Ich empfehle aber einen Umweg, sodass a) jede Schicht die Konfigurationsdatei an einer nachvollziehbaren Position ablegt und b) in der Konfigurationsdatei für den Servlet-Container keine zusätzlichen Einstellungen abgelegt werden. Für den Fall, das Unit-Tests für die Präsentationsschicht durchgeführt werden müssten, wäre es sehr schwierig (wenn nicht unmöglich), an diese Datei heranzukommen. Listing 3.22 applicationContext.xml
In diesem binden wir die noch zu erstellende Datei webapp.xml aus dem Ressourcenpfad ein. Danach erstellen wir diese Datei im Verzeichnis src/main/resources/de/wicketpraxis/web/. Listing 3.23 webapp.xml
Die Anwendung ist damit vollständig konfiguriert und kann jetzt gestartet werden.
3.5.5
Start der Anwendung
Wir haben jetzt alles zusammen. Alle Teilprojekte sollten, wenn noch nicht geschehen, über das ParentPom-Projekt erstellt (mvn install) werden. Dann wechseln wir in das Webapp-Projekt und starten die Webanwendung mit: $ mvn jetty:run –Dwicket.configuration=development
Der eingebettete Webserver sollte starten und den erfolgreichen Start mit folgender Meldung quittieren: ... INFO: [WicketPraxisApplication] Started Wicket version 1.4 in development mode ******************************************************************** *** WARNING: Wicket is running in DEVELOPMENT mode. *** *** ^^^^^^^^^^^ *** *** Do NOT deploy to your live server(s) without changing this. *** *** See Application#getConfigurationType() for more information. *** ******************************************************************** 2009-01-13 19:18:17.161::INFO: Started
[email protected]:8080 [INFO] Started Jetty Server
46
3.5 Präsentationsschicht Damit wurde der Server erfolgreich gestartet. Man öffnet nun mit dem Browser die Seite http://localhost:8080/. Dort erscheint eine Fehlermeldung mit einem Link, der auf unsere Webanwendung verweist. Nach einem Klick sollte man ein Ergebnis wie in Abbildung 3.1 sehen.
Abbildung 3.1 Startseite mit Daten aus der Datenbank
Wenn man wie ich schon einen Eintrag in die Datenbank getätigt hat, sieht man in der Ergebnistabelle auch schon einen Eintrag. Damit ist die Anwendung funktionsfähig, und jede der notwendigen Schichten ist erstellt und eingebunden. Hinweis
Wicket unterscheidet zwischen den Modi Deployment und Development. Alle erweiterten Informationen, die im Development-Modus angezeigt werden, werden durch den Modus Deployment deaktiviert. Für die Entwicklung sollte der Development-Modus gewählt werden, weil das die Fehlersuche erheblich vereinfacht.
47
3 Mit Leben füllen
48
4 4 Die Wicket-Architektur Nachdem wir nun unsere erste kleine Webanwendung erstellt haben, möchte ich etwas genauer auf die Architektur von Wicket eingehen, bevor wir uns in den nächsten Kapiteln mit den einzelnen Komponenten beschäftigen.
4.1
Wicket und das HTTP-Protokoll Eine Webanwendung benutzt das HTTP-Protokoll, um mit dem Browser zu kommunizieren. Über dieses Protokoll wird eine Anfrage an den Server geschickt und die Antwort an den Browser übermittelt. Das Protokoll dient dem Transport der Daten und ist selbst zustandslos. Das bedeutet, dass eine Anfrage an den Server in keiner Beziehung zu einer vorangegangenen Antwort stehen muss. Diese Zustandslosigkeit des Protokolls verursacht eine Reihe von Problemen, die in den verschiedenen Webframeworks unterschiedlich gelöst werden. Wicket orientiert sich dabei stark an einer Desktop-Anwendung, sodass man als Entwickler nur selten damit konfrontiert wird, dass es sich immer noch um eine Webanwendung handelt. Dennoch unterstützt Wicket den Entwickler mit einer Reihe von Schnittstellen, die weiterhin den Zugriff auf das Transportprotokoll ermöglichen oder davon abstrahiert bestimmte Funktionen zur Verfügung stellt.
4.2
Struktur Wicket besitzt verschiedene Elemente, die in einer Webanwendung zusammenspielen. Ich werde jedes im Folgenden etwas eingehender erläutern, sodass sich ein Gesamtbild ergibt, was für das Verständnis von Wicket hilfreich ist.
49
4 Die Wicket-Architektur
4.2.1
WebApplication
Wie wir bereits gesehen haben, müssen wir für unsere eigene Anwendung eine von WebApplication abgeleitete Klasse erstellen. Von dieser Klasse wird auf dem Server nur eine Instanz pro Anwendung erzeugt. Die init-Methode wird beim Start nur einmal ausgeführt, sodass an dieser Stelle alle Einstellungen vorgenommen werden können, die für die Anwendung gelten sollen. Außerdem gibt es verschiedene Methoden, die man mit eigenen Implementierungen überladen und so die Anwendung an die eigenen Bedürfnisse anpassen kann.
4.2.2
Session
Jeder Nutzer, der auf die Anwendung zugreift bekommt eine Session zugewiesen. Das ist notwendig, da Wicket alle Informationen, die für eine Nutzerinteraktion notwendig sind, in dieser Session speichert. Während einige andere Frameworks versuchen, den Zustand einer Anwendung in URL-Parametern abzulegen, überträgt Wicket in dieser Phase nur die vom Nutzer durchgeführte Aktion. Die Daten einer Session werden in einem SessionStore gespeichert. Normalerweise werden die Daten in einer HttpSession aus dem javax.servlet-Paket gespeichert. Man könnte allerdings auch seinen eigenen SessionStore implementieren.
4.2.3
PageMap
Jede Session hat mindestens eine PageMap. In dieser PageMap sind die Seiten (Page) abgelegt, die der Nutzer aufgerufen hat. Neben der zuletzt besuchten Version einer Seite finden sich in der PageMap auch ältere Versionen der Seite wieder. Dadurch kann der Nutzer im Browser auf die letzten Seiten zurückspringen, ohne dass es zu Fehlern kommt, weil Wicket den Zustand der Anwendung zu diesem Zeitpunkt wiederherstellen kann. Wenn ein Nutzer mehr als ein Browserfenster geöffnet hat (z.B. mit einem Popup), legt Wicket mehr als eine PageMap an. Auch wenn diese Vorgehensweise dafür sorgt, dass jederzeit die korrekten Funktionen aufgerufen werden, muss man sich als Entwickler nicht mit solchen Details beschäftigen, denn dieser Vorgang geschieht automatisch und vollkommen transparent.
4.2.4
Page
Eine Seite (Page) ist eine Komponente, die sich von anderen Wicket-Komponenten nur dadurch unterscheidet, dass alle anderen Komponenten immer zu einer Seite gehören. Die Seite ist damit die oberste Komponente im Komponentenbaum. Man kann sich eine Page auch als Browserfenster vorstellen. Die Darstellung einer Seite liefert als Ergebnis die HTML-Daten, die der Browser dann darstellt.
50
4.3 Request-Behandlung
4.2.5
PageStore
Wicket hält die aktuellste Seite der PageMap im Speicher vor. Alle anderen Seiten werden im PageStore abgelegt. Im Standardfall wird dazu die Seite serialisiert und in einem DiskPageStore, also auf der Festplatte abgelegt. Bei Bedarf wird die Seite wieder aus dem PageStore geladen. Auf diese Weise hält sich der Speicherverbrauch in Grenzen, ohne an Funktionalität einzubüßen.
4.2.6
Component
Eine Komponente ist die Basiseinheit einer Wicket-Anwendung. Alle Wicket-Komponenten sind von dieser Klasse abgeleitet.
4.3
Request-Behandlung Wicket kapselt die bei einer Webanwendung beteiligten HttpRequest- und HttpResponse-Klassen. Diese werden durch einen RequestCycle abgearbeitet, wobei die Behandlung der verschiedenen Phasen an einen RequestCycleProcessor übergeben werden. Im RequestCycle werden folgende Phasen durchlaufen: PREPARE_REQUEST: Startet die Request-Verarbeitung. RESOLVE_TARGET: Ermittelt das Ziel dieser Abfrage (RequestTarget) durch den RequestCycleProcessor. PROCESS_EVENTS: Die Event-Behandlung durch den RequestCycleProcessor wird
gestartet. RESPOND: Der Response wird durch den RequestCycleProcessor erstellt. DETACH_REQUEST: Alle temporären Daten werden gelöscht, indem für jede Komponente die Methode detach() aufgerufen wird. Danach wird die Seite in der PageMap abgelegt. Zusätzlich wird die Seite serialisiert, damit sie im PageStore abgelegt werden kann. DONE: Die Abarbeitung ist abgeschlossen, der nächste Request kann verarbeitet werden. Hinweis
Wenn an dieser Stelle eine Komponente noch Referenzen auf Objekte hat, die nicht serialisiert werden können, kann Wicket die ganze Seite nicht serialisieren und somit nicht in den PageStore schreiben. Das kann zu Problemen führen, wenn man später auf diese Seite zugreifen möchte. Wenn zu einem späteren Zeitpunkt auf diese Seite zugegriffen wird, kann Wicket diese nicht im PageStore finden und gibt eine Fehlermeldung aus.
Der RequestCycleProcessor stellt dabei folgende Funktionen bereit: resolve(RequestCycle, RequestParameters): Die URL und die URL-Parameter
werden dekodiert und das RequestTarget ermittelt.
51
4 Die Wicket-Architektur processEvents(RequestCycle): Wenn das RequestTarget ermittelt wurde, werden die Events verarbeitet. Dabei werden dann Events wie der Klick auf einen Link oder das Abschicken eines Formulars ausgewertet (IRequestListener z.B. IlinkListener). respond(RequestCycle): Nachdem die Events verarbeitet wurden, wird das Ergebnis gerendert und an den Browser zurückgeschickt. respond(RuntimeException, RequestCycle): Wenn ein Fehler aufgetreten ist, der
nicht abgefangen wurde, wird diese Methode aufgerufen, um auf diesen Zustand geeignet zu reagieren. Im Entwicklungsmodus wird z.B. eine andere, mit mehr Informationen versehene Fehlerseite dargestellt. Was dabei an den Browser zurückgeschickt wird, hängt natürlich davon ab, ob die Anfrage eine Seite oder ein Bild zurückliefern soll, oder ob das Ergebnis für eine Ajax-Anfrage aufbereitet werden muss.
4.3.1
Komponentenphasen
Jede Komponente hat neben dem Lebenszyklus, der mit dem Erstellen der Komponente über einen Konstruktor beginnt und mit dem Bereinigen durch den Garbage Collector endet, auch einen Request-Zyklus. Dabei sind die wesentlichen Phasen: Request-Behandlung: Die durch den Request beschriebene Aktion wird durchgeführt (Abschnitt 4.3.1.1). onBeforeRender: Wenn die Komponente sichtbar ist, dann wird, bevor die Komponente dargestellt wird, die onBeforeRender-Methode aufgerufen. Dabei kann z.B. die Sichtbarkeit manipuliert werden, was Einfluss auf diesen und den nächsten Schritt hat. onRender: wird aufgerufen, wenn die Komponente dargestellt wird. onAfterRender: wird immer aufgerufen, auch wenn die Komponente unsichtbar ist. onDetach: wird danach aufgerufen und sorgt dafür, dass die temporären Daten gelöscht werden können, damit die Session nur soviel Platz wie nötig belegt.
4.3.1.1 Request-Behandlung Die durch den Request ausgelösten Aktionen werden durchgeführt. Dabei werden zuerst die Komponenten gesucht, bei der eine Aktion ausgelöst wurde. Wenn die Komponente gefunden wurde, wird die Aktion ausgelöst, die dann z.B. im Fall eines Links dazu führt, dass irgendwann onClick() aufgerufen wird. Bei Formularen werden die durch den Request übergebenen Werte der Formularkomponenten verarbeitet.
4.3.2
Nebenläufigkeit – Threads
Pro Request wird ein Thread ausgeführt, sodass man sich normalerweise keine Gedanken um dieses Thema machen muss – mit einer wichtigen Ausnahme: die Session. Da ein Nut-
52
4.4 Komponenten, Modelle, Markup zer einer Session zugewiesen ist, kann es passieren, dass der Zugriff auf die Session gleichzeitig erfolgt. Daher muss man selbst sicherstellen, dass der Zugriff synchronisiert erfolgt.
4.4
Komponenten, Modelle, Markup Wicket ist ein MVC-Framework. MVC steht dabei für Model, View und Controller. Das Model stellt die Daten bereit, die durch den Controller verändert werden können und die durch eine View dargestellt werden.
4.4.1
Komponenten
Die kleinste Einheit einer Wicket-Anwendung ist die Komponente. Eine Komponente übernimmt dabei die Funktion eines Controllers, wobei das Framework dafür sorgt, dass die Aktion des Nutzers der richtigen Komponente zugeordnet werden kann und die Komplexität dieser Verarbeitung vollständig kapselt.
4.4.2
Modelle
Wicket orientiert sich dabei an dem Programmiermodell einer Desktop-Anwendung. In diesem Modell informiert der Controller die View darüber, ob sich etwas geändert hat und die Komponente neu gezeichnet werden muss. Da bei Webanwendungen immer (es sei denn, Ajax kommt zum Einsatz) die ganze Seite dargestellt werden muss, entfällt das Benachrichtigen der View über die Veränderung. Es werden immer alle Komponenten mit den zugehörigen Daten dargestellt. Wenn Wicket über die Modelländerungen informiert wird, zieht Wicket diese Informationen heran, um für diesen neuen Zustand eine neue Version der Seite anzulegen. Die alte Version mit alten Daten wird im PageStore abgelegt.
4.4.3
Markup
Eine Wicket-Komponente besteht aus einer Java-Klasse, die von einer Wicket-Basisklasse geerbt hat, und einer dazugehörigen Markup-Datei, die denselben Namen besitzt, in src/main/resources im selben Unterverzeichnis wie die Klasse abgelegt werden muss und die Endung html besitzt. Das bedeutet, dass eine Klasse StartPage im Package de.wicketpraxis.pages von der Klasse WebPage abgeleitet ist und eine Markup-Datei im Verzeichnis de/wicketpraxis/pages/ innerhalb des Ressourcenverzeichnisses src/main/resources mit den Dateinamen StartPage.html benötigt.
53
5 5 Modelle Es ist sicher ungewöhnlich, dass man bei einem MVC-Framework mit der Erklärung der Modelle anfängt. Dass ich es trotzdem tue, hat zwei Gründe: Einerseits gibt es kaum Komponenten, die nicht auf ein Modell zugreifen, um etwas darzustellen. Andererseits ist die häufigste Frage in Bezug auf Wicket: Wie stelle ich etwas dar? Und bevor man diese Frage beantworten kann, muss man erst klären, woher dieses Etwas kommt. Die meisten Komponenten greifen auf ein Modell zu, um die Modelldaten direkt oder indirekt darzustellen. Dazu greifen die Komponenten über sehr einfache Methoden auf die Daten zu. Die einfachste Komponente Label konvertiert die Daten des Modells in einen String und stellt diesen dann dar. Das IModel-Interface, das alle Modelle implementieren müssen, definiert nur drei Methoden: setObject(T): setzt die Daten. setObject(T): gibt die Daten zurück. detach(): Wenn ein Modell einer Komponente zugeordnet wurde, wird bei einem Aufruf der detach()-Methode der Komponente auch die detach()-Methode des Modells aufgerufen. Das bedeutet, dass Modelle beliebige Daten enthalten können.
5.1
Konverter Die Daten, die ein Modell zurückliefern kann, können beliebigen Typs sein. Damit diese Daten sinnvoll dargestellt werden können, liefert Wicket für die wichtigsten Datentypen Konverter mit, welche die Daten in Text und Text in Daten umwandeln können, wie es z.B. bei Formularen benötigt wird. Auch wenn Wicket für die wichtigsten Datentypen passende Konverter mitbringt, kann es notwendig sein, eigene Konverter für unbekannte Datentypen oder die bestehenden Konverter für einen oder mehrere Datentypen bereitzustellen. Dazu überschreibt man am besten die newConverterLocator()-Methode der WebApplication-Klasse.
55
5 Modelle Listing 5.1 WicketPraxisApplication.java ... @Override protected IConverterLocator newConverterLocator() { return new CustomConverterLocator(super.newConverterLocator()); } ...
Da wir nicht alle Konverter neu definieren möchten, sondern bei fast allen Typen auf die bewährten Konverter zurückgreifen wollen, sollte unsere ConverterLocator-Klasse auf die Konverter der Standardklasse zurückgreifen. Listing 5.2 CustomConverterLocator.java package de.wicketpraxis.web.converter; ... public class CustomConverterLocator implements IconverterLocator { IConverterLocator _fallback; Map, \ IConverter>(); { _customMap.put(Some.class, new SomeClassConverter()); } public CustomConverterLocator(IConverterLocator fallback) { _fallback=fallback; } public IConverter getConverter(Class type) { IConverter ret=_customMap.get(type); if (ret==null) ret=_fallback.getConverter(type); return ret; } }
Das IConverter-Interface ist ähnlich einfach wie das IModel-Interface. Die Methode convertToString ist für die Umwandlung zur Darstellung verantwortlich. Die Methode convertToObject konvertiert im besten Fall einen String in den gewünschten Typ. Interessant ist hierbei, dass mit Locale auch die gewünschte Spracheinstellung übergeben wird. Listing 5.3 IConverter.java package org.apache.wicket.util.convert; ... public interface IConverter extends IClusterable { Object convertToObject(String value, Locale locale); String convertToString(Object value, Locale locale); }
56
5.2 Einfache Modelle
5.2
Einfache Modelle Genug der Theorie. Schauen wir uns an einem ersten Beispiel an, wie sich das alles zusammenfügt. Dass dabei noch nicht alles bekannt ist, liegt in der Natur der Sache. In diesem ersten Beispiel möchten wir die aktuelle Uhrzeit auf der Seite anzeigen. Dazu erstellen wir eine Seite (das bedeutet: eine neue Klasse, die von WebPage abgeleitet wird). Listing 5.4 SimpleModelPage.java package de.wicketpraxis.web.thema.models; ... public class SimpleModelPage extends WebPage { public SimpleModelPage() { IModel message=Model.of("Initialwert"); message.setObject("Jetzt ist "+new Date()); add(new Label("message",message)); } }
Im Konstruktor der Seite erzeugen wir ein Modell mit einem String als Inhalt. Dabei gibt es mehr als eine Variante des Aufrufs. Die im Quelltext benutzte Variante ist eine Kurzform für new Model("Initialwert"). Die Kurzform erspart einiges an Schreibarbeit, sodass man gut beraten ist, nur diese Variante zu benutzen. In der nächsten Zeile werden die Daten im Modell überschrieben und ein Label erstellt, das zur Anzeige das Modell als Parameter übergeben bekommt. Damit die Seite angezeigt werden kann, müssen wir jetzt noch ein passendes Markup anlegen. Dazu legen wir im Ressourcenpfad (src/main/resources) ein Verzeichnis de/wicketpraxis/web/thema/models an. Das Verzeichnis entspricht der Struktur, wie Java Pakete im Verzeichnisbaum abbildet. Tipp
Um ein Verzeichnis im Ressourcenpfad eines Projekts anzulegen, kann man unter Eclipse auch einfach ein Package mit demselben Namen wie das Package der Klasse erzeugen. Eclipse erzeugt dann wie für den Quelltext der Klasse auch das entsprechende Verzeichnis. Das geht zum einen schneller, und zum anderen kann man auch in den Ressourcen wie in den Quelltexten über die Hierarchieansicht navigieren.
In dem Fall lautet der Name des Pakets de.wicketpraxis.web.thema.models. In diesem Verzeichnis/Paket legen wir nun eine Datei an. Die Markup-Datei einer Klasse muss sich immer im selben Pfad oder Paket wie die dazugehörige Komponentenklasse befinden und denselben Namen tragen. Nur die Endung lautet „.html“.
57
5 Modelle Listing 5.5 SimpleModelPage.html Simple Model Page Das wird ersetzt.
Wichtig ist, dass der Wert in wicket:id derselbe ist wie der erste Parameter der LabelKomponente, damit Wicket weiß, welche Komponente er darstellen soll. Der erste Parameter einer Komponente ist meist die ID der Komponente, hier kurz Wicket-ID. Jetzt müssen wir noch in unserer WebApplication-Klasse den Rückgabewert von getHomePage() anpassen, damit wir beim Start der Anwendung auf unserer Seite landen, und schon sehen wir folgendes Ergebnis (natürlich mit einem anderen Datum): Jetzt ist Wed Mar 25 20:57:11 CET 2009
Wenn wir jetzt im Browser die Seite neu laden, wird immer wieder der Konstruktor aufgerufen und eine neue Seite mit einem neuen Datum erstellt. Dadurch ändert sich natürlich das Ergebnis.
5.2.1
Modelle verändern
Wenn man die Daten eines Modells verändern möchte, kann man setObject() des Modells aufrufen oder über die Komponentenmethode setDefaultModelObject() die Daten des Modells der Komponente aktualisieren. Jede Komponente hat ein Modell. Entweder wird das konkrete Modell als Parameter beim Erzeugen der Komponente übergeben oder ein leeres Modell benutzt. Das Modell einer Komponente kann mit setDefaultModel neu gesetzt werden. Wenn man das Modell direkt verändert (also die Methode setObject des Modells benutzt), dann kann Wicket unter Umständen nicht feststellen, ob sich Daten geändert haben. In diesem Fall wird Wicket keine neue Version einer Seite erstellen. Was bedeutet das konkret? Wenn der Nutzer auf einer Seite etwas ändert, dann erzeugt Wicket für den Fall, dass sich Daten geändert haben, eine neue Version der Seite. Wenn der Nutzer dann über den Browser auf eine ältere Seite zurück navigiert, kann Wicket auf diese Seite in diesem älteren Zustand zurückgreifen. Der Nutzer sieht nicht nur alte Daten, sondern die Interaktion, die der Nutzer dann durchführt, basiert auch auf diesen Daten. Wenn Wicket nun keine neue Version einer Seite anlegt, dann navigiert der Nutzer auf die Seite im ursprünglichen Zustand. Auch wenn man dieses Verhalten deaktivieren kann, eröffnet es doch interessante Anwendungsmöglichkeiten. Wenn man nicht verhindern kann, dass man die Daten eines Models direkt ändern muss, kann man Wicket trotzdem mitteilen, dass sich der Zustand eines Modells geändert hat. Dazu ruft man für die Komponente, die sich dadurch verändert, die Methode modelChanging() vor der Veränderung und modelChanged() nach der Veränderung auf. Diese Funk-
58
5.2 Einfache Modelle tionen werden auch in setDefaultModelObject() aufgerufen. Das folgende Beispiel soll den Unterschied veranschaulichen. Listing 5.6 ModelChangePage.java package de.wicketpraxis.web.thema.models; ... public class ModelChangePage extends WebPage { public ModelChangePage() { final IModel message = Model.of(0); add(new Label("message", message)); add(new Link("changeModel",message) { @Override public void onClick() { setModelObject(getModelObject()+1); } }); add(new Link("changeModelDirect",message) { @Override public void onClick() { getModel().setObject(getModel().getObject()+1); } }); add(new Link("doNothing") { @Override public void onClick() { } }); } }
Es wird wieder ein Modell angelegt, das durch ein Label zur Darstellung gebracht wird. Gleichzeitig wird ein Link angelegt, bei dem nach einem Klick der Wert des Modells, das zusätzlich an den Link gebunden ist, aktualisiert wird. In diesem Fall erhöhen wir den Wert um 1. Der zweite Link führt dieselbe Aktion aus, ohne Wicket davon in Kenntnis zu setzen. Der dritte Link führt keinerlei Aktionen aus. Die Seite muss trotzdem neu geladen werden, sodass der aktuelle Inhalt des Modells durch das Label dargestellt wird. Das Markup für diese Seite sieht ähnlich einfach aus wie für die Seite im Beispiel davor und muss im passenden Verzeichnis abgelegt werden. Listing 5.7 ModelChangePage.html Model Change Page
59
5 Modelle Aktueller Wert: Das wird ersetzt. ändern direkt ändern nicht ändern
Wie zu erwarten, ändern wir den Rückgabewert der getHomePage-Methode auf diese Seite und starten die Anwendung neu. Je nachdem, welchen Link wir anklicken, wird der Wert jeweils um eins erhöht. Wenn wir auf den Link „direkt ändern“ klicken, dann ändert sich ebenfalls der Wert. Wenn wir dann im Browser auf die Seite davor springen, sehen wir nicht eine Seite mit dem vorletzten Wert, sondern die Seite, die als Letztes versioniert werden konnte. Zusammenfassung Jetzt sind wir bereits in der Lage, Informationen anzuzeigen und durch Aktionen zu verändern. Diese einfache Möglichkeit ist nicht besonders elegant, veranschaulicht aber sehr schön das Prinzip.
5.3
Modell-Hilfsklassen Wicket bietet für bestimmte Anwendungen bereits vordefinierte Hilfsklassen, um aus z.B. aus einer Liste ein Modell zu erzeugen. Das folgende Beispiel dient nur zur Darstellung der verschiedenen Klassen und Funktionsaufrufe. Unter den auskommentierten Funktionsaufrufen findet sich der gleichwertige kürzere Aufruf. Listing 5.8 ModelTypesPage.java package de.wicketpraxis.web.thema.models; ... public class ModelTypesPage extends WebPage { public ModelTypesPage() { List liste = Arrays.asList("Das", "ist", "ne", "Liste"); Map map = new HashMap(); IModel collectionModel = \ new CollectionModel(liste); IModel listModel = new ListModel(liste); IModel setModel = new SetModel(map.keySet());
60
// //
IModel mapModel = new MapModel(map); IModel mapModel = Model.ofMap(map);
// //
IModel