http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php?title=Spezial:Beitr%C3%A4ge&feed=atom&target=LkSalespoint - Benutzerbeiträge [de]2024-03-28T08:48:10ZAus SalespointMediaWiki 1.14.0http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-10-07T15:14:39Z<p>Lk: /* Ansprechpartner */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
'''01.07.2010: Version 2010-1.0.10 released'''<br/><br />
* AutoTimer improved<br />
<br />
'''26.06.2010: Version 2010-1.0.9 released'''<br/><br />
* keepAR parameter for ImagePanel<br />
* Sorting Issue in Tables resolved... again<br />
<br />
'''26.06.2010: Version 2010-1.0.8 released'''<br/><br />
* DoubleValue in QuoteValue is now displayed correctly in ListViews<br />
* Sorting Issue in Tables resolved<br />
<br />
'''23.06.2010: Version 2010-1.0.7 released'''<br/><br />
* Deadlock issues resolved<br />
* ClassFieldMapper bugs fixed<br />
* getStatement methods now public in PersistenceManager<br />
<br />
'''22.06.2010: Version 2010-1.0.6 released'''<br/><br />
* support for persistent Strings larger than 500/1024 chars (depends on db driver). Just annotate affected string ( @PersistenceProperty(isLongString = true) )<br />
<br />
'''21.06.2010: Version 2010-1.0.5 released'''<br/><br />
* web applications can be deployed to real server environments (that doesn't have a gui mapping layer) now<br />
<br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
'''08.06.2010: Version 2010-1.0.3 released'''<br /><br />
<br /><br />
Aufgrund der negativen Resonanz zur Übermittlung von Daten des Salespoint-Frameworks an den Server des Lehrstuhls Softwaretechnologie soll an dieser Stelle aufgeklärt werden, welche Informationen zu welchem Zweck übermittelt werden.<br /> <br />
Der Zweck dieser Datenübermittlung besteht darin, dass über den Zeitraum des Praktikums hinweg die durchschnittliche Belastung der Praktikumsteilnehmer in den verschiedenen Phasen gemessen werden kann. Damit sind qualitative Verbesserungen, insbesondere in Bezug auf die Aufwandsverteilung, für zukünftige Semester/Praktika möglich. Folgende Daten wurden im Einzelnen übertragen:<br /><br />
*Der System Fingerprint: Eine aus der Java Umgebung generierte Zeichenfolge, die mittels des SHA-512 Algorithmus anonymisiert wird.<br />
*Eine fortlaufende Sequenznummer.<br />
*Die genutzte Version des Salespoint-Frameworks, um festzustellen wie schnell bereitgestellte Updates genutzt werden.<br />
*Das genutzte Betriebssystem und die Java-Version, um herauszufinden welche Plattformen für zukünftige Framework-Tests in Betracht gezogen werden müssen.<br />
*Die aktuelle Sprache des Benutzers, um die Notwendigkeit zusätzlicher Lokalisierungen zu erheben.<br />
<br />
Alle Informationen wurden verschlüsselt übertragen und in aggregierter Form gespeichert.<br /><br />
Als Konsequenz der Beschwerden von Praktikumsteilnehmern und aufgrund der mangelnden Transparenz des oben beschriebenen Verfahrens entschuldigen wir uns für die entstandene Verunsicherung und haben die '''Datenübermittlungsfunktion mit Version 1.0.3''' des Frameworks vorbehaltlich einer späteren Einigung mit den Praktikumsteilnehmern '''entfernt'''. Informationen älterer Framework-Versionen werden vom Lehrstuhlserver mit sofortiger Wirkung nicht mehr angenommen. <br /><br /><br />
'''29.05.2010: Version 2010-1.0.2 released'''<br /><br />
'''10.05.2010: Version 2010-1.0.1 released'''<br /><br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im [http://st.inf.tu-dresden.de/SalesPoint/forum Salespoint Forum] gestellt werden:<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-10-07T15:14:00Z<p>Lk: /* Ansprechpartner */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
'''01.07.2010: Version 2010-1.0.10 released'''<br/><br />
* AutoTimer improved<br />
<br />
'''26.06.2010: Version 2010-1.0.9 released'''<br/><br />
* keepAR parameter for ImagePanel<br />
* Sorting Issue in Tables resolved... again<br />
<br />
'''26.06.2010: Version 2010-1.0.8 released'''<br/><br />
* DoubleValue in QuoteValue is now displayed correctly in ListViews<br />
* Sorting Issue in Tables resolved<br />
<br />
'''23.06.2010: Version 2010-1.0.7 released'''<br/><br />
* Deadlock issues resolved<br />
* ClassFieldMapper bugs fixed<br />
* getStatement methods now public in PersistenceManager<br />
<br />
'''22.06.2010: Version 2010-1.0.6 released'''<br/><br />
* support for persistent Strings larger than 500/1024 chars (depends on db driver). Just annotate affected string ( @PersistenceProperty(isLongString = true) )<br />
<br />
'''21.06.2010: Version 2010-1.0.5 released'''<br/><br />
* web applications can be deployed to real server environments (that doesn't have a gui mapping layer) now<br />
<br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
'''08.06.2010: Version 2010-1.0.3 released'''<br /><br />
<br /><br />
Aufgrund der negativen Resonanz zur Übermittlung von Daten des Salespoint-Frameworks an den Server des Lehrstuhls Softwaretechnologie soll an dieser Stelle aufgeklärt werden, welche Informationen zu welchem Zweck übermittelt werden.<br /> <br />
Der Zweck dieser Datenübermittlung besteht darin, dass über den Zeitraum des Praktikums hinweg die durchschnittliche Belastung der Praktikumsteilnehmer in den verschiedenen Phasen gemessen werden kann. Damit sind qualitative Verbesserungen, insbesondere in Bezug auf die Aufwandsverteilung, für zukünftige Semester/Praktika möglich. Folgende Daten wurden im Einzelnen übertragen:<br /><br />
*Der System Fingerprint: Eine aus der Java Umgebung generierte Zeichenfolge, die mittels des SHA-512 Algorithmus anonymisiert wird.<br />
*Eine fortlaufende Sequenznummer.<br />
*Die genutzte Version des Salespoint-Frameworks, um festzustellen wie schnell bereitgestellte Updates genutzt werden.<br />
*Das genutzte Betriebssystem und die Java-Version, um herauszufinden welche Plattformen für zukünftige Framework-Tests in Betracht gezogen werden müssen.<br />
*Die aktuelle Sprache des Benutzers, um die Notwendigkeit zusätzlicher Lokalisierungen zu erheben.<br />
<br />
Alle Informationen wurden verschlüsselt übertragen und in aggregierter Form gespeichert.<br /><br />
Als Konsequenz der Beschwerden von Praktikumsteilnehmern und aufgrund der mangelnden Transparenz des oben beschriebenen Verfahrens entschuldigen wir uns für die entstandene Verunsicherung und haben die '''Datenübermittlungsfunktion mit Version 1.0.3''' des Frameworks vorbehaltlich einer späteren Einigung mit den Praktikumsteilnehmern '''entfernt'''. Informationen älterer Framework-Versionen werden vom Lehrstuhlserver mit sofortiger Wirkung nicht mehr angenommen. <br /><br /><br />
'''29.05.2010: Version 2010-1.0.2 released'''<br /><br />
'''10.05.2010: Version 2010-1.0.1 released'''<br /><br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://st.inf.tu-dresden.de/SalesPoint/forum]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-07-06T21:01:37Z<p>Lk: /* Dokumentation zum Framework */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_10.jar Salespoint-2010-1.0.10.jar]</span><span class="fileInfo"> (2,12 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_9.jar Salespoint-2010-1.0.9.jar]</span><span class="fileInfo"> (2,12 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_8.jar Salespoint-2010-1.0.8.jar]</span><span class="fileInfo"> (2,12 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_7.jar Salespoint-2010-1.0.7.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_6.jar Salespoint-2010-1.0.6.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.5.jar|Salespoint-2010-1.0.5.jar]]</span><span class="fileInfo"> (2,15 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_4.jar Salespoint-2010-1.0.4.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_3.jar Salespoint-2010-1.0.3.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_2.jar Salespoint-2010-1.0.2.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_1.jar Salespoint-2010-1.0.1.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[[Media:Salespoint-2010-javadoc.zip|salespoint-2010-javadoc.zip]]</span><span class="fileInfo"> (2,92 MB)</span> als zip-Datei für Offline-Benutzung<br />
* [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/javadoc/ Online Version]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-javadoc.zipDatei:Salespoint-2010-javadoc.zip2010-07-06T20:59:17Z<p>Lk: </p>
<hr />
<div></div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-07-01T17:41:03Z<p>Lk: /* Aktuelle Ereignisse */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
'''01.07.2010: Version 2010-1.0.10 released'''<br/><br />
* AutoTimer improved<br />
<br />
'''26.06.2010: Version 2010-1.0.9 released'''<br/><br />
* keepAR parameter for ImagePanel<br />
* Sorting Issue in Tables resolved... again<br />
<br />
'''26.06.2010: Version 2010-1.0.8 released'''<br/><br />
* DoubleValue in QuoteValue is now displayed correctly in ListViews<br />
* Sorting Issue in Tables resolved<br />
<br />
'''23.06.2010: Version 2010-1.0.7 released'''<br/><br />
* Deadlock issues resolved<br />
* ClassFieldMapper bugs fixed<br />
* getStatement methods now public in PersistenceManager<br />
<br />
'''22.06.2010: Version 2010-1.0.6 released'''<br/><br />
* support for persistent Strings larger than 500/1024 chars (depends on db driver). Just annotate affected string ( @PersistenceProperty(isLongString = true) )<br />
<br />
'''21.06.2010: Version 2010-1.0.5 released'''<br/><br />
* web applications can be deployed to real server environments (that doesn't have a gui mapping layer) now<br />
<br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
'''08.06.2010: Version 2010-1.0.3 released'''<br /><br />
<br /><br />
Aufgrund der negativen Resonanz zur Übermittlung von Daten des Salespoint-Frameworks an den Server des Lehrstuhls Softwaretechnologie soll an dieser Stelle aufgeklärt werden, welche Informationen zu welchem Zweck übermittelt werden.<br /> <br />
Der Zweck dieser Datenübermittlung besteht darin, dass über den Zeitraum des Praktikums hinweg die durchschnittliche Belastung der Praktikumsteilnehmer in den verschiedenen Phasen gemessen werden kann. Damit sind qualitative Verbesserungen, insbesondere in Bezug auf die Aufwandsverteilung, für zukünftige Semester/Praktika möglich. Folgende Daten wurden im Einzelnen übertragen:<br /><br />
*Der System Fingerprint: Eine aus der Java Umgebung generierte Zeichenfolge, die mittels des SHA-512 Algorithmus anonymisiert wird.<br />
*Eine fortlaufende Sequenznummer.<br />
*Die genutzte Version des Salespoint-Frameworks, um festzustellen wie schnell bereitgestellte Updates genutzt werden.<br />
*Das genutzte Betriebssystem und die Java-Version, um herauszufinden welche Plattformen für zukünftige Framework-Tests in Betracht gezogen werden müssen.<br />
*Die aktuelle Sprache des Benutzers, um die Notwendigkeit zusätzlicher Lokalisierungen zu erheben.<br />
<br />
Alle Informationen wurden verschlüsselt übertragen und in aggregierter Form gespeichert.<br /><br />
Als Konsequenz der Beschwerden von Praktikumsteilnehmern und aufgrund der mangelnden Transparenz des oben beschriebenen Verfahrens entschuldigen wir uns für die entstandene Verunsicherung und haben die '''Datenübermittlungsfunktion mit Version 1.0.3''' des Frameworks vorbehaltlich einer späteren Einigung mit den Praktikumsteilnehmern '''entfernt'''. Informationen älterer Framework-Versionen werden vom Lehrstuhlserver mit sofortiger Wirkung nicht mehr angenommen. <br /><br /><br />
'''29.05.2010: Version 2010-1.0.2 released'''<br /><br />
'''10.05.2010: Version 2010-1.0.1 released'''<br /><br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://www.ifsr.de/forum/viewforum.php?f=39 Hackbrett >> Softwaretechnologie >> Salespoint]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Web_ErweiterungWeb Erweiterung2010-06-28T09:23:56Z<p>Lk: /* TagLibary */</p>
<hr />
<div>Seit der Version 2010 stellt Salespoint die Möglichkeit sowie Hilfsmittel bereit, in einem ServletContainer (vorrangig [http://tomcat.apache.org/ Apache Tomcat]) ausgeführt zu werden.<br />
<br />
=Allgemeines=<br />
Basierend auf dem etablierten, quelloffenen Framework Spring (in der Version 3), dessen MVC-Umsetzung das Request/Response-Handling kapselt und die Entwicklung einer Webapplikation stark vereinfacht. Die komplette Salespoint Daten- und Nutzerverwaltung mit integrierte Persistenz kann auf gleiche, bisher beschriebene Weise genutzt werden. Wie Spring funktioniert kann einerseits in den Web-Tutorials sowie in der sehr anschaulichen und umfangreichen [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Dokumentation von Spring] nachgelesen werden.<br />
<br />
Im Folgenden wird auf die von Salespoint bereitgestellte TagLibary zur Visualisierung von Datenbeständen sowie einiger Kontrollstrukturen näher eingegangen.<br />
<br />
=TagLibary=<br />
In eine JSP-Datei lässt sich die TagLibary mit folgendem Befehl einbinden:<br />
<code xml><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
</code><br />
==View==<br />
''View-Tag''s dienen dem Rendern von Katalogen und Beständen. Diese werden in ein ''AbstractTabelModel'' (ATM), welches den Katalog bzw Bestand in tabellarischer beschreibt, überführt und dem ''ViewTag'' übergeben, welcher daraus eine entsprechende Repräsentation in HTML rendert. Es gibt 3 einfach zu benutzende konkrete ''ViewTag''s:<br />
<br />
*;Table: rendert eine native HTML-Tabelle mit HTML-Tags wie <nowiki><table>,<tr>,<td>,...</nowiki><br />
<br />
*;CssTable: rendert eine CSS-Tabelle mit HTML-Tags wie <nowiki><div style="display:table;">,<div style="display:table-row;">,<div style="display:table-cell;">,...</nowiki><br />
<br />
*;List: rendert eine 'floating'-List. Dabei werden die Zeilen des ATM als Objekte interpretiert und die Zellen in <nowiki><object></nowiki>-Tags als Absätze eingefügt. Um die floatenden Objekte ist eine Devision, welche mit ''style="display:inline-block;"'' den Umfluss nicht auf äußere Tags überträgt.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|abstractTableModel<br />
|align="center"|X<br />
|align="center"|<br />
|ATM des zu rendernden Datenbestandes<br />
|-<br />
|renderSettingsConfigurator<br />
|align="center"|<br />
|align="center"| leer<br />
|Settings zum darstellen des Views. Die Hierarchie der Einstellungen ist wie folgt (die jeweils nachfolgende Instanz überschreibt die Vorhergehende):<br/><br />
View-Settings --> renderSettingsConfigurator --> Attribute der Tags<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|-<br />
|style<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-style-Attribut zur Inline-CSS-Definition<br />
|-<br />
|searchField<br />
|align="center"|<br />
|align="center"| false<br />
|true, wenn ein Sucheingabefeld zum Filtern eingeblendet werden soll. Achtung: SearchFieldInterceptor aktivieren! [[FAQ#Wie aktiviere ich diese Interceptor?]]<br />
|-<br />
|searchString<br />
|align="center"|<br />
|align="center"| leer<br />
|Zeichenkette, nach der der Bestand gefiltert werden soll. Funktioniert unabhängig von der Einblendung des Sucheingabefeldes.<br />
|-<br />
|extraCols<br />
|align="center"|<br />
|align="center"| leer<br />
|Liste mit ExtraColumns für den Datenbestand<br />
|-<br />
|positionOfExtraColumns<br />
|align="center"|<br />
|align="center"| BACK<br />
|Position der ExtraColumns<br />
|-<br />
|zebraStyle<br />
|align="center"|<br />
|align="center"| false<br />
|Wechselt das class-attribut einer Tabellenzeile zwischen 'table-row-even' and 'table-row-odd'. (Nur in 'Table' und 'CssTable' verfügbar.)<br />
|-<br />
|caption<br />
|align="center"|<br />
|align="center"| leer<br />
|Eine Überschrift für den View.<br />
|-<br />
|summary<br />
|align="center"|<br />
|align="center"| leer<br />
|Eine 'summary' kann nur in Views mit NativeHtmlTable oder direkt im Table-Tag verwendet werden. In allen anderen Fällen wird sie nicht mitgerendert. (Das 'summary'-Attribut wird in einem <nowiki><table></nowiki>-Tag gesetzt und kann mit Blindentastaturen ausgelesen werden.)<br />
|-<br />
|viewDirection<br />
|align="center"|<br />
|align="center"| LEFT<br />
|(Funktioniert nur in Listen) Positioniert die Objekte einer Liste in umgekehrter Reihenfolge.<br />
|}<br />
<br />
Für fortgeschrittende Nutzung stehen 2 weitere ViewTags zur Verfügung:<br />
*;View: allg. ViewRenderer, dem zusätzlich eine ''IHtmlViewRepresentation'' übergeben wird. Sinnvoll, wenn man eine eigene Html-Repräsentation definieren will.<br />
<br />
*;CompletedView: rendert eine übergebene ''View''-Klasse. Sinnvoll, wenn man eine eigene View definieren will.<br />
<br />
==DoubleView==<br />
Der DoubleViewTag ist die Oberflächenkomponente neben dem DoubleViewController und stellt die Möglichkeit bereit, den Benutzer Elemente von einem Katalog bzw. Bestand in einen anderen verschieben zu lassen. Wie dieses Zusammenwirken funktioniert, wird in [[Videoautomat_Web#Ausleih-_und_Bezahlvorgang]] beschrieben.<br />
<br />
'''Achtung:''' Der DoubleViewTag kann nur in Zusammenwirken mit dem DoubleViewController sinnvoll eingesetzt werden!<br />
<br />
Der DoubleViewTag beinhaltet 2 ViewTags und kann auf 2 verschiedene Weisen eingesetzt werden:<br />
<br />
'''Beispiel:'''<br />
#Views als Attribute<code xml><sp:DoubleView sourceView="${sourceATM}" destinationView="${destATM}" /></code><br />
#Views als InnerTag<code xml><br />
<sp:DoubleView><br />
<sp:Table abstractTableModel="${sourceATM}" /><br />
<sp:Table abstractTableModel="${destATM}" /><br />
</sp:DoubleView><br />
</code><br />
<br />
Die 2 Variante wird empfohlen, da sich damit die individuellen ViewTags komplett und wie die einzeln Verwendeten konfigurieren lassen.<br />
'''Achtung:''' Es müssen exakt 2 ViewTags angegeben werden - dies können auch unterschiedliche ViewTags sein.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|sourceView<br />
|align="center"|<br />
|align="center"|null<br />
|ATM des zu rendernden Quelldatenbestandes<br />
|-<br />
|destinationView<br />
|align="center"|<br />
|align="center"|null<br />
|ATM des zu rendernden Zieldatenbestandes<br />
|-<br />
|showNumberField<br />
|align="center"|<br />
|align="center"|false<br />
|''true'', wenn ein Eingabefeld für die Anzahl eingeblendet werden soll<br />
|-<br />
|showBackButton<br />
|align="center"|<br />
|align="center"|true<br />
|''false'', wenn der Button zum zurück-Verschieben angezeigt werden soll<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}<br />
<br />
==LoginDialog==<br />
Der LoginDialogTag rendert ein einfaches LoginFormular.<br />
<br />
'''Achtung:''' LoginInterceptor notwendig! [[FAQ#Wie aktiviere ich diese Interceptor?]]<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:LoginDialog /><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|showUsers<br />
|align="center"|<br />
|align="center"|true<br />
|false, wenn statt der SelectBox mit registrierten Nutzernamen ein leeres Eingabefeld angezeigt werden soll<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}<br />
<br />
==LoggedIn==<br />
Der LoggedInTag ist eine Kontrollstruktur mitdessen Hilfe man Teile einer JSP-Seite anhand des Loginstatus aus- bzw. einblenden kann.<br />
'''Beispiel:'''<code xml><br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
</code><br />
Im Beispiel wird das LoginFormular nur dann angezeigt, wenn der diese Seite aufrufende Benutzer nicht eingeloggt ist.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|status<br />
|align="center"|X<br />
|align="center"|<br />
|true: alle inneren Tags werden gerendert, wenn der Nutzer eingeloggt ist.<br />
<br />
false: alle inneren Tags werden gerendert, wenn der Nutzer ausgeloggt ist. <br />
|}<br />
<br />
==HasCapability==<br />
Der HasCapabilityTag ist eine weitere Kontrollstruktur, die alle innere Tags nur dann anzeigt, wenn der aktuelle Nutzer eine Capability mit dem übergebenen Namen besitzt.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:HasCapability capabilityName="admin"><br />
<a href="/administrationSite">adminSite</a><br />
</sp:HasCapability><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|capabilityName<br />
|align="center"|X<br />
|align="center"|<br />
|Rendert alle inneren Tags nur, wenn der aktuelle Nutzer eine Capability mit diesem NAmen besitzt.<br />
|}<br />
<br />
==Image==<br />
Der ImageTag rendert ein übergebenes ''BufferdImage'' Base64-kodiert (gemäß RFC 2397) direkt in die HTML-Seite hinein.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:Image image="${user.image}" alt="avatar of ${user.name}" /><br />
</code><br />
<br />
'''Achtung:''' Alle gescheiten Browser (FireFox, Opera, Safari, Chrome) bis auf den IE unterstützen diese Technik. Der IE8 kann es zumindest für kleine Bilder (bis 32kb Base64-Codelänge).<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|image<br />
|align="center"|X<br />
|align="center"|<br />
|das zu rendernde Bild vom Typ BufferedImage<br />
|-<br />
|alt<br />
|align="center"|X<br />
|align="center"|<br />
|entspricht dem HTML-alt-Attribut, textuelle Beschreibung des Bildes<br />
|-<br />
|height<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-height-Attribut, Angabe mit '%' oder ohne Einheit(px)<br />
|-<br />
|width<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-width-Attribut, Angabe mit '%' oder ohne Einheit(px)<br />
|}<br />
<br />
==Message==<br />
Der MessageTag rendert die übergebene Liste von Strings.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:Message messages="${spErros}" styleName="errorMessages" /><br />
<sp:Message messages="${spFlash}" styleName="flashMessages" /><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|messages<br />
|align="center"|X<br />
|align="center"|<br />
|Liste von Strings mit anzuzeigenden Nachrichten<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Benutzer_Diskussion:92.229.37.57Benutzer Diskussion:92.229.37.572010-06-24T11:55:03Z<p>Lk: Die Seite wurde neu angelegt: „Link zu Downloads eingefügt, um Wartung zu erleichtern.“</p>
<hr />
<div>Link zu Downloads eingefügt, um Wartung zu erleichtern.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DokumentationDokumentation2010-06-24T11:53:47Z<p>Lk: Änderungen von 92.229.37.57 (Diskussion) rückgängig gemacht und letzte Version von 77.189.134.78 wiederhergestellt</p>
<hr />
<div>==Technischer Überblick==<br />
<br />
Der technische Überblick soll einen Blick hinter die Kulissen ermöglichen, um ein besseres Verständnis für das Framework zu entwickeln. Die Technische Referenz bietet darüber hinaus einen detaillierten Einblick in das Innenleben von SalesPoint.<br />
<br />
* [[Technischer Überblick]] eine kurze Einführung in das Framework <br />
* [http://st.inf.tu-dresden.de/SalesPoint/v3.3/download/beleg_de.pdf Großer Beleg] die Grundlage für den technischen Überblick (pdf-Datei)<br />
* [[Technische Referenz]] eine rudimentäre Beschreibung des internen Aufbaus von SalesPoint<br />
* [[Persistence Layer]] eine Beschreibung der Peristenzschicht des Frameworks<br />
* [[Web Erweiterung]] eine Beschreibung der Salespointeigenen Hilfmittel für den Betrieb als Webapplikation<br />
<br />
==API Spezifikation==<br />
Unterstützung bei der Arbeit mit dem Framework soll die API-Spezifikation (Application-programming Interface, Javadoc) bieten. Hier sind alle Klassen, deren Attribute, die enthaltenen Methoden, sowie deren Dokumentation zu finden.<br />
<br />
verfügbar unter [[Downloads]]<br />
<br />
==HowTos==<br />
Hierunter verbergen sich stichwortartige Hilfen mit Codebeispielen welche Anpassungsmöglichkeiten des Frameworks beschreiben. <br />
* [[HowTos]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-06-21T14:13:18Z<p>Lk: /* Framework-Versionen */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.5.jar|Salespoint-2010-1.0.5.jar]]</span><span class="fileInfo"> (2,15 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_4.jar Salespoint-2010-1.0.4.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_3.jar Salespoint-2010-1.0.3.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_2.jar Salespoint-2010-1.0.2.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_1.jar Salespoint-2010-1.0.1.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-1.0.1-javadoc.rar Salespoint-2010-1.0.1-javadoc.rar]</span><span class="fileInfo"> (2,91 MB)</span> gezippt für offline Benutzung<br />
* Gleiches in freien Formaten: [http://www1.inf.tu-dresden.de/~swt-10-43/sp-javadoc/salespoint-2010-1.0.1-javadoc.7z 7z (814K)], [http://www1.inf.tu-dresden.de/~swt-10-43/sp-javadoc/salespoint-2010-1.0.1-javadoc.zip zip (3.3M)]<br />
* [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/javadoc/ Online Version]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-1.0.5.jarDatei:Salespoint-2010-1.0.5.jar2010-06-21T14:12:51Z<p>Lk: </p>
<hr />
<div></div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-06-21T14:10:07Z<p>Lk: /* Aktuelle Ereignisse */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
'''21.06.2010: Version 2010-1.0.5 released'''<br/><br />
* web applications can be deployed to real server environments (that doesn't have a gui mapping layer) now<br />
<br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
'''08.06.2010: Version 2010-1.0.3 released'''<br /><br />
<br /><br />
Aufgrund der negativen Resonanz zur Übermittlung von Daten des Salespoint-Frameworks an den Server des Lehrstuhls Softwaretechnologie soll an dieser Stelle aufgeklärt werden, welche Informationen zu welchem Zweck übermittelt werden.<br /> <br />
Der Zweck dieser Datenübermittlung besteht darin, dass über den Zeitraum des Praktikums hinweg die durchschnittliche Belastung der Praktikumsteilnehmer in den verschiedenen Phasen gemessen werden kann. Damit sind qualitative Verbesserungen, insbesondere in Bezug auf die Aufwandsverteilung, für zukünftige Semester/Praktika möglich. Folgende Daten wurden im Einzelnen übertragen:<br /><br />
*Der System Fingerprint: Eine aus der Java Umgebung generierte Zeichenfolge, die mittels des SHA-512 Algorithmus anonymisiert wird.<br />
*Eine fortlaufende Sequenznummer.<br />
*Die genutzte Version des Salespoint-Frameworks, um festzustellen wie schnell bereitgestellte Updates genutzt werden.<br />
*Das genutzte Betriebssystem und die Java-Version, um herauszufinden welche Plattformen für zukünftige Framework-Tests in Betracht gezogen werden müssen.<br />
*Die aktuelle Sprache des Benutzers, um die Notwendigkeit zusätzlicher Lokalisierungen zu erheben.<br />
<br />
Alle Informationen wurden verschlüsselt übertragen und in aggregierter Form gespeichert.<br /><br />
Als Konsequenz der Beschwerden von Praktikumsteilnehmern und aufgrund der mangelnden Transparenz des oben beschriebenen Verfahrens entschuldigen wir uns für die entstandene Verunsicherung und haben die '''Datenübermittlungsfunktion mit Version 1.0.3''' des Frameworks vorbehaltlich einer späteren Einigung mit den Praktikumsteilnehmern '''entfernt'''. Informationen älterer Framework-Versionen werden vom Lehrstuhlserver mit sofortiger Wirkung nicht mehr angenommen. <br /><br /><br />
'''29.05.2010: Version 2010-1.0.2 released'''<br /><br />
'''10.05.2010: Version 2010-1.0.1 released'''<br /><br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://www.ifsr.de/forum/viewforum.php?f=39 Hackbrett >> Softwaretechnologie >> Salespoint]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-06-21T14:09:02Z<p>Lk: /* Aktuelle Ereignisse */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
'''21.06.2010: Version 2010-1.0.5 released'''<br/><br />
* web applications can be deployed to real server environments (that doesn't have gui mapping layer)<br />
<br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
'''08.06.2010: Version 2010-1.0.3 released'''<br /><br />
<br /><br />
Aufgrund der negativen Resonanz zur Übermittlung von Daten des Salespoint-Frameworks an den Server des Lehrstuhls Softwaretechnologie soll an dieser Stelle aufgeklärt werden, welche Informationen zu welchem Zweck übermittelt werden.<br /> <br />
Der Zweck dieser Datenübermittlung besteht darin, dass über den Zeitraum des Praktikums hinweg die durchschnittliche Belastung der Praktikumsteilnehmer in den verschiedenen Phasen gemessen werden kann. Damit sind qualitative Verbesserungen, insbesondere in Bezug auf die Aufwandsverteilung, für zukünftige Semester/Praktika möglich. Folgende Daten wurden im Einzelnen übertragen:<br /><br />
*Der System Fingerprint: Eine aus der Java Umgebung generierte Zeichenfolge, die mittels des SHA-512 Algorithmus anonymisiert wird.<br />
*Eine fortlaufende Sequenznummer.<br />
*Die genutzte Version des Salespoint-Frameworks, um festzustellen wie schnell bereitgestellte Updates genutzt werden.<br />
*Das genutzte Betriebssystem und die Java-Version, um herauszufinden welche Plattformen für zukünftige Framework-Tests in Betracht gezogen werden müssen.<br />
*Die aktuelle Sprache des Benutzers, um die Notwendigkeit zusätzlicher Lokalisierungen zu erheben.<br />
<br />
Alle Informationen wurden verschlüsselt übertragen und in aggregierter Form gespeichert.<br /><br />
Als Konsequenz der Beschwerden von Praktikumsteilnehmern und aufgrund der mangelnden Transparenz des oben beschriebenen Verfahrens entschuldigen wir uns für die entstandene Verunsicherung und haben die '''Datenübermittlungsfunktion mit Version 1.0.3''' des Frameworks vorbehaltlich einer späteren Einigung mit den Praktikumsteilnehmern '''entfernt'''. Informationen älterer Framework-Versionen werden vom Lehrstuhlserver mit sofortiger Wirkung nicht mehr angenommen. <br /><br /><br />
'''29.05.2010: Version 2010-1.0.2 released'''<br /><br />
'''10.05.2010: Version 2010-1.0.1 released'''<br /><br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://www.ifsr.de/forum/viewforum.php?f=39 Hackbrett >> Softwaretechnologie >> Salespoint]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-06-21T14:07:27Z<p>Lk: /* Aktuelle Ereignisse */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
'''21.06.2010: Version 2010-1.0.5 released'''<br/><br />
* web applications can be deployed to real server environments (that doesn't have gui mapping layer)<br />
<br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
<br /><br />
Aufgrund der negativen Resonanz zur Übermittlung von Daten des Salespoint-Frameworks an den Server des Lehrstuhls Softwaretechnologie soll an dieser Stelle aufgeklärt werden, welche Informationen zu welchem Zweck übermittelt werden.<br /> <br />
Der Zweck dieser Datenübermittlung besteht darin, dass über den Zeitraum des Praktikums hinweg die durchschnittliche Belastung der Praktikumsteilnehmer in den verschiedenen Phasen gemessen werden kann. Damit sind qualitative Verbesserungen, insbesondere in Bezug auf die Aufwandsverteilung, für zukünftige Semester/Praktika möglich. Folgende Daten wurden im Einzelnen übertragen:<br /><br />
*Der System Fingerprint: Eine aus der Java Umgebung generierte Zeichenfolge, die mittels des SHA-512 Algorithmus anonymisiert wird.<br />
*Eine fortlaufende Sequenznummer.<br />
*Die genutzte Version des Salespoint-Frameworks, um festzustellen wie schnell bereitgestellte Updates genutzt werden.<br />
*Das genutzte Betriebssystem und die Java-Version, um herauszufinden welche Plattformen für zukünftige Framework-Tests in Betracht gezogen werden müssen.<br />
*Die aktuelle Sprache des Benutzers, um die Notwendigkeit zusätzlicher Lokalisierungen zu erheben.<br />
<br />
Alle Informationen wurden verschlüsselt übertragen und in aggregierter Form gespeichert.<br /><br />
Als Konsequenz der Beschwerden von Praktikumsteilnehmern und aufgrund der mangelnden Transparenz des oben beschriebenen Verfahrens entschuldigen wir uns für die entstandene Verunsicherung und haben die '''Datenübermittlungsfunktion mit Version 1.0.3''' des Frameworks vorbehaltlich einer späteren Einigung mit den Praktikumsteilnehmern '''entfernt'''. Informationen älterer Framework-Versionen werden vom Lehrstuhlserver mit sofortiger Wirkung nicht mehr angenommen. <br /><br /><br />
<br />
<br />
Um aktuelle Informationen, Hinweise und Hotfixes zu finden besuchen sie bitte die [[Aktuelle_Ereignisse|News]]<br /><br />
'''19.06.2010: Version 2010-1.0.4 released'''<br /><br />
* Swing components got names for qftests <br />
* JModelFilter now works with Tables<br />
* WindowsExtensions dll load issues solved<br />
<br />
'''08.06.2010: Version 2010-1.0.3 released'''<br /><br />
'''29.05.2010: Version 2010-1.0.2 released'''<br /><br />
'''10.05.2010: Version 2010-1.0.1 released'''<br /><br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://www.ifsr.de/forum/viewforum.php?f=39 Hackbrett >> Softwaretechnologie >> Salespoint]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-06-21T14:02:46Z<p>Lk: /* Framework-Versionen */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.6.jar|Salespoint-2010-1.0.6.jar]]</span><span class="fileInfo"> (2,15 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_4.jar Salespoint-2010-1.0.4.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_3.jar Salespoint-2010-1.0.3.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_2.jar Salespoint-2010-1.0.2.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_1.jar Salespoint-2010-1.0.1.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-1.0.1-javadoc.rar Salespoint-2010-1.0.1-javadoc.rar]</span><span class="fileInfo"> (2,91 MB)</span> gezippt für offline Benutzung<br />
* Gleiches in freien Formaten: [http://www1.inf.tu-dresden.de/~swt-10-43/sp-javadoc/salespoint-2010-1.0.1-javadoc.7z 7z (814K)], [http://www1.inf.tu-dresden.de/~swt-10-43/sp-javadoc/salespoint-2010-1.0.1-javadoc.zip zip (3.3M)]<br />
* [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/javadoc/ Online Version]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-06-21T14:02:35Z<p>Lk: /* Framework-Versionen */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
<span class="dangerousLink">[[Media:Salespoint-2010-1.0.6.jar|Salespoint-2010-1.0.6.jar]]</span><span class="fileInfo"> (2,15 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_4.jar Salespoint-2010-1.0.4.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_3.jar Salespoint-2010-1.0.3.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_2.jar Salespoint-2010-1.0.2.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/sp2010_1_0_1.jar Salespoint-2010-1.0.1.jar]</span><span class="fileInfo"> (2,11 MB)</span><br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-1.0.1-javadoc.rar Salespoint-2010-1.0.1-javadoc.rar]</span><span class="fileInfo"> (2,91 MB)</span> gezippt für offline Benutzung<br />
* Gleiches in freien Formaten: [http://www1.inf.tu-dresden.de/~swt-10-43/sp-javadoc/salespoint-2010-1.0.1-javadoc.7z 7z (814K)], [http://www1.inf.tu-dresden.de/~swt-10-43/sp-javadoc/salespoint-2010-1.0.1-javadoc.zip zip (3.3M)]<br />
* [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/javadoc/ Online Version]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Web_ErweiterungWeb Erweiterung2010-05-26T16:33:28Z<p>Lk: /* DoubleView */</p>
<hr />
<div>Seit der Version 2010 stellt Salespoint die Möglichkeit sowie Hilfsmittel bereit, in einem ServletContainer (vorrangig [http://tomcat.apache.org/ Apache Tomcat]) ausgeführt zu werden.<br />
<br />
=Allgemeines=<br />
Basierend auf dem etablierten, quelloffenen Framework Spring (in der Version 3), dessen MVC-Umsetzung das Request/Response-Handling kapselt und die Entwicklung einer Webapplikation stark vereinfacht. Die komplette Salespoint Daten- und Nutzerverwaltung mit integrierte Persistenz kann auf gleiche, bisher beschriebene Weise genutzt werden. Wie Spring funktioniert kann einerseits in den Web-Tutorials sowie in der sehr anschaulichen und umfangreichen [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Dokumentation von Spring] nachgelesen werden.<br />
<br />
Im Folgenden wird auf die von Salespoint bereitgestellte TagLibary zur Visualisierung von Datenbeständen sowie einiger Kontrollstrukturen näher eingegangen.<br />
<br />
=TagLibary=<br />
In eine JSP-Datei lässt sich die TagLibary mit folgendem Befehl einbinden:<br />
<code xml><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
</code><br />
==View==<br />
''View-Tag''s dienen dem Rendern von Katalogen und Beständen. Diese werden in ein ''AbstractTabelModel'' (ATM), welches den Katalog bzw Bestand in tabellarischer beschreibt, überführt und dem ''ViewTag'' übergeben, welcher daraus eine entsprechende Repräsentation in HTML rendert. Es gibt 3 einfach zu benutzende konkrete ''ViewTag''s:<br />
<br />
*;Table: rendert eine native HTML-Tabelle mit HTML-Tags wie <nowiki><table>,<tr>,<td>,...</nowiki><br />
<br />
*;CssTable: rendert eine CSS-Tabelle mit HTML-Tags wie <nowiki><div style="display:table;">,<div style="display:table-row;">,<div style="display:table-cell;">,...</nowiki><br />
<br />
*;List: rendert eine 'floating'-List. Dabei werden die Zeilen des ATM als Objekte interpretiert und die Zellen in <nowiki><object></nowiki>-Tags als Absätze eingefügt. Um die floatenden Objekte ist eine Devision, welche mit ''style="display:inline-block;"'' den Umfluss nicht auf äußere Tags überträgt.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|abstractTableModel<br />
|align="center"|X<br />
|align="center"|<br />
|ATM des zu rendernden Datenbestandes<br />
|-<br />
|renderSettingsConfigurator<br />
|align="center"|<br />
|align="center"| leer<br />
|Settings zum darstellen des Views. Die Hierarchie der Einstellungen ist wie folgt (die jeweils nachfolgende Instanz überschreibt die Vorhergehende):<br/><br />
View-Settings --> renderSettingsConfigurator --> Attribute der Tags<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|-<br />
|style<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-style-Attribut zur Inline-CSS-Definition<br />
|-<br />
|searchField<br />
|align="center"|<br />
|align="center"| false<br />
|true, wenn ein Sucheingabefeld zum Filtern eingeblendet werden soll. Achtung: SearchFieldInterceptor aktivieren! [[FAQ#Wie aktiviere ich diese Interceptor?]]<br />
|-<br />
|searchString<br />
|align="center"|<br />
|align="center"| leer<br />
|Zeichenkette, nach der der Bestand gefiltert werden soll. Funktioniert unabhängig von der Einblendung des Sucheingabefeldes.<br />
|-<br />
|extraCols<br />
|align="center"|<br />
|align="center"| leer<br />
|Liste mit ExtraColumns für den Datenbestand<br />
|-<br />
|positionOfExtraColumns<br />
|align="center"|<br />
|align="center"| BACK<br />
|Position der ExtraColumns<br />
|-<br />
|zebraStyle<br />
|align="center"|<br />
|align="center"| false<br />
|Wechselt das class-attribut einer Tabellenzeile zwischen 'table-row-even' and 'table-row-odd'. (Nur in 'Table' und 'CssTable' verfügbar.)<br />
|-<br />
|caption<br />
|align="center"|<br />
|align="center"| leer<br />
|Eine Überschrift für den View.<br />
|-<br />
|summary<br />
|align="center"|<br />
|align="center"| leer<br />
|Eine 'summary' kann nur in Views mit NativeHtmlTable oder direkt im Table-Tag verwendet werden. In allen anderen Fällen wird sie nicht mitgerendert. (Das 'summary'-Attribut wird in einem <nowiki><table></nowiki>-Tag gesetzt und kann mit Blindentastaturen ausgelesen werden.)<br />
|-<br />
|viewDirection<br />
|align="center"|<br />
|align="center"| LEFT<br />
|(Funktioniert nur in Listen) Positioniert die Objekte einer Liste in umgekehrter Reihenfolge.<br />
|}<br />
<br />
Für fortgeschrittende Nutzung stehen 2 weitere ViewTags zur Verfügung:<br />
*;View: allg. ViewRenderer, dem zusätzlich eine ''IHtmlViewRepresentation'' übergeben wird. Sinnvoll, wenn man eine eigene Html-Repräsentation definieren will.<br />
<br />
*;CompletedView: rendert eine übergebene ''View''-Klasse. Sinnvoll, wenn man eine eigene View definieren will.<br />
<br />
==DoubleView==<br />
Der DoubleViewTag ist die Oberflächenkomponente neben dem DoubleViewController und stellt die Möglichkeit bereit, den Benutzer Elemente von einem Katalog bzw. Bestand in einen anderen verschieben zu lassen. Wie dieses Zusammenwirken funktioniert, wird in [[Videoautomat_Web#Ausleih-_und_Bezahlvorgang]] beschrieben.<br />
<br />
'''Achtung:''' Der DoubleViewTag kann nur in Zusammenwirken mit dem DoubleViewController sinnvoll eingesetzt werden!<br />
<br />
Der DoubleViewTag beinhaltet 2 ViewTags und kann auf 2 verschiedene Weisen eingesetzt werden:<br />
<br />
'''Beispiel:'''<br />
#Views als Attribute<code xml><sp:DoubleView sourceView="${sourceATM}" destinationView="${destATM}" /></code><br />
#Views als InnerTag<code xml><br />
<sp:DoubleView><br />
<sp:Table abstractTableModel="${sourceATM}" /><br />
<sp:Table abstractTableModel="${destATM}" /><br />
</sp:DoubleView><br />
</code><br />
<br />
Die 2 Variante wird empfohlen, da sich damit die individuellen ViewTags komplett und wie die einzeln Verwendeten konfigurieren lassen.<br />
'''Achtung:''' Es müssen exakt 2 ViewTags angegeben werden - dies können auch unterschiedliche ViewTags sein.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|sourceView<br />
|align="center"|<br />
|align="center"|null<br />
|ATM des zu rendernden Quelldatenbestandes<br />
|-<br />
|destinationView<br />
|align="center"|<br />
|align="center"|null<br />
|ATM des zu rendernden Zieldatenbestandes<br />
|-<br />
|showNumberField<br />
|align="center"|<br />
|align="center"|false<br />
|''true'', wenn ein Eingabefeld für die Anzahl eingeblendet werden soll<br />
|-<br />
|showBackButton<br />
|align="center"|<br />
|align="center"|true<br />
|''false'', wenn der Button zum zurück-Verschieben angezeigt werden soll<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}<br />
<br />
==LoginDialog==<br />
Der LoginDialogTag rendert ein einfaches LoginFormular.<br />
<br />
'''Achtung:''' LoginInterceptor notwendig! [[FAQ#Wie aktiviere ich diese Interceptor?]]<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:LoginDialog /><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|showUsers<br />
|align="center"|<br />
|align="center"|true<br />
|false, wenn statt der SelectBox mit registrierten Nutzernamen ein leeres Eingabefeld angezeigt werden soll<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}<br />
<br />
==LoggedIn==<br />
Der LoggedInTag ist eine Kontrollstruktur mitdessen Hilfe man Teile einer JSP-Seite anhand des Loginstatus aus- bzw. einblenden kann.<br />
'''Beispiel:'''<code xml><br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
</code><br />
Im Beispiel wird das LoginFormular nur dann angezeigt, wenn der diese Seite aufrufende Benutzer nicht eingeloggt ist.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|status<br />
|align="center"|X<br />
|align="center"|<br />
|true: alle inneren Tags werden gerendert, wenn der Nutzer eingeloggt ist.<br />
<br />
false: alle inneren Tags werden gerendert, wenn der Nutzer ausgeloggt ist. <br />
|}<br />
<br />
==HasCapability==<br />
Der HasCapabilityTag ist eine weitere Kontrollstruktur, die alle innere Tags nur dann anzeigt, wenn der aktuelle Nutzer eine Capability mit dem übergebenen Namen besitzt.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:HasCapability capabilityName="admin"><br />
<a href="/administrationSite">adminSite</a><br />
</sp:HasCapability><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|capabilityName<br />
|align="center"|X<br />
|align="center"|<br />
|Rendert alle inneren Tags nur, wenn der aktuelle Nutzer eine Capability mit diesem NAmen besitzt.<br />
|}<br />
<br />
==Image==<br />
Der ImageTag rendert ein übergebenes ''BufferdImage'' Base64-kodiert (gemäß RFC 2397) direkt in die HTML-Seite hinein.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:Image image="${user.image}" alt="avatar of ${user.name}" /><br />
</code><br />
<br />
'''Achtung:''' Alle gescheiten Browser (FireFox, Opera, Safari, Chrome) bis auf den IE unterstützen diese Technik. Der IE8 kann es zumindest für kleine Bilder (bis 32kb Base64-Codelänge).<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|image<br />
|align="center"|X<br />
|align="center"|<br />
|das zu rendernde Bild vom Typ BufferedImage<br />
|-<br />
|alt<br />
|align="center"|X<br />
|align="center"|<br />
|entspricht dem HTML-alt-Attribut, textuelle Beschreibung des Bildes<br />
|-<br />
|height<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-height-Attribut, Angabe mit '%' oder ohne Einheit(px)<br />
|-<br />
|width<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-width-Attribut, Angabe mit '%' oder ohne Einheit(px)<br />
|}<br />
<br />
==Message==<br />
Der MessageTag rendert die übergebene Liste von Strings.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:Message messages="${spErros}" styleName="errorMessages" /><br />
<sp:Message messages="${spFlash}" styleName="flashMessages" /><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|messages<br />
|align="center"|X<br />
|align="center"|<br />
|Liste von Strings mit anzuzeigenden Nachrichten<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Web_ErweiterungWeb Erweiterung2010-05-26T16:33:16Z<p>Lk: /* DoubleView */</p>
<hr />
<div>Seit der Version 2010 stellt Salespoint die Möglichkeit sowie Hilfsmittel bereit, in einem ServletContainer (vorrangig [http://tomcat.apache.org/ Apache Tomcat]) ausgeführt zu werden.<br />
<br />
=Allgemeines=<br />
Basierend auf dem etablierten, quelloffenen Framework Spring (in der Version 3), dessen MVC-Umsetzung das Request/Response-Handling kapselt und die Entwicklung einer Webapplikation stark vereinfacht. Die komplette Salespoint Daten- und Nutzerverwaltung mit integrierte Persistenz kann auf gleiche, bisher beschriebene Weise genutzt werden. Wie Spring funktioniert kann einerseits in den Web-Tutorials sowie in der sehr anschaulichen und umfangreichen [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Dokumentation von Spring] nachgelesen werden.<br />
<br />
Im Folgenden wird auf die von Salespoint bereitgestellte TagLibary zur Visualisierung von Datenbeständen sowie einiger Kontrollstrukturen näher eingegangen.<br />
<br />
=TagLibary=<br />
In eine JSP-Datei lässt sich die TagLibary mit folgendem Befehl einbinden:<br />
<code xml><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
</code><br />
==View==<br />
''View-Tag''s dienen dem Rendern von Katalogen und Beständen. Diese werden in ein ''AbstractTabelModel'' (ATM), welches den Katalog bzw Bestand in tabellarischer beschreibt, überführt und dem ''ViewTag'' übergeben, welcher daraus eine entsprechende Repräsentation in HTML rendert. Es gibt 3 einfach zu benutzende konkrete ''ViewTag''s:<br />
<br />
*;Table: rendert eine native HTML-Tabelle mit HTML-Tags wie <nowiki><table>,<tr>,<td>,...</nowiki><br />
<br />
*;CssTable: rendert eine CSS-Tabelle mit HTML-Tags wie <nowiki><div style="display:table;">,<div style="display:table-row;">,<div style="display:table-cell;">,...</nowiki><br />
<br />
*;List: rendert eine 'floating'-List. Dabei werden die Zeilen des ATM als Objekte interpretiert und die Zellen in <nowiki><object></nowiki>-Tags als Absätze eingefügt. Um die floatenden Objekte ist eine Devision, welche mit ''style="display:inline-block;"'' den Umfluss nicht auf äußere Tags überträgt.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|abstractTableModel<br />
|align="center"|X<br />
|align="center"|<br />
|ATM des zu rendernden Datenbestandes<br />
|-<br />
|renderSettingsConfigurator<br />
|align="center"|<br />
|align="center"| leer<br />
|Settings zum darstellen des Views. Die Hierarchie der Einstellungen ist wie folgt (die jeweils nachfolgende Instanz überschreibt die Vorhergehende):<br/><br />
View-Settings --> renderSettingsConfigurator --> Attribute der Tags<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|-<br />
|style<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-style-Attribut zur Inline-CSS-Definition<br />
|-<br />
|searchField<br />
|align="center"|<br />
|align="center"| false<br />
|true, wenn ein Sucheingabefeld zum Filtern eingeblendet werden soll. Achtung: SearchFieldInterceptor aktivieren! [[FAQ#Wie aktiviere ich diese Interceptor?]]<br />
|-<br />
|searchString<br />
|align="center"|<br />
|align="center"| leer<br />
|Zeichenkette, nach der der Bestand gefiltert werden soll. Funktioniert unabhängig von der Einblendung des Sucheingabefeldes.<br />
|-<br />
|extraCols<br />
|align="center"|<br />
|align="center"| leer<br />
|Liste mit ExtraColumns für den Datenbestand<br />
|-<br />
|positionOfExtraColumns<br />
|align="center"|<br />
|align="center"| BACK<br />
|Position der ExtraColumns<br />
|-<br />
|zebraStyle<br />
|align="center"|<br />
|align="center"| false<br />
|Wechselt das class-attribut einer Tabellenzeile zwischen 'table-row-even' and 'table-row-odd'. (Nur in 'Table' und 'CssTable' verfügbar.)<br />
|-<br />
|caption<br />
|align="center"|<br />
|align="center"| leer<br />
|Eine Überschrift für den View.<br />
|-<br />
|summary<br />
|align="center"|<br />
|align="center"| leer<br />
|Eine 'summary' kann nur in Views mit NativeHtmlTable oder direkt im Table-Tag verwendet werden. In allen anderen Fällen wird sie nicht mitgerendert. (Das 'summary'-Attribut wird in einem <nowiki><table></nowiki>-Tag gesetzt und kann mit Blindentastaturen ausgelesen werden.)<br />
|-<br />
|viewDirection<br />
|align="center"|<br />
|align="center"| LEFT<br />
|(Funktioniert nur in Listen) Positioniert die Objekte einer Liste in umgekehrter Reihenfolge.<br />
|}<br />
<br />
Für fortgeschrittende Nutzung stehen 2 weitere ViewTags zur Verfügung:<br />
*;View: allg. ViewRenderer, dem zusätzlich eine ''IHtmlViewRepresentation'' übergeben wird. Sinnvoll, wenn man eine eigene Html-Repräsentation definieren will.<br />
<br />
*;CompletedView: rendert eine übergebene ''View''-Klasse. Sinnvoll, wenn man eine eigene View definieren will.<br />
<br />
==DoubleView==<br />
Der DoubleViewTag ist die Oberflächenkomponente neben dem DoubleViewController und stellt die Möglichkeit bereit, den Benutzer Elemente von einem Katalog bzw. Bestand in einen anderen verschieben zu lassen. Wie dieses Zusammenwirken funktioniert, wird [[Videoautomat_Web#Ausleih-_und_Bezahlvorgang]] beschrieben.<br />
<br />
'''Achtung:''' Der DoubleViewTag kann nur in Zusammenwirken mit dem DoubleViewController sinnvoll eingesetzt werden!<br />
<br />
Der DoubleViewTag beinhaltet 2 ViewTags und kann auf 2 verschiedene Weisen eingesetzt werden:<br />
<br />
'''Beispiel:'''<br />
#Views als Attribute<code xml><sp:DoubleView sourceView="${sourceATM}" destinationView="${destATM}" /></code><br />
#Views als InnerTag<code xml><br />
<sp:DoubleView><br />
<sp:Table abstractTableModel="${sourceATM}" /><br />
<sp:Table abstractTableModel="${destATM}" /><br />
</sp:DoubleView><br />
</code><br />
<br />
Die 2 Variante wird empfohlen, da sich damit die individuellen ViewTags komplett und wie die einzeln Verwendeten konfigurieren lassen.<br />
'''Achtung:''' Es müssen exakt 2 ViewTags angegeben werden - dies können auch unterschiedliche ViewTags sein.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|sourceView<br />
|align="center"|<br />
|align="center"|null<br />
|ATM des zu rendernden Quelldatenbestandes<br />
|-<br />
|destinationView<br />
|align="center"|<br />
|align="center"|null<br />
|ATM des zu rendernden Zieldatenbestandes<br />
|-<br />
|showNumberField<br />
|align="center"|<br />
|align="center"|false<br />
|''true'', wenn ein Eingabefeld für die Anzahl eingeblendet werden soll<br />
|-<br />
|showBackButton<br />
|align="center"|<br />
|align="center"|true<br />
|''false'', wenn der Button zum zurück-Verschieben angezeigt werden soll<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}<br />
<br />
==LoginDialog==<br />
Der LoginDialogTag rendert ein einfaches LoginFormular.<br />
<br />
'''Achtung:''' LoginInterceptor notwendig! [[FAQ#Wie aktiviere ich diese Interceptor?]]<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:LoginDialog /><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|showUsers<br />
|align="center"|<br />
|align="center"|true<br />
|false, wenn statt der SelectBox mit registrierten Nutzernamen ein leeres Eingabefeld angezeigt werden soll<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}<br />
<br />
==LoggedIn==<br />
Der LoggedInTag ist eine Kontrollstruktur mitdessen Hilfe man Teile einer JSP-Seite anhand des Loginstatus aus- bzw. einblenden kann.<br />
'''Beispiel:'''<code xml><br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
</code><br />
Im Beispiel wird das LoginFormular nur dann angezeigt, wenn der diese Seite aufrufende Benutzer nicht eingeloggt ist.<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|status<br />
|align="center"|X<br />
|align="center"|<br />
|true: alle inneren Tags werden gerendert, wenn der Nutzer eingeloggt ist.<br />
<br />
false: alle inneren Tags werden gerendert, wenn der Nutzer ausgeloggt ist. <br />
|}<br />
<br />
==HasCapability==<br />
Der HasCapabilityTag ist eine weitere Kontrollstruktur, die alle innere Tags nur dann anzeigt, wenn der aktuelle Nutzer eine Capability mit dem übergebenen Namen besitzt.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:HasCapability capabilityName="admin"><br />
<a href="/administrationSite">adminSite</a><br />
</sp:HasCapability><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|capabilityName<br />
|align="center"|X<br />
|align="center"|<br />
|Rendert alle inneren Tags nur, wenn der aktuelle Nutzer eine Capability mit diesem NAmen besitzt.<br />
|}<br />
<br />
==Image==<br />
Der ImageTag rendert ein übergebenes ''BufferdImage'' Base64-kodiert (gemäß RFC 2397) direkt in die HTML-Seite hinein.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:Image image="${user.image}" alt="avatar of ${user.name}" /><br />
</code><br />
<br />
'''Achtung:''' Alle gescheiten Browser (FireFox, Opera, Safari, Chrome) bis auf den IE unterstützen diese Technik. Der IE8 kann es zumindest für kleine Bilder (bis 32kb Base64-Codelänge).<br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|image<br />
|align="center"|X<br />
|align="center"|<br />
|das zu rendernde Bild vom Typ BufferedImage<br />
|-<br />
|alt<br />
|align="center"|X<br />
|align="center"|<br />
|entspricht dem HTML-alt-Attribut, textuelle Beschreibung des Bildes<br />
|-<br />
|height<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-height-Attribut, Angabe mit '%' oder ohne Einheit(px)<br />
|-<br />
|width<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-width-Attribut, Angabe mit '%' oder ohne Einheit(px)<br />
|}<br />
<br />
==Message==<br />
Der MessageTag rendert die übergebene Liste von Strings.<br />
<br />
'''Beispiel:'''<br />
<code xml><br />
<sp:Message messages="${spErros}" styleName="errorMessages" /><br />
<sp:Message messages="${spFlash}" styleName="flashMessages" /><br />
</code><br />
<br />
'''Attribute:'''<br />
{|border="1" cellpadding="2" cellspacing="0"<br />
! Name<br />
! Notwendig<br />
! Defaultwert<br />
! Beschreibung<br />
|-<br />
|messages<br />
|align="center"|X<br />
|align="center"|<br />
|Liste von Strings mit anzuzeigenden Nachrichten<br />
|-<br />
|id<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-id-Attribut<br />
|-<br />
|styleName<br />
|align="center"|<br />
|align="center"| leer<br />
|entspricht dem HTML-class-Attribut zur Zuweisung von CSS-Klassen<br />
|}</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-05-04T19:04:53Z<p>Lk: /* Videoautomat */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[[Media:Salespoint-2010-1.0-javadoc.zip|Salespoint-2010-1.0-javadoc.zip]]</span><span class="fileInfo"> (2,96 MB)</span> gezippt für offline Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/TutorienTutorien2010-05-04T19:04:03Z<p>Lk: /* Großmarkt */</p>
<hr />
<div>Die aufgeführten Tutorien sollen helfen, einen schnellen Einstieg in das Framework zu geben. Dabei ersetzten sie jedoch nicht die Dokumentation. Diese sollte zusätzlich gelesen bzw. benutzt werden. <br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop-src.zip|Salespoint-2010-videoautomat-desktop-src.zip]]</span><span class="fileInfo"> (10,39 MB)</span> - Quellcode<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop.jar|Salespoint-2010-videoautomat-desktop.jar]]</span><span class="fileInfo"> (9,1 MB)</span> - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web.zip|Salespoint-2010-videoautomat-web.zip]]</span><span class="fileInfo"> (11,95 MB)</span> - Webprojekt inklusive Quellcode<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web-test-gui.zip|Salespoint-2010-videoautomat-web-test-gui.zip]]</span><span class="fileInfo"> (6 KB)</span> - GUI QFTest<br />
<br />
==ScreenCasts==<br />
* [http://www.youtube.com/watch?v=ZI7gpzBoq6I Desktop Setup]<br />
* [http://www.youtube.com/watch?v=JNCIa4uhxq0 Web IDE Setup]<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE Eclipse Subversive Installation]<br />
* [http://www.youtube.com/watch?v=ZvA8DB-DeMA Catalogs]<br />
* [http://www.youtube.com/watch?v=dmO8mJMo5co PersistentList]<br />
* [http://www.youtube.com/watch?v=uosCmjCjyok PersistentMap]<br />
* [http://www.youtube.com/watch?v=EL-9dKiSlM8 Videoautomat Desktop Demo]<br />
* [http://www.youtube.com/watch?v=8-XJI3Z9jhc Web mit MySQL]<br />
* [http://www.youtube.com/watch?v=uYBbUg63F7k Web QFTest]<br />
<br />
==Großmarkt==<br />
Dieses Schritt-für-Schritt-Tutorial zeigt im Gegensatz zum "Einführenden Beispiel" den gesamten Projektverlauf - von der Analyse bis zum fertigen Programm und dessen Wartung - zur Realisierung einer Verkaufsanwendung unter Verwendung des Frameworks SalesPoint. <br />
<br /><br />
Das Großmarkt Beispiel finden sie auf den Seiten der [http://st.inf.tu-dresden.de/SalesPoint/v3.3/tutorial/index.html Salespoint v3.3 Homepage]<br />
* Es existiert eine kompatibilisierte Version für version 2010:<br />
**<span class="dangerousLink">[[Media:Salespoint-2010-desktop-market-src.zip|Salespoint-2010-desktop-market-src.zip]]</span><span class="fileInfo"> (7,12 MB)</span> - Quellcode<br />
**<span class="dangerousLink">[[Media:Salespoint-2010-desktop-market.jar|Salespoint-2010-desktop-market.jar]]</span><span class="fileInfo"> (7,54 MB)</span> - ausführbare JAR-Datei<br />
<br />
==SVN Tools==<br />
* [http://tortoisesvn.tigris.org TortoiseSVN]<br />
* weitere Hinweise finden sie unter [http://www.gidf.de/ gidf.de]<br />
<br />
==Eclipse und SVN==<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE ScreenCast: Subversive-Installation]<br />
* [http://www.eclipse.org/subversive/documentation/gettingStarted/aboutSubversive/install.php Installation von Subversive / Svn-Client für Eclipse]<br />
* weitere Hinweise finden sie unter [http://www.google.de/ google.de]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-desktop-market-src.zipDatei:Salespoint-2010-desktop-market-src.zip2010-05-04T19:03:35Z<p>Lk: Salespoint 2010 Großmarkt Desktop Quellcode</p>
<hr />
<div>Salespoint 2010 Großmarkt Desktop Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-desktop-market.jarDatei:Salespoint-2010-desktop-market.jar2010-05-04T19:00:50Z<p>Lk: Salespoint 2010 Großmarkt Desktop</p>
<hr />
<div>Salespoint 2010 Großmarkt Desktop</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/TutorienTutorien2010-05-04T18:58:16Z<p>Lk: /* Videoautomat */</p>
<hr />
<div>Die aufgeführten Tutorien sollen helfen, einen schnellen Einstieg in das Framework zu geben. Dabei ersetzten sie jedoch nicht die Dokumentation. Diese sollte zusätzlich gelesen bzw. benutzt werden. <br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop-src.zip|Salespoint-2010-videoautomat-desktop-src.zip]]</span><span class="fileInfo"> (10,39 MB)</span> - Quellcode<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop.jar|Salespoint-2010-videoautomat-desktop.jar]]</span><span class="fileInfo"> (9,1 MB)</span> - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web.zip|Salespoint-2010-videoautomat-web.zip]]</span><span class="fileInfo"> (11,95 MB)</span> - Webprojekt inklusive Quellcode<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web-test-gui.zip|Salespoint-2010-videoautomat-web-test-gui.zip]]</span><span class="fileInfo"> (6 KB)</span> - GUI QFTest<br />
<br />
==ScreenCasts==<br />
* [http://www.youtube.com/watch?v=ZI7gpzBoq6I Desktop Setup]<br />
* [http://www.youtube.com/watch?v=JNCIa4uhxq0 Web IDE Setup]<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE Eclipse Subversive Installation]<br />
* [http://www.youtube.com/watch?v=ZvA8DB-DeMA Catalogs]<br />
* [http://www.youtube.com/watch?v=dmO8mJMo5co PersistentList]<br />
* [http://www.youtube.com/watch?v=uosCmjCjyok PersistentMap]<br />
* [http://www.youtube.com/watch?v=EL-9dKiSlM8 Videoautomat Desktop Demo]<br />
* [http://www.youtube.com/watch?v=8-XJI3Z9jhc Web mit MySQL]<br />
* [http://www.youtube.com/watch?v=uYBbUg63F7k Web QFTest]<br />
<br />
==Großmarkt==<br />
Dieses Schritt-für-Schritt-Tutorial zeigt im Gegensatz zum "Einführenden Beispiel" den gesamten Projektverlauf - von der Analyse bis zum fertigen Programm und dessen Wartung - zur Realisierung einer Verkaufsanwendung unter Verwendung des Frameworks SalesPoint. <br />
<br /><br />
Das Großmarkt Beispiel finden sie auf den Seiten der [http://st.inf.tu-dresden.de/SalesPoint/v3.3/tutorial/index.html Salespoint v3.3 Homepage]<br />
* Es existiert eine kompatibilisierte Version für version 2010:<br />
**[http://files.kraiz.de/sp/salespoint-2010-desktop-market-src.zip salespoint-2010-desktop-market-src.zip]<br />
**[http://files.kraiz.de/sp/salespoint-2010-desktop-market.jar salespoint-2010-desktop-market.jar]<br />
<br />
==SVN Tools==<br />
* [http://tortoisesvn.tigris.org TortoiseSVN]<br />
* weitere Hinweise finden sie unter [http://www.gidf.de/ gidf.de]<br />
<br />
==Eclipse und SVN==<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE ScreenCast: Subversive-Installation]<br />
* [http://www.eclipse.org/subversive/documentation/gettingStarted/aboutSubversive/install.php Installation von Subversive / Svn-Client für Eclipse]<br />
* weitere Hinweise finden sie unter [http://www.google.de/ google.de]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/TutorienTutorien2010-05-04T18:58:01Z<p>Lk: /* Videoautomat */</p>
<hr />
<div>Die aufgeführten Tutorien sollen helfen, einen schnellen Einstieg in das Framework zu geben. Dabei ersetzten sie jedoch nicht die Dokumentation. Diese sollte zusätzlich gelesen bzw. benutzt werden. <br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop-src.zip|Salespoint-2010-videoautomat-desktop-src.zip]]</span><span class="fileInfo"> (10,39 MB)</span> - Quellcode<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop.jar|Salespoint-2010-videoautomat-desktop.jar]]</span><span class="fileInfo"> (9,1 MB)</span> - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web.zip|Salespoint-2010-videoautomat-web.zip]]</span><span class="fileInfo"> (11,95 MB)</span> - Webprojekt inklusive Quellcode<br />
<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web-test-gui.zip|Salespoint-2010-videoautomat-web-test-gui.zip]]</span><span class="fileInfo"> (6 KB)</span> - GUI QFTest<br />
<br />
==ScreenCasts==<br />
* [http://www.youtube.com/watch?v=ZI7gpzBoq6I Desktop Setup]<br />
* [http://www.youtube.com/watch?v=JNCIa4uhxq0 Web IDE Setup]<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE Eclipse Subversive Installation]<br />
* [http://www.youtube.com/watch?v=ZvA8DB-DeMA Catalogs]<br />
* [http://www.youtube.com/watch?v=dmO8mJMo5co PersistentList]<br />
* [http://www.youtube.com/watch?v=uosCmjCjyok PersistentMap]<br />
* [http://www.youtube.com/watch?v=EL-9dKiSlM8 Videoautomat Desktop Demo]<br />
* [http://www.youtube.com/watch?v=8-XJI3Z9jhc Web mit MySQL]<br />
* [http://www.youtube.com/watch?v=uYBbUg63F7k Web QFTest]<br />
<br />
==Großmarkt==<br />
Dieses Schritt-für-Schritt-Tutorial zeigt im Gegensatz zum "Einführenden Beispiel" den gesamten Projektverlauf - von der Analyse bis zum fertigen Programm und dessen Wartung - zur Realisierung einer Verkaufsanwendung unter Verwendung des Frameworks SalesPoint. <br />
<br /><br />
Das Großmarkt Beispiel finden sie auf den Seiten der [http://st.inf.tu-dresden.de/SalesPoint/v3.3/tutorial/index.html Salespoint v3.3 Homepage]<br />
* Es existiert eine kompatibilisierte Version für version 2010:<br />
**[http://files.kraiz.de/sp/salespoint-2010-desktop-market-src.zip salespoint-2010-desktop-market-src.zip]<br />
**[http://files.kraiz.de/sp/salespoint-2010-desktop-market.jar salespoint-2010-desktop-market.jar]<br />
<br />
==SVN Tools==<br />
* [http://tortoisesvn.tigris.org TortoiseSVN]<br />
* weitere Hinweise finden sie unter [http://www.gidf.de/ gidf.de]<br />
<br />
==Eclipse und SVN==<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE ScreenCast: Subversive-Installation]<br />
* [http://www.eclipse.org/subversive/documentation/gettingStarted/aboutSubversive/install.php Installation von Subversive / Svn-Client für Eclipse]<br />
* weitere Hinweise finden sie unter [http://www.google.de/ google.de]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-videoautomat-web-test-gui.zipDatei:Salespoint-2010-videoautomat-web-test-gui.zip2010-05-04T18:57:19Z<p>Lk: Salespoint 2010 Videoatuomat Web GUI-Test mit QF-Test</p>
<hr />
<div>Salespoint 2010 Videoatuomat Web GUI-Test mit QF-Test</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-05-04T18:50:28Z<p>Lk: /* Videoautomat */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[[Media:Salespoint-2010-1.0-javadoc.zip|Salespoint-2010-1.0-javadoc.zip]]</span><span class="fileInfo"> (2,96 MB)</span> gezippt für offline Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]<br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop-src.zip|Salespoint-2010-videoautomat-desktop-src.zip]]</span><span class="fileInfo"> (10,39 MB)</span> - Quellcode<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-desktop.jar|Salespoint-2010-videoautomat-desktop.jar]]</span><span class="fileInfo"> (9,1 MB)</span> - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** <span class="dangerousLink">[[Media:Salespoint-2010-videoautomat-web.zip|Salespoint-2010-videoautomat-web.zip]]</span><span class="fileInfo"> (11,95 MB)</span> - Webprojekt inklusive Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-05-04T18:48:43Z<p>Lk: /* Dokumentation zum Framework */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* <span class="dangerousLink">[[Media:Salespoint-2010-1.0-javadoc.zip|Salespoint-2010-1.0-javadoc.zip]]</span><span class="fileInfo"> (2,96 MB)</span> gezippt für offline Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]<br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-videoautomat-desktop-src.zip salespoint-2010-videoautomat-desktop-src.zip] - Quellcode<br />
** [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-videoautomat-desktop.jar salespoint-2010-videoautomat-desktop.jar] - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-videoautomat-web.war salespoint-2010-videoautomat-web.war] - Webprojekt inklusive Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-05-04T18:47:27Z<p>Lk: /* Framework-Versionen */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-1.0.jar|Salespoint-2010-1.0.jar]]</span><span class="fileInfo"> (2,08 MB)</span><br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-dependencies.jar|Salespoint-2010-dependencies.jar]]</span><span class="fileInfo"> (4,98 MB)</span><br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*<span class="dangerousLink">[[Media:Salespoint-2010-blankweb.zip|Salespoint-2010-blankweb.zip]]</span><span class="fileInfo"> (2,24 MB)</span><br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* gezippt für [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-1.0-javadoc.zip offline] Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]<br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-videoautomat-desktop-src.zip salespoint-2010-videoautomat-desktop-src.zip] - Quellcode<br />
** [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-videoautomat-desktop.jar salespoint-2010-videoautomat-desktop.jar] - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** [http://www.st.inf.tu-dresden.de/SalesPoint/v4.0/download/salespoint-2010-videoautomat-web.war salespoint-2010-videoautomat-web.war] - Webprojekt inklusive Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-blankweb.zipDatei:Salespoint-2010-blankweb.zip2010-05-04T18:40:30Z<p>Lk: Salespoint 2010 BlankWeb-Projekt - gezipptes WAR-Archiv</p>
<hr />
<div>Salespoint 2010 BlankWeb-Projekt - gezipptes WAR-Archiv</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-videoautomat-web.zipDatei:Salespoint-2010-videoautomat-web.zip2010-05-04T18:39:46Z<p>Lk: Salespoint 2010 Videoautomat Web (inkl. Quellcode) - gezipptes WAR-Archiv</p>
<hr />
<div>Salespoint 2010 Videoautomat Web (inkl. Quellcode) - gezipptes WAR-Archiv</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-videoautomat-desktop-src.zipDatei:Salespoint-2010-videoautomat-desktop-src.zip2010-05-04T18:36:34Z<p>Lk: Salespoint 2010 Videoatuomat Desktop Quellcode</p>
<hr />
<div>Salespoint 2010 Videoatuomat Desktop Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-videoautomat-desktop.jarDatei:Salespoint-2010-videoautomat-desktop.jar2010-05-04T18:34:13Z<p>Lk: Salespoint 2010 Videoautomat Desktop</p>
<hr />
<div>Salespoint 2010 Videoautomat Desktop</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-1.0-javadoc.zipDatei:Salespoint-2010-1.0-javadoc.zip2010-05-04T18:31:08Z<p>Lk: Salespoint 2010 Javadoc</p>
<hr />
<div>Salespoint 2010 Javadoc</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-1.0.jarDatei:Salespoint-2010-1.0.jar2010-05-04T18:30:17Z<p>Lk: Salespoint 2010 Framework</p>
<hr />
<div>Salespoint 2010 Framework</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Datei:Salespoint-2010-dependencies.jarDatei:Salespoint-2010-dependencies.jar2010-05-04T18:21:39Z<p>Lk: Zusammenfassung aller Abhängigkeiten von Salespoint 2010 (im Wesentlichen Datenbanktreiber)</p>
<hr />
<div>Zusammenfassung aller Abhängigkeiten von Salespoint 2010 (im Wesentlichen Datenbanktreiber)</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/TutorienTutorien2010-04-26T22:27:27Z<p>Lk: /* ScreenCasts */</p>
<hr />
<div>Die aufgeführten Tutorien sollen helfen, einen schnellen Einstieg in das Framework zu geben. Dabei ersetzten sie jedoch nicht die Dokumentation. Diese sollte zusätzlich gelesen bzw. benutzt werden. <br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop-src.zip salespoint-2010-videoautomat-desktop-src.zip] - Quellcode<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop.jar salespoint-2010-videoautomat-desktop.jar] - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-web.war salespoint-2010-videoautomat-web.war] - Webprojekt inklusive Quellcode<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-web-test-gui.qft salespoint-2010-videoautomat-web-test-gui.qft] - GUI QFTest<br />
<br />
==ScreenCasts==<br />
* [http://www.youtube.com/watch?v=ZI7gpzBoq6I Desktop Setup]<br />
* [http://www.youtube.com/watch?v=JNCIa4uhxq0 Web IDE Setup]<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE Eclipse Subversive Installation]<br />
* [http://www.youtube.com/watch?v=ZvA8DB-DeMA Catalogs]<br />
* [http://www.youtube.com/watch?v=dmO8mJMo5co PersistentList]<br />
* [http://www.youtube.com/watch?v=uosCmjCjyok PersistentMap]<br />
* [http://www.youtube.com/watch?v=EL-9dKiSlM8 Videoautomat Desktop Demo]<br />
* [http://www.youtube.com/watch?v=8-XJI3Z9jhc Web mit MySQL]<br />
* [http://www.youtube.com/watch?v=uYBbUg63F7k Web QFTest]<br />
<br />
==Großmarkt==<br />
Dieses Schritt-für-Schritt-Tutorial zeigt im Gegensatz zum "Einführenden Beispiel" den gesamten Projektverlauf - von der Analyse bis zum fertigen Programm und dessen Wartung - zur Realisierung einer Verkaufsanwendung unter Verwendung des Frameworks SalesPoint. <br />
<br /><br />
Das Großmarkt Beispiel finden sie auf den Seiten der [http://st.inf.tu-dresden.de/SalesPoint/v3.3/tutorial/index.html Salespoint v3.3 Homepage]<br />
* Es existiert eine kompatibilisierte Version für version 2010:<br />
**[http://files.kraiz.de/sp/salespoint-2010-desktop-market-src.zip salespoint-2010-desktop-market-src.zip]<br />
**[http://files.kraiz.de/sp/salespoint-2010-desktop-market.jar salespoint-2010-desktop-market.jar]<br />
<br />
==SVN Tools==<br />
* [http://tortoisesvn.tigris.org TortoiseSVN]<br />
* weitere Hinweise finden sie unter [http://www.gidf.de/ gidf.de]<br />
<br />
==Eclipse und SVN==<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE ScreenCast: Subversive-Installation]<br />
* [http://www.eclipse.org/subversive/documentation/gettingStarted/aboutSubversive/install.php Installation von Subversive / Svn-Client für Eclipse]<br />
* weitere Hinweise finden sie unter [http://www.google.de/ google.de]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-04-26T09:17:56Z<p>Lk: /* Ansprechpartner */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
<br />
Um aktuelle Informationen, Hinweise und Hotfixes zu finden besuchen sie bitte die [[Aktuelle_Ereignisse|News]]<br />
<br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die '''Salespoint''' direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://www.ifsr.de/forum/viewforum.php?f=39 Hackbrett >> Softwaretechnologie >> Salespoint]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Salespoint_2010Salespoint 20102010-04-26T09:17:38Z<p>Lk: /* Ansprechpartner */</p>
<hr />
<div>[[Datei:general_logo.png|center|Salespoint]]<br />
<br /><br />
{| border="0" width="100%"<br />
| align="center" | '''Willkommen auf der Salespoint 2010 Homepage'''<br />
|-<br />
| align="center" | Hier finden sie technische Informationen und Downloads rund um das Salespoint Framework.<br />
|-<br />
| align="center" | Alle Teilnehmer des Praktikums sind dazu eingeladen an der Verbesserung dieses Wikis aktiv teilzunehmen um ihre Erfahrungen mit anderen Studenten zu teilen. Bearbeiten sie dazu freigegebene Seiten. Mißbrauch wird entsprechend geahndet.<br />
|}<br />
==Aktuelle Ereignisse==<br />
<br />
Um aktuelle Informationen, Hinweise und Hotfixes zu finden besuchen sie bitte die [[Aktuelle_Ereignisse|News]]<br />
<br />
'''08.04.2010: Version 2010-1.0 released'''<br />
<br />
==Videoautomat Reloaded Screenshots==<br />
<br />
{| width="100%"<br />
| align="center" |<br />
Watch Tutorials on [[Datei:Youtube.png]] [http://www.youtube.com/user/swtprak2010 http://www.youtube.com/user/swtprak2010]<br />
|}<br />
<br />
Einige Screenshots des Videoautomaten. Der Sourcecode und die ausführbare Datei sind unter [[Downloads]] zu finden. Weiterhin gibt es ein ausführliches [[Videoautomat|Tutorial]].<br />
<br />
{| width="100%"<br />
| align="center" |<br />
[[Datei:Va1 small.png|230px]]<br />
[[Datei:Va2 small.png|230px]]<br />
[[Datei:Va3 small.png|230px]]<br />
[[Datei:Va4 small.png|230px]]<br />
[[Datei:Va5 small.png|230px]]<br />
[[Datei:Va6 small.png|230px]]<br />
|}<br />
<br />
==Tutorien==<br />
<br />
Einen kleinen Einstieg in das Framework und eine komplette Anleitung zur Gestaltung eines Softwareprojektes findest Du auf diesen Seiten. Außerdem gibt es hier kleine Hilfen zum Umgang mit Eclipse und Svn. <br />
<br /><br />
[[Tutorien]]<br />
<br />
==Dokumentation==<br />
<br />
Die API-Spezifikation des SalesPoint-Frameworks (javadoc), ein kleiner technischer Überblick zum ersten Einstieg, eine technische Referenz und ein paar HowTos zum besseren Verständnis verbergen sich hinter diesem Verweis.<br />
<br /><br />
[[Dokumentation]]<br />
<br />
==Download==<br />
<br />
Nicht nur die aktuelle und frühere Framework-Version steht Dir hier zum Herunterladen zu Verfügung. Es gibt auch einige Druck- und HTML-Versionen für zu Hause.<br />
<br /><br />
[[Downloads]] <br />
<br />
==FAQ==<br />
<br />
Oft gestellte Fragen zum Framework und die dazugehörigen Antworten aus den letzten Semestern sind an dieser Stelle nochmal zusammengetragen.<br />
<br /><br />
[[FAQ]]<br />
<br />
==Weiterführende Links==<br />
<br />
Informationen zum Softwarepraktikum gibt es auf der [http://www.st.inf.tu-dresden.de/content/index.php?node=teaching&leaf=1&subject=142 Homepage des Lehrstuhls] .<br /><br />
Fragen und Bugreports mit im [http://www.ifsr.de/forum/viewforum.php?f=97 Forum] posten. <br /><br />
Die frühere SalesPoint version 3.3 finden sie unter [http://st.inf.tu-dresden.de/SalesPoint/v3.3/ Salespoint v3.3]<br />
<br />
==Ansprechpartner==<br />
Für den Fall dass es Probleme '''mit dem Wiki''' gibt, kontaktieren sie bitte einen der folgenden Ansprechpartner<br />
* Thomas Kissinger ([mailto:freekmastah@gmail.com freekmastah@gmail.com])<br />
* Lars Kreisz ([mailto:s7218282@mail.inf.tu-dresden.de s7218282@mail.inf.tu-dresden.de])<br />
Alle Anfragen, die Salespoint direkt betreffen sollten bitte im Forum gestellt werden:<br />
* [http://www.ifsr.de/forum/viewforum.php?f=39 Hackbrett >> Softwaretechnologie >> Salespoint]<br />
<br />
==Interner Bereich==<br />
[[Interner Entwicklungs Bereich]]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/TutorienTutorien2010-04-09T00:47:53Z<p>Lk: </p>
<hr />
<div>Die aufgeführten Tutorien sollen helfen, einen schnellen Einstieg in das Framework zu geben. Dabei ersetzten sie jedoch nicht die Dokumentation. Diese sollte zusätzlich gelesen bzw. benutzt werden. <br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop-src.zip salespoint-2010-videoautomat-desktop-src.zip] - Quellcode<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop.jar salespoint-2010-videoautomat-desktop.jar] - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-web.war salespoint-2010-videoautomat-web.war] - Webprojekt inklusive Quellcode<br />
<br />
==ScreenCasts==<br />
* [http://www.youtube.com/watch?v=ZI7gpzBoq6I Desktop Setup]<br />
* [http://www.youtube.com/watch?v=JNCIa4uhxq0 Web IDE Setup]<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE Eclipse Subversive Installation]<br />
* [http://www.youtube.com/watch?v=ZvA8DB-DeMA Catalogs]<br />
* [http://www.youtube.com/watch?v=dmO8mJMo5co PersistentList]<br />
* [http://www.youtube.com/watch?v=uosCmjCjyok PersistentMap]<br />
<br />
==Großmarkt==<br />
Dieses Schritt-für-Schritt-Tutorial zeigt im Gegensatz zum "Einführenden Beispiel" den gesamten Projektverlauf - von der Analyse bis zum fertigen Programm und dessen Wartung - zur Realisierung einer Verkaufsanwendung unter Verwendung des Frameworks SalesPoint. <br />
<br /><br />
Das Großmarkt Beispiel finden sie auf den Seiten der [http://st.inf.tu-dresden.de/SalesPoint/v3.3/tutorial/index.html Salespoint v3.3 Homepage]<br />
* Es existiert eine kompatibilisierte Version für version 4.0: [http://st.inf.tu-dresden.de/SalesPoint/v4.0/download/grossmarkt_compat.zip Großmarkt 4.0 Source]<br />
<br />
==SVN Tools==<br />
* [http://tortoisesvn.tigris.org TortoiseSVN]<br />
* weitere Hinweise finden sie unter [http://www.gidf.de/ gidf.de]<br />
<br />
==Eclipse und SVN==<br />
* [http://www.youtube.com/watch?v=KJ-MvcTditE ScreenCast: Subversive-Installation]<br />
* [http://www.eclipse.org/subversive/documentation/gettingStarted/aboutSubversive/install.php Installation von Subversive / Svn-Client für Eclipse]<br />
* weitere Hinweise finden sie unter [http://www.google.de/ google.de]</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-09T00:30:06Z<p>Lk: /* Bezahlen */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
try { <br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
} catch (DuplicateUserException dke) {}<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, <br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteDBESSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
<br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-09T00:28:46Z<p>Lk: /* Bezahlen */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
try { <br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
} catch (DuplicateUserException dke) {}<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, <br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteDBESSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
<br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-09T00:26:13Z<p>Lk: /* Ausleihen */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
try { <br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
} catch (DuplicateUserException dke) {}<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, <br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteDBESSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-09T00:24:35Z<p>Lk: /* Ausleihen */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
try { <br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
} catch (DuplicateUserException dke) {}<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, <br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteDBESSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-09T00:09:41Z<p>Lk: /* Nutzerregistrierung */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
try { <br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
} catch (DuplicateUserException dke) {}<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, <br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteSSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-08T23:57:36Z<p>Lk: /* Usermanager */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
try { <br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
} catch (DuplicateUserException dke) {}<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.registerSuccessful", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
return index(mav);<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
videoautomat.registerSuccessful = registered successfully as ''{0}''<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt und eine Erfolgsnachricht in die ModelMap geschrieben sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteSSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-08T23:53:25Z<p>Lk: /* Nutzerregistrierung */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(userName) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.registerSuccessful", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
return index(mav);<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
videoautomat.registerSuccessful = registered successfully as ''{0}''<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt und eine Erfolgsnachricht in die ModelMap geschrieben sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteSSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-08T23:51:49Z<p>Lk: /* Nutzerregistrierung */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(name) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.registerSuccessful", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
return index(mav);<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
videoautomat.registerSuccessful = registered successfully as ''{0}''<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt und eine Erfolgsnachricht in die ModelMap geschrieben sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteSSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-08T23:43:50Z<p>Lk: /* Nutzerregistrierung */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(name) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.registerSuccessful", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
return index(mav);<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
videoautomat.registerSuccessful = registered successfully as ''{0}''<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt und eine Erfolgsnachricht in die ModelMap geschrieben sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteSSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/Videoautomat_WebVideoautomat Web2010-04-08T23:38:31Z<p>Lk: /* Startseite mit Login */</p>
<hr />
<div>=Einleitung=<br />
Frameworks erleichtern die Programmierarbeit in vielerlei Hinsicht. Sie können Datenstrukturen und Prozesse eines bestimmten Anwendungsgebietes vordefinieren und darüber hinaus einen sauberen Entwurf erzwingen. Dennoch bedeutet ihre Verwendung zunächst einen erhöhten Einarbeitungsaufwand für den Programmierer. Um diesen zu minimieren wurde die folgende Abhandlung geschrieben. Grundlage für das Verständnis dieses Tutorials ist der Technische Überblick über das Framework SalesPoint. Wer diesen bisher noch nicht gelesen hat, wird gebeten diese Abkürzung zu nehmen.<br />
<br />
Des weiteren sehr hilfreich zu Installation der Entwicklungsumgebung ist dieser [http://www.youtube.com/watch?v=JNCIa4uhxq0 ScreenCast].<br />
<br />
==Spring Basics==<br />
Zusätzlich kommt als Webframework Spring zum Einsatz. Dieses Framework stellt eine MVC-Implementierung bereit, die Anfragen an den Webserver annimmt, an den richtigen Controller (das Salespoint-Web-Äquivalent zum ''Salespoint'') weiterleitet, der auf dem Model (Catalogs, Stocks, Users) arbeitet und bestimmt welche View (JSP-Datei) als Antwort zum Browser zurückgesendet wird.<br />
Spring ist ein sehr umfangreiches Framework und hat viele weitere Einsatzgebiete als nur Webapplikationen. Es wird kein allumfassendes Verständnis darüber verlangt, bei auftretenden MVC-Problemen sowie Fragen zur Erweiterung hier angeführter Möglichkeiten sei die [http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Spring-Dokumentation der Version 3] allerdings '''erste Anlaufstelle'''. Im Folgenden wird ein ganz grober MVC-Überblick von Spring geliefert. Es bestehen '''sehr viele''' andere Konfigurationsmöglichkeiten. Wir verwenden eine annotationsbasierte Konfiguration, die in der salespoint-2010-blankweb.war hinterlegt ist und als Ausgangsbasis für eine neues Projekt benutzt werden kann.<br />
<br />
===Servlet Engine Konfiguration===<br />
In Javabasierten Webprojekten ist eine gewisse Verzeichnisstruktur vorgegeben. Wichtig hierbei ist, dass die Datei ''WebContent/WEB-INF/web.xml'' existiert, welche die grundlegende Konfiguration der Webapplikation darstellt. Neben dem Namen und einer Beschreibung der Applikation wird anhand von URL-Pattern festgelegt, welche Anfragen auf welche Servlets (Javaklassen, die ein gewisses Interface Implementieren) abgebildet werden. Da dies nur wenig Abstraktionsmöglichkeiten zulässt, definieren wir neben einem ''default''-Servlet, das nur statische Dateien wie Bilder, CSS-Dateien, etc. ausliefert, nur einen großen FrontController, der alle Anfragen annimmt und delegieren somit das Abbildungsproblem an diesen. Im Falle von Spring ist dies der ''DisplatchServlet''.<br />
<br />
'''WebContent/WEB-INF/web.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />
xmlns="http://java.sun.com/xml/ns/javaee" <br />
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" <br />
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"><br />
<br />
<!--Basicsettings--><br />
<display-name>sp2010_videoautomat_web</display-name><br />
<description>SalesPoint2010-BlankWebapplication</description><br />
<br />
<!--Mapping of static resources--><br />
<servlet-mapping><br />
<servlet-name>default</servlet-name><br />
<url-pattern>/static/*</url-pattern><br />
</servlet-mapping><br />
<br />
<!--DispatcherConfig--><br />
<servlet> <br />
<description>Spring MVC Dispatcher Servlet</description> <br />
<servlet-name>dispatch</servlet-name> <br />
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <br />
<load-on-startup>2</load-on-startup> <br />
</servlet><br />
<servlet-mapping> <br />
<servlet-name>dispatch</servlet-name><br />
<url-pattern>/</url-pattern><br />
</servlet-mapping><br />
<br />
</web-app><br />
</code><br />
<br />
Als Servlet Engine/Container wird Tomcat in der Version 6.x empfohlen.<br />
<br />
===Spring Konfiguration===<br />
[[Datei:spring_mvc.png]]<br />
<br />
Die Grafik stammt aus der Spring Dokumentation und zeigt Spring's MVC-Prinzip. Der Frontcontroller entspricht, wie oben erwähnt, dem DispatchServlet. Dieser wird in der ''WebContent/WEB-INF/dispatch-servlet.xml'' näher konfiguriert.<br />
<br />
Bevor darauf näher eingegangen werden kann, gilt es Spring's ''Dependency Injection'' zu verstehen. Die Idee dabei ist, Teile der Application möglichst lose miteinander zu koppeln - gemeinsame Abhängigkeiten nicht zwischeneinander ständig hinundherzureichen, sondern von außen zu ''injizieren''. Man lässt somit Spring XML-konfiguriert Instanzen von Klassen erzeugen und jeweils untereinander injizieren.<br />
<br />
'''WebContent/WEB-INF/dispatch-servlet.xml'''<br />
<code xml><br />
<?xml version="1.0" encoding="UTF-8"?> <br />
<beans xmlns="http://www.springframework.org/schema/beans"<br />
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br />
xmlns:p="http://www.springframework.org/schema/p"<br />
xmlns:mvc="http://www.springframework.org/schema/mvc"<br />
xmlns:context="http://www.springframework.org/schema/context"<br />
xsi:schemaLocation="http://www.springframework.org/schema/beans<br />
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd<br />
http://www.springframework.org/schema/context<br />
http://www.springframework.org/schema/context/spring-context-3.0.xsd<br />
http://www.springframework.org/schema/mvc<br />
http://www.springframework.org/schema/mvc/spring-mvc.xsd"><br />
<br />
<!-- messages for i18n --><br />
1 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"><br />
1.1 <property name="basename" value="messages" /><br />
</bean><br />
<br />
<!-- use the interceptor-enabled annotation based handler mapping --><br />
2 <bean class="org.salespointframework.web.spring.annotations.SalespointAnnotationHandlerMapping"><br />
<property name="messageSource" ref="messageSource" /><br />
</bean><br />
<br />
<!-- scan this package for annotated controllers --><br />
3 <context:component-scan base-package="org.salespointframework.web.examples.videoautomat" /><br />
<br />
<!-- very standard viewresolver --><br />
4 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><br />
<property name="prefix" value="/jsp/" /><br />
<property name="suffix" value=".jsp" /><br />
</bean><br />
<br />
</beans><br />
</code><br />
#Erzeugt eine Instanz der angegebenen Klasse welche später per definierter id referenzierbar ist. In diesem Fall wird eine MessageSource-Instanz erzeugt, die für die Internationalisierung verwendet wird.<br />
##Nach der Instanzierung wird per Setter das Attribut "basename" mit dem String "messages" gesetzt, was bedeutet dass im classpath die Datei messages.properties(sowie für weitere Sprachen z.b. messages_de.properties, messages_en.properties) erwartet wird, in der unter gewissen Codes die richtige Sprachversion des Textes abgelegt wird.<br />
#Instanziiert ein Salespointspezifisches HandlerMapping, welches anhand von annotatierten Klassen ein URL-auf-Controller-Mapping bereitstellt.<br />
##injeziert die MessageSource-Instanz<br />
#Gibt ein Package an, in dem nach annotierten Klassen gesucht werden soll. Dieser Tag kann mehrmals einsetzt werden um mehrere Packages durchsuchen zu lassen.<br />
#Erzeugt ein ViewResolver, der vom Controller zurückgegebene ViewNames auf einen Pfad zur JSP abbildet, z.B "index" => "/jsp/index.jsp"<br />
<br />
Zusammenfassend bedeutet diese Konfiguration, dass das Package ''org.salespointframework.web.spring'' nach annotierten Klassen durchsucht wird, die selbst per Annotation bestimmen, auf welche URLs sie reagieren, und Strings zurückgeben, die vom viewResolver auf den Pfad zur JSP-Datei gemappt wird.<br />
<br />
=Der Grundaufbau=<br />
<br />
==Aufbau des Shops==<br />
<br />
Begonnen wird mit der zentralen Klasse einer jeden SalesPoint-Anwendung, dem Shop. Es wird eine neue Klasse VideoShop erzeugt, als Ableitung von Shop.<br />
<code java><br />
@Component<br />
public class VideoShop extends Shop {<br />
public VideoShop() {<br />
super();<br />
Shop.setTheShop(this);<br />
}<br />
}<br />
</code><br />
Der Konstruktor von ''VideoShop'' ruft den Konstruktor der Oberklasse durch den Befehl ''super()'' auf und setzt sich selbst über die statische Methode ''Shop.setTheShop()''. Dieser Aufruf bewirkt, dass die übergebene Instanz zur einzigen und global erreichbaren erhoben wird. Auf diese globale Instanz kann über die ebenfalls statische Methode Shop.getTheShop() von überall aus zugegriffen werden. Das hier angewandte Entwurfsmuster Singleton ist insofern zweckmäßig, da über dieses einzelne Shopobjekt nahezu alle global benötigten Daten gekapselt werden können.<br />
Die Annotation ''@Component'' verrät Spring, dass es sich um die Instanziierung dieser Klasse beim Start der Webapplikation zu kümmern hat.<br />
<br />
==Der Videokatalog==<br />
<br />
Die Videos eines Automaten zeichnen sich durch einen Titel und die jeweilige Anzahl, sowie den Einkaufspreis für den Betreiber und den Verkaufspreis für den Kunden aus. Entsprechend bietet sich zu ihrer Datenhaltung ein CountingStock an. Ein solcher Bestand referenziert auf die Einträge des ihm zugeordneten Katalogs und speichert deren verfügbare Anzahl. Die Katalogeinträge wiederum besitzen die Attribute Bezeichnung und Preis.<br />
<br />
Dementsprechend wird zunächst ein Catalog benötigt, der die Videonamen und -preise enthält. Da es sich dabei um ein Interface handelt, bedarf es einer Klasse, die dieses Schnittstellenverhalten implementiert. Im Framework existiert bereits eine vordefinierte Klasse namens CatalogImpl, die für die meisten Zwecke ausreichen dürfte. Der Konstruktor dieser Klasse verlangt einen Bezeichner, der den Katalog eindeutig von anderen unterscheidet. Es wird zunächst folgende Zeile der Klasse VideoShop hinzugefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
public static final CatalogIdentifier<CatalogItemImpl> C_VIDEOS = new CatalogIdentifier<CatalogItemImpl>("VideoCatalog");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Diese Konstante soll der künftige Bezeichner für den Videokatalog sein. Kataloge werden in SalesPoint (ähnlich der Java Collection API) nach deren Einträgen getypt. Dasselbe gilt für ihre Bezeichner. Um nicht immer die generischen Parameter mit angeben zu müssen ist es zweckmäßig eine eigene Klasse dafür anzulegen:<br />
<code java> <br />
public class VideoCatalog extends CatalogImpl<CatalogItemImpl> {<br />
public VideoCatalog(CatalogIdentifier<CatalogItemImpl> id) {<br />
super(id);<br />
}<br />
}<br />
</code> <br />
<br />
Der Katalog wird im Konstruktor von VideoShop wie folgt instantiiert:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addCatalog(new VideoCatalog(C_VIDEOS));<br />
}<br />
}<br />
</code> <br />
<br />
Der Videokatalog ist durch die Aufnahme in die Katalogsammlung des Ladens von jeder Klasse der Anwendung aus erreichbar, jedoch ist der Aufruf, um an den Katalog zu gelangen unangenehm lang und wird vermutlich mehr als einmal verwendet. Zur Erleichterung wird eine statische Hilfsmethode in der Klasse VideoShop geschaffen, die den Videokatalog zurückgibt.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static VideoCatalog getVideoCatalog() {<br />
return (VideoCatalog) Shop.getTheShop().getCatalog(C_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Nun existiert zwar ein Katalog, jedoch ohne Einträge. Damit im weiteren Verlauf des Programmierens und Testens einige Daten zur Verfügung stehen, wird die MainClass um folgende Methode ergänzt:<br />
<code java><br />
public class MainClass {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
Category c1 = new Category("Action");<br />
Category c2 = new Category("Science Fiction");<br />
List<Video> videos = new ArrayList<Video>();<br />
try {<br />
videos.add(new Video("Event Horizon", "event_horizon", 1999, c2));<br />
videos.add(new Video("H.E.A.T.", "heat", 1999, c1));<br />
videos.add(new Video("Matrix", "matrix", 1499, c2));<br />
videos.add(new Video("Sin City", "sin_city", 2199, null));<br />
videos.add(new Video("Taken (Blue-Ray)", "taken", 3199, c1));<br />
videos.add(new Video("Terminator", "terminator", 999, c1));<br />
videos.add(new Video("Terminator 2", "terminator2", 999, c1));<br />
videos.add(new Video("Terminator 3", "terminator3", 1299, c1));<br />
videos.add(new Video("True Lies", "true_lies", 999, c1));<br />
videos.add(new Video("The X-Files", "xfiles", 999, c2));<br />
} catch (URISyntaxException e) {<br />
e.printStackTrace();<br />
} catch (NullPointerException e) {<br />
e.printStackTrace();<br />
}<br />
for (Video video : videos) {<br />
videoCatalog.add(video, null);<br />
}<br />
}<br />
}<br />
</code> <br />
Was hier passiert ist relativ leicht ersichtlich. Wir holen uns zuerst den VideoKatalog aus dem Shop mit der vorhin erstellten Methode getVideoCatalog(), erstellen daraufhin 2 Kategorien in unserem Anwendungsfall Genres, nach denen wir später die Videos besser sortieren können und fügen dann nach und nach einzelne Videos mit Titel, Bildtitel, Preiswert in Cent und Genrekategorie in eine zuvor erstellte Arraylist ein, aus der wir in der For-Schleife zum Schluss alles in unseren Katalog schieben. Was uns dazu fehlt ist natürlich die VideoKlasse die von CatalogItemImpl erbt, um in unseren VideoCatalog zu passen und Categorizable implementiert, um die Categories nutzbar zu machen.<br />
Dem Konstruktor von CatalogItemImpl muss mindestens ein String und ein Value übergeben werden. Value ist ein Interface und es existieren zwei Implementationen dieser Schnittstelle im Framework. Zum Einen NumberValue, welches einen numerischen Wert kapselt und QuoteValue, das ein Paar von Werten repräsentiert, z.B. einen Ein- und Verkaufswert. In diesem Fall wird dem Konstruktor unter anderem eine Instanz von DoubleValue (neben IntegerValue eine der beiden Spezialisierung von NumberValue) übergeben.<br />
Der RecoveryConstructor ist nötig für die Instanzierung des Objektes aus der Datenbank (siehe [[Persistence Layer]]). Der ResourceManager dient hier primär zum Beschaffen von binären Formaten wie vor allem Bildern. Hier ruft er über den Typ PNG, im Projektordner res die einzelnen Bilder ab. Diese entweder dem downloadbaren Sourceverzeichnis entnehmen oder einfach den Aufruf durch null ersetzen.<br />
<br />
<code java><br />
public class Video extends CatalogItemImpl implements Categorizable {<br />
<br />
private Category category = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public Video(String name) {<br />
super(name);<br />
}<br />
<br />
public Video(String name, String image, int price, Category category) throws URISyntaxException, NullPointerException {<br />
super(name, new DoubleValue(price), ResourceManager.getInstance().getResource(ResourceManager.RESOURCE_PNG, "videos." + image).toURI());<br />
this.category = category;<br />
}<br />
<br />
protected CatalogItemImpl getShallowClone() {<br />
return null;<br />
}<br />
<br />
public Category getCategory() {<br />
return category;<br />
}<br />
}<br />
</code><br />
<br />
<br />
Abschließend wird im Videoshop die neue Methode zur Ausführung gebracht, damit die Änderungen wirksam werden. Wir nutzen dazu die Methode initializeData zur Kapselung, da gleich noch weitere Initialisierungsmethoden dazukommen werden und fügen einen Aufruf dessen dem Kontruktor hinzu. Wenn die diese Daten bereits in der Datenbank enthalten sind, fällt eine DuplicateKeyException, die wir in diesem Fall einfach ignorieren können.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
try {<br />
initializeData();<br />
} catch(DuplicateKeyException dke){}<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
initializeVideos();<br />
} <br />
.<br />
.<br />
}<br />
</code><br />
<br />
==Der Videobestand==<br />
<br />
Nach der Fertigstellung des Katalogs kann im Folgenden der Bestand aufgebaut werden. Wie bereits im Abschnitt Der Videokatalog erwähnt, sollen die Videos des Automaten in einem CountingStock gespeichert werden. Auch für dieses Interface existiert eine vordefinierte Klasse mit dem gewohnten Impl am Ende des Namens. Ein jeder Bestand bezieht sich auf einen Katalog, so dass dieser konsequenterweise neben dem String-Bezeichner dem Konstruktor von CountingStockImpl übergeben werden muss. Ähnlich den Katalogen sind auch die Bestände nach ihren Einträgen getypt, zusätzlich aber auch noch mit den Eintragstypen des zugehörigen Katalogs. Allein aus diesem Grund lohnt es sich, eine eigene Klasse hierfür zu definieren:<br />
<code java><br />
public class AutomatVideoStock extends CountingStockImpl<StockItemImpl, CatalogItemImpl> {<br />
public AutomatVideoStock(StockIdentifier<StockItemImpl, CatalogItemImpl> siId, Catalog<CatalogItemImpl> ciRef) {<br />
super(siId, ciRef);<br />
}<br />
}<br />
</code><br />
<br />
StockItemImpl ist dabei die Standardimplementation eines Bestandseintrages. Wir werden später noch einmal etwas genauer darauf zurückkommen. Am Anfang der Shop-Klasse muss der Identifikator für den neuen Bestand deklariert werden:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final StockIdentifier<StockItemImpl, CatalogItemImpl> CC_VIDEOS = new StockIdentifier<StockItemImpl, CatalogItemImpl>("VideoStock");<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Jetzt können wir den eigentlichen Stock anlegen. Dazu wird analog zum Videokatalog in den Konstruktor von VideoShop folgende Zeile eingefügt:<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
addStock(new AutomatVideoStock(CC_VIDEOS, getCatalog(C_VIDEOS)));<br />
}<br />
.<br />
.<br />
}<br />
</code> <br />
<br />
Auch beim Videobestand lohnt es sich eine Hilfsmethode zu schreiben, die selbigen zurückliefert.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static AutomatVideoStock getVideoStock() {<br />
return (AutomatVideoStock) Shop.getTheShop().getStock(CC_VIDEOS);<br />
}<br />
}<br />
</code> <br />
<br />
Der neue Bestand ist wiederum leer. Durch das Hinzufügen einer Zeile in die Initialisierungsmethode der Videos im VideoShop können dem Videobestand die benötigten Testdaten zugefügt werden.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static void initializeVideos() {<br />
<br />
VideoCatalog videoCatalog = VideoShop.getVideoCatalog();<br />
AutomatVideoStock videoStock = VideoShop.getVideoStock();<br />
.<br />
.<br />
for (Video video : videos) {<br />
.<br />
.<br />
videoStock.add(video.getName(), 5, null);<br />
}<br />
}<br />
}<br />
</code> <br />
<br />
Der Aufruf add(String id, int count, DataBasket db) bewirkt, dass von dem Katalogeintrag mit der Bezeichnung id insgesamt count-Stück in den Bestand aufgenommen werden. Der DataBasket, der zum Schluss übergeben wird, hat etwas mit der Sichtbarkeit der vollführten Aktion zu tun. Vorerst reicht es zu wissen, dass hier durch die Übergabe von null das Hinzufügen unmittelbar wirksam wird.<br />
<br />
Zum Schluss noch die Klasse, die StockItemImpl erbt und es uns dadurch möglich macht später auf unserem VideoStock zu arbeiten.<br />
<code java><br />
public class VideoCassette extends StockItemImpl {<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public VideoCassette(String key) {<br />
super(key);<br />
}<br />
<br />
}<br />
</code><br />
<br />
==Geldkatalog==<br />
Da wir eine Bezahlfunktion einbauen wollen, brauchen wir eine Repräsentation von gültigen Geldeinheiten respektive Münzen/Scheine. Um diesen nicht bei Gebrauch ständig neu instanziieren zu müssen, legen wir ihn einfach als Katalog im Shop an.<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public static final String C_CURRENCY = "CurrencyCatalog";<br />
.<br />
.<br />
public VideoShop() {<br />
.<br />
.<br />
super.addCatalog(new EUROCurrencyImpl(C_CURRENCY));<br />
.<br />
.<br />
}<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeMoney();<br />
}<br />
.<br />
.<br />
public void initializeMoney() {<br />
getCurrency().initCurrencyItems();<br />
}<br />
.<br />
.<br />
public static EUROCurrencyImpl getCurrency() {<br />
return (EUROCurrencyImpl)Shop.getTheShop().getCatalog(C_CURRENCY);<br />
}<br />
}<br />
</code><br />
<br />
==Usermanager==<br />
Bevor mit dem ersten Prozess der Anwendung, der Nutzerregistrierung, begonnen werden kann, muss die Nutzerverwaltung angelegt werden. Zur Erinnerung: es können lediglich registrierte Kunden am Automaten Filme entleihen. Entsprechend braucht die Anwendung eine Datenstruktur, anhand derer der Automat erkennen kann, wer Kunde ist und wer nicht.<br />
<br />
SalesPoint bietet dafür die Klassen User und UserManager an.<br />
<br />
Der Nutzermanager ist eine Art Containerklasse, in der alle Nutzer gespeichert werden. Ebenso wie beim Shop wurde bei der Klasse UserManager auf das Entwurfsmuster Singleton zurückgegriffen, d.h. es gibt genau eine Instanz des Nutzermanagers, die über UserManager.getInstance() referenziert werden kann. <br />
<br />
Anwender des Programms können durch die Klasse User dargestellt werden. Ein User besitzt einen Namen, über den er eindeutig identifiziert werden kann. Darüber hinaus besteht die Möglichkeit ein Passwort zu setzen, sowie Rechte auf mögliche Aktionen zu vergeben.<br />
<br />
Damit die entliehenen Videos eines Kunden in der ihn repräsentierenden Instanz gekapselt werden können, muss eine neue Klasse von User abgeleitet werden. Abgesehen vom Kunden gibt es die Nutzergruppe der Betreiber bzw. Administratoren. Da diese sich im Prinzip nur darin unterscheiden, dass sie über mehr Rechte verfügen, genügt es, eine einzige Klasse für Kunden und Administratoren zu definieren.<br />
<br />
Die recover Methode(siehe [[Persistence Layer]]) wird bei der Wiederherstellung des Benutzers aufgerufen und initialisiert dort seinen VideoStock.<br />
<br />
<code java><br />
public class AutomatUser extends User implements Recoverable {<br />
<br />
@PersistenceProperty(follow = false)<br />
private UserVideoStock ss_videos = null;<br />
<br />
@RecoveryConstructor(parameters = { "m_sName" })<br />
public AutomatUser(String user_ID) {<br />
super(user_ID);<br />
}<br />
<br />
public AutomatUser(String user_ID, String passWd) {<br />
super(user_ID);<br />
setPassWd(garblePassWD(passWd));<br />
ss_videos = new UserVideoStock(user_ID, VideoShop.getVideoCatalog());<br />
}<br />
<br />
public UserVideoStock getVideoStock() {<br />
return ss_videos;<br />
}<br />
<br />
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit) {<br />
if (!reInit)<br />
ss_videos = new UserVideoStock(getName(), VideoShop.getVideoCatalog());<br />
}<br />
}<br />
</code> <br />
<br />
Im Gegensatz zum VideoShop werden hier die Videos in einem StoringStockImpl verwaltet. In einem solchen wird nicht nur die Anzahl gewisser Katalogeinträge gespeichert, sondern es wird jedes einzelne StockItem separat behandelt. Die Bestandseinträge der Videos werden über die Klasse VideoCassette definiert.<br />
<br />
Ähnlich dem Videokatalog und dem Videobestand des Automaten ist es auch hier zweckmäßig (aufgrund der Typisierung), eine neue Klasse UserVideoStock anzulegen, die ebendiese generischen Parameter festlegt:<br />
<code java><br />
public class UserVideoStock extends StoringStockImpl<VideoCassette, CatalogItemImpl> {<br />
public UserVideoStock(String sName, CatalogImpl<CatalogItemImpl> ciRef) {<br />
super(sName, ciRef);<br />
}<br />
} <br />
</code><br />
<br />
Im Gegensatz zu Katalog und Bestand des Automaten benötigen wir hier keinen eigenen Identifikator, da der UserVideoStock stets nur beim Nutzer und nie im Shop selbst registriert wird. Deswegen überladen wir auch den String - Konstruktor anstelle des Identifikator - Konstruktors.<br />
<br />
Weiterhin ist zu erkennen, dass beim Setzen des Passworts nicht das Passwort selbst, sondern der Rückgabewert der Methode garblePassWD(passWd) übergeben wird. Dabei handelt es sich um eine Sicherheitsmaßnahme. Damit das Passwort nicht im Klartext abgespeichert wird und ausgelesen werden kann, wird es vorher umschlüsselt. Welcher Algorithmus dabei verwendet wird, kann ebenfalls über die Klasse User gesetzt werden. Der voreingestellte Algorithmus für die Umschlüsselung ist einfach umkehrbar und daher für sicherheitsrelevante Kontexte nicht zu empfehlen. Wird das Passwort mit der Methode isPassWd(String s) überprüft, ist darauf zu achten, dass der übergebene String vorher ebenfalls umschlüsselt wird.<br />
<br />
Die boolesche Variable wird erst zu einem späteren Zeitpunkt für die Rechtevergabe benötigt.<br />
<br />
Zum Schluss sollen einige Testkunden und ein Administrator der Nutzerverwaltung hinzugefügt werden. Als Passwort wird der vereinfachten Testbarkeit halber jeweils eine leere Zeichenkette übergeben.<br />
<br />
Um den verschiedene Nutzergruppen zu unterscheiden, können ihnen verschiedene Capabilities zugeordnet werden. In unserm Fall eine mit dem Namen "admin".<br />
<code java><br />
public class VideoShop extends Shop {<br />
.<br />
.<br />
public void initializeData() {<br />
.<br />
.<br />
initializeUsers();<br />
}<br />
.<br />
.<br />
public static void initializeUsers() {<br />
AutomatUser usr = new AutomatUser("Administrator", ""));<br />
usr.setCapability(new NameCapability("admin"));<br />
UserManager.getInstance().addUser(usr);<br />
}<br />
}<br />
</code><br />
<br />
=Spring MVC Roundtrip=<br />
==Startseite mit Login==<br />
Nun haben wir das Model aus dem MVC-Pattern aufgebaut und können unseren ersten Controller mit entsprechender View erstellen. Zunächst soll die Startseite der Applikation erstellt werden, auf der man sich einloggen sowie nach dem Login den Videokatalog ansehen kann.<br />
<br />
Dazu erstellen wir die Datei ''WebContent/jsp/index.jsp'' mit folgendem Inhalt:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.salespoint-framework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
</sp:LoggedIn><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
In der JSP sehen wir neben gewöhnlichen HTML-Tags im oberen Bereich taglib-Deklarationen und im unteren Bereich dann dessen Verwendung. TagLibraries sind vorgefertigte Konstrukte, die zur Auswertung mit echtem HTML ersetzt werden. Salespoint stellt hiervon einige sehr nützliche bereit. Diese werden unter [[Web Erweiterung#TagLibrary]] detailiert beschrieben. Für uns im Moment wichtig zu wissen ist, dass der Message-Tag jeweils eine Liste von Strings HTML-formatiert ausgibt (Fehler bzw. Erfolgsnachrichten). Der LoggedIn-Tag rendert seinen Inhalt immer dann, wenn der diese Seite aufrufende Nutzer entweder eingeloggt ist oder nicht (entsprechend dem Attribut status). Der LoginDialog stellt ein LoginFormular bereit und der List-Tag listet uns ein übergebenes ''AbstractTableModel'' auf, welches eine Abstraktion von Katalogen und Beständen darstellt. Auffällig sind die Konstrukte alá "${videoCatalog}". Das sind Java-Objekte aus der sog. ModelMap, die wir im Controller befüllen.<br />
<br />
Dazu erstellen wir eine Klasse im Package bzw. in einem Unterpaket, welches nach Springkomponenten durchsucht (siehe oben) wird.<br />
<code java><br />
package org.salespointframework.web.examples.videoautomat.controller;<br />
<br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class Maincontroller {<br />
@RequestMapping("/")<br />
public ModelAndView index(ModelAndView mav) {<br />
ATMBuilder<VideoCatalog> videoCatalog = new ATMBuilder<VideoCatalog>(VideoShop.getVideoCatalog());<br />
mav.addObject("videoCatalog",videoCatalog.getATM());<br />
mav.setViewName("index");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wir annotieren die Klasse mit ''@Controller'', damit Spring und der DispatchServlet diese Klasse kennen und sie nach Methoden durchsuchen, die mit ''@RequestMapping'' annotiert sind. Somit weiß der DispatchServlet, dass er Anfragen auf das Wurzelverzeichnis unserer Webapplikation auf die auch als Action bezeichnete Methode ''MainController.index()'' weiterzuleiten hat. Diese wiederum erstellt bzw. befüllt das übergebene ModelAndView-Object mit den Daten, die wir in unserer JSP brauchen und gibt mit setViewName() auch die JSP an (der in der ''dispatch-servlet.xml'' angegebene viewResolver löst den String zu einem Pfad auf).<br />
Der ATMBuilder baut uns dem übergebenen VideoCatalog, den wir uns aus unserem VideoShop-Singleton holen, ein AbstractTabelModel, welches wir in das ModelAndView legen.<br />
Die zweite Annotation auf Klassenebene ''@Interceptors({LoginInterceptor.class})'' ist notwendet für die Loginfunktionalität. Interceptor in Spring ''unterbrechen'' die Ausführung von Actions, in dem sie entweder vor oder nach der Action ausgeführt werden. Die Annotation sagt aus, dass alle Methoden im MainController vom LoginInterceptor unterbrochen werden.<br />
<br />
Doch genug der grauen Theorie, nun ist der richtige Zeitpunkt, die Webapplikation zum ersten Mal zu starten. Es sollte ein Loginformular zu sehen sein mit dem Administrator (leeres Passwort). Nach dem Klick auf den LoginButton sollte eine Liste mit Videos zu sehen sein.<br />
<br />
==Nutzerregistrierung==<br />
Damit wir zwischen verschiedenen Nutzern unterscheiden können, brauchen diese eine Repräsentation im Model - einen Nutzeraccount. Hierzu können wir eine einfache Formularverarbeitung von Spring nutzen.<br />
Zuerst einmal fügen wir auf der Startseite einen Link auf das Registrierformular hinzu:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="false"><br />
<sp:LoginDialog /><br />
<a href="register"><spring:message code="videoautomat.register" /></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Augenmerk hierbei liegt auf dem MessageTag aus dem Spring-Namespace. Dieser schaut in unserer MessageSource nach diesem Code und ersetzt ihn mit der richtigen Sprachversion. Wenn man von Beginn einer neuen Webapplikation dieses Mechanismus nutzt, macht es bis auf das Übersetzen der ''messages.properties'' '''keine''' Zusatzarbeit, die gesamte Applikation in anderen Sprachen anzubieten. Wir fügen also diesen Code in unsere StandardMessageSourceDatei ein:<br />
<code java><br />
videoautomat.register = register<br />
</code><br />
Die nächste Aufgabe ist es, eine Action für diese URL zu definieren. Aus Bequemlichkeit gleich im MainController:<br />
<code java><br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.GET)<br />
public ModelAndView registerForm(ModelAndView mav) {<br />
mav.setViewName("registerForm");<br />
return mav;<br />
}<br />
.<br />
</code><br />
Diese Action nimmt ausschließlich GET-Anfragen auf der URL "/register" an und bestimmt ausschließlich die View(''WebContent/jsp/registerForm.jsp''):<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-Videoautomat</title><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<form:form method="post" action="register"><br />
<table><br />
<tr><br />
<td><spring:message code="videoautomat.userName" /></td><br />
<td><input type="text" name="userName" /></td><br />
</tr><br />
<tr><br />
<td><spring:message code="videoautomat.userPasswd" /></td><br />
<td><input type="password" name="userPasswd" /></td><br />
</tr><br />
<tr><br />
<td></td><br />
<td><input type="submit" value="<spring:message code="videoautomat.register" />" /></td><br />
</tr><br />
</table><br />
</form:form><br />
<br />
</body><br />
</html><br />
</code><br />
sowie die benutzten neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.userName = your name<br />
videoautomat.userPasswd = password<br />
</code><br />
Das HTML-Formular wird wie üblich als POST-Anfrage abgesendet und zwar an dieselbe URL ("/register"). Wir benötigen also eine weitere Action, die nun die POST-Anfragen mit den mitgesendeten Parametern annimmt. Spring bietet dafür eine sehr einfache Variante an, in dem annotationsbasiert diese Requestparameter an Parameter der Action gebunden werden. <br />
<code java><br />
@Controller<br />
@Interceptors({LoginInterceptor.class})<br />
public class MainController {<br />
.<br />
.<br />
@Autowired<br />
private MessageSource messageSource;<br />
<br />
public void setMessageSource(MessageSource messageSource) {<br />
this.messageSource = messageSource;<br />
}<br />
.<br />
.<br />
@RequestMapping(value="/register",method=RequestMethod.POST)<br />
public ModelAndView register(<br />
@RequestParam("userName") String userName,<br />
@RequestParam("userPasswd") String userPasswd,<br />
HttpServletRequest request,<br />
ModelAndView mav) {<br />
<br />
if(userName.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"user name"}, RequestContextUtils.getLocale(request)));<br />
if(userPasswd.isEmpty())<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.empty", new String[]{"password"}, RequestContextUtils.getLocale(request)));<br />
if(UserManager.getInstance().getUser(name) != null)<br />
MessagesUtil.addError(mav, messageSource.getMessage("videoautomat.userNameNotFree", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
<br />
if(MessagesUtil.hasErrors(mav)) {<br />
return registerForm(mav);<br />
} else {<br />
AutomatUser usr = new AutomatUser(userName, userPasswd);<br />
UserManager.getInstance().addUser(usr);<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.registerSuccessful", new String[]{userName}, RequestContextUtils.getLocale(request)));<br />
return index(mav);<br />
}<br />
}<br />
}<br />
.<br />
</code><br />
mit den neuen MessageSourceCodes:<br />
<code java><br />
videoautomat.empty = input field ''{0}'' should not be empty<br />
videoautomat.userNameNotFree = user name ''{0}'' is already taken by another user<br />
videoautomat.registerSuccessful = registered successfully as ''{0}''<br />
</code><br />
OK, nun der Reihe nach:<br />
*;MessageSource: Die über dem Attribut definierte Annotation ''@Autowired'' gibt Spring Bescheid, dass eine unter diesem Namen instanziierte JavaKlasse über die entsprechende Setter-Methode in diese Klasse injiziert werden soll (wir erinnern uns an den in der ''dispatch-servlet.xml'' definierten beanTag mit der id="messageSource"). Diese Instanz kann auf diese Weise zwischen allen Klassen der Applikation gemeinschaftlich und entsprechend speicherschonend benutzt werden, um lokalisierte Strings aufzulösen. Um das aktuelle Locale-Object zu bekommen, gibt es einen Toolbox-Klasse von Spring, die es aus dem ''Request''-Object, welches man sich einfach mit übergeben lassen kann, herausholt. Ein Zusatzfeature hierbei ist, dass auch für MessageSourceCodes Parameter mitgegeben und per geschweiften Klammern mit dem Index darin referenziert werden.<br />
*;MessageUtil: Toolbox zum Hinzufügen(add)/Abfragen(has)/Löschen(clear) von Fehler-(Error) oder Erfolgs-(Flash)nachrichten. Diese werden in einer Liste von Strings unter den Schlüsseln "spErrors" bzw. "spFlash" in der ModelMap abgelegt und können unter diesen auch wieder abgefragt werden (siehe JSP).<br />
<br />
Falls etwas schiefgegangen ist und Fehler dem ModelAndView hinzugefügt wurden, wird diesem der registerForm()-Methode übergeben, die auf der JSP das Formular sowie die Fehlermeldungen aus der MAV anzeigt. Falls keine Fehler aufgetreten sind, wird eine neue Instanz der Klasse AutomatUser erstellt, dem UserManager hingefügt und eine Erfolgsnachricht in die ModelMap geschrieben sowie auf die Hauptseite "geroutet".<br />
<br />
Diese Funktionalität sollte nun erstmal wieder getestet werden.<br />
<br />
==Ausleih- und Bezahlvorgang==<br />
Um Elemente von einem Catalog/Bestand benutzereigabengesteuert in einen anderen zu verschieben sollte stets die DoubleViewController/DoubleViewTag-Kombination verwendet werden. Der DoubleViewController stellt auf Basis seiner Initialisierung sämtliche Verschiebeaktionen bereit.<br />
<br />
Wenn ein Nutzer Videos ausleiht, verschiebt er sie vom Bestand des Automaten in seinen eigenen Videobestand. Diese Verschiebeaktion gilt allerdings solange als temporär, bis er sie im 2.Schritt auch bezahlt hat. Dies ist also ein Einsatzgebiet vom Salespoint ''DataBasket''. Eine Instanz vom DataBasket speichert die Information, welche Videos der Nutzer ausleihen will, und wenn er sie bezahlt hat, wird auf ihm ''commit()'' aufgerufen und damit die Verschiebeaktion persistent oder aber, falls der Nutzer sich anders entschieden hat und die Aktion abbricht, wird mit ''rollback()'' die temporäre Verschiebeaktion einfach rückgängig gemacht.<br />
<br />
===Ausleihen===<br />
Ein Link von der Hauptseite ''WebContent/jsp/index.jsp'' aus bietet den Zugang zum Ausleihvorgang:<br />
<code xml><br />
.<br />
<sp:LoggedIn status="true"><br />
<sp:List abstractTableModel="${videoCatalog}" /><br />
<a href="rent"><spring:message code="videoautomat.rent"/></a><br />
</sp:LoggedIn><br />
.<br />
</code><br />
Auf die Angabe der entsprechenden MessageSourceCodes wird absofort verzichtet.<br />
<br />
Eine neue Controller-Klasse, die vom DoubleViewController erbt, bildet die Ausgangsbasis. Fundamental wichtig hierbei ist die richtige Typisierung dieser Klasse. Macht euch bewusst, von welchem Datentyp zu welchem verschoben werden soll.<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
initialize(<br />
/*source*/ VideoShop.getVideoStock(),<br />
/*destination*/ ((AutomatUser)UserManager.getInstance().getCurrentUser(request.getSession())).getVideoStock(),<br />
/*moveStrategy*/ new CSSSStrategy() {<br />
@Override protected StockItem createStockItem(StockItem ci) {<br />
return new VideoCassette(ci.getName());<br />
}<br />
},<br />
/*dataBasket*/ new DataBasketImpl());<br />
<br />
mav.setViewName("rent");<br />
<br />
mav.addObject("videoStock", new ATMBuilder<AutomatVideoStock>(getSource())<br />
.db(getDataBasket())<br />
.getATM());<br />
<br />
mav.addObject("basket", new ATMBuilder<DataBasket>(getDataBasket())<br />
.ted(new VideoCassetteDBESSTED())<br />
.dbc(DataBasketConditionImpl.allStockItemsWithDest(getDestination()))<br />
.getATM());<br />
<br />
return mav;<br />
}<br />
}<br />
</code><br />
Nun wieder der Reihe nach die Neuerungen:<br />
*;@Scope("session"): Diese Annotation besagt, dass für jeden Nutzer eine neue Instanz dieses Controllers angelegt wird. Dies ist notwendig, da wir hier einen sog. ''stateful'' Controller habn, der über mehrere Requests hinweg unterschiedliche Daten für einen speziellen Nutzer speichert. Controller ohne explizit angegebenem Scope werden von Spring nur einmal instanziiert und für alle Nutzer verwendet.<br />
<br />
*;@RequestMapping: Die RequestMapping-Annotation kann auch auf Klassenebene verwendet werden und dessen Attribut wird dann als Prefix für alle Actions verwendet. Dies ist beim DoubleViewController sogar notwendet, da die ''index()''-Action '''unbedingt''' ein leeres RequestMapping haben muss.<br />
<br />
*;initialize(): Mit dieser Methode wird der DoubleViewController initialisiert. Dies sollte im Normalfall im Konstruktor des Controllers geschehen oder aber, wie in diesem Fall, in der index()-Action, wenn z.B. der Request nötig ist. Über den Request kommt man an das Session-Object über das ein Nutzer im UserManager angemeldet wird.<br />
**;source: Der Quellbestand, aus dem heraus verschoben werden soll.<br />
**;dest: Der Zielbestand, in den verschoben werden soll.<br />
**;moveStrategy: Die richtige(!) MoveStrategy. In unserm Fall ist der Quellbestand - der AutomatVideoStock - ein ''CountingStock'', der Zielbestand - der UserVideoStock - ein ''StoringStock'' -, also brauchen wir die ''CSSSStrategy'' aus dem Web-Package. Nicht alle dieser Strategien sind konkret, da z.b. aus einem CountingStock kein StockItem wieder herausgeholt werden kann. Deshalb muss in diesem Fall eine FactoryMethod implementiert werden, die ein frisches konkretes VideoCassette-Objekt aus einem StockItemImpl generiert.<br />
**;db: Eine DataBasket-Instanz, in der die Verschiebeaktionen gespeichert werden, um sie im Anschluss zu ''commit''tn oder zu ''rollback''n.<br />
*;ATMBuilder: Den ATMBuilder haben wir bereits auf der Startseite kennengelernt. Hier wird er allerdings in einer Version genutzt, die sich ''FluentBuilder'' nennt und auf dem ''MethodChaining''-Prinzip basiert. Methoden, die im Javadoc der Klasse als MethodChaining-Methoden ausgegeben wurden, geben nach dem Setzen des Attributes das Object selbst zurück, sodass in der selben Zeile damit weitergearbeitet werden kann.<br />
*;VideoCassetteDBESSTED: Mit einem ''TableEntryDescriptor''(TED) kann man bestimmen, wie sich die Elemente eines Catalogs/Stocks/DataBaskets in einer Visualisierung beschreiben sollen. Da wir hier einen DataBasket visualisieren wollen und dafür kein DefaultTED existiert, müssen wir einen angeben, der z.B. so aussieht:<br />
<code java><br />
public class VideoCassetteSSTED extends AbstractTableEntryDescriptor {<br />
<br />
private static final String[] cNames = { "Name", "Price" };<br />
private static final Class<?>[] cClasses = { String.class, Double.class };<br />
<br />
public int getColumnCount() {<br />
return cNames.length;<br />
}<br />
<br />
public String getColumnName(int nIdx) {<br />
return cNames[nIdx];<br />
}<br />
<br />
public Class<?> getColumnClass(int nIdx) {<br />
return cClasses[nIdx];<br />
}<br />
<br />
@Override<br />
public Object getValueAt(Object oRecord, int nIdx) {<br />
StoringStockItemDBEntry dbe = (StoringStockItemDBEntry)oRecord;<br />
VideoCassette si = (VideoCassette)dbe.getValue();<br />
<br />
switch (nIdx) {<br />
case 0: return si.getName();<br />
case 1: return new DecimalFormat("#.## &euro;").format(((NumberValue)si.getAssociatedItem(null).getValue()).getValue());<br />
}<br />
return null;<br />
}<br />
}<br />
</code><br />
Jetz haben wir 2 ATMs in der ModelMap und überführen diese, wie auf der Startseite, in der neuen ''WebContent/jsp/rent.jsp'' jeweils in einen ViewTag, die sich diesmal jedoch in einem DoubleViewTag befinden.<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
</head><br />
<body><br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:DoubleView showNumberField="true"><br />
<sp:Table abstractTableModel="${videoStock}" /><br />
<sp:Table abstractTableModel="${basket}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay" id="pay"><br />
<input type="submit" value="<spring:message code="videoautomat.rent" />" /><br />
</form:form><br />
<form:form method="get" action="rent/cancel" id="rent"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
2 weitere Buttons fügen wir ein und bestimmen action-Attribut, wohin die Reise geht, wenn der entsprechende Submit-Button gedrückt wird. Ersterer verlinkt nach "/pay", was in der nächsten Rubrik behandelt wird. Der zweite Button ruft die URL "rent/cancel" auf. Hierfür definieren wir eine Action im RentController:<br />
<code java><br />
.<br />
@RequestMapping("/rent")<br />
public class RentController extends DoubleViewController<AutomatVideoStock, UserVideoStock> {<br />
.<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
getDataBasket().rollback();<br />
mav.setViewName("redirect:/");<br />
return mav;<br />
}<br />
}<br />
</code><br />
Wie bereits erwähnt werden die RequestMapping-Annotation-Attribute konkateniert und ergeben die richtige URL. Auf dem DataBasket wird ''rollback()'' aufgerufen und damit verfallen alle getätigten Verschiebeaktionen. Danach wird kein echter ViewName gesetzt, sondern ein Redirect ausgelöst, der den ClientBrowser auf die Startseite weiterleitet.<br />
<br />
===Bezahlen===<br />
Für das Ziel des "pay"-Buttons auf der Ausleihseite - die Bezahlseite - brauchen wir einen neuen Controller, der von DoubleViewController erbt und auf die URL "/pay" lauscht:<br />
<code java><br />
@Controller<br />
@Scope("session")<br />
@RequestMapping("/pay")<br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
public PayController() {<br />
initialize(<br />
VideoShop.getCurrency(),<br />
new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true),<br />
new CCSStrategy(),<br />
null);<br />
}<br />
<br />
@RequestMapping("")<br />
public ModelAndView index(ModelAndView mav, HttpServletRequest request, HttpServletResponse response) {<br />
<br />
Value pricePaid = calculatePricePaid();<br />
Value priceToPay = calculatePriceToPay();<br />
<br />
mav.addObject("pricePayedEnough", pricePaid.compareTo(priceToPay) >= 0);<br />
<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.pricePayed",<br />
new String[{""+pricePaid,<br />
""+priceToPay,<br />
EUROCurrencyImpl.ABBREVIATION},<br />
LocaleContextHolder.getLocale()));<br />
/* videoautomat.pricePayed = {0} of {1} {2} payed */<br />
<br />
mav.addObject("currency",new ATMBuilder<Currency<CurrencyItemImpl>>(getSource()).getATM());<br />
<br />
mav.addObject("moneyBag", new ATMBuilder<MoneyBag>(getDestination())<br />
.zeros(false)<br />
.getATM()); <br />
<br />
mav.addObject("currencyExtraColumns", Collections.singletonList(new EuroCurrencyImageEC("50", null)));<br />
<br />
mav.setViewName("pay");<br />
return mav;<br />
}<br />
<br />
private Value calculatePricePaid() {<br />
return getDestination().sumStock(getDataBasket(), new CatalogItemValue(), new DoubleValue(0)).divide(new IntegerValue(100));<br />
}<br />
<br />
private Value calculatePriceToPay() {<br />
???<br />
}<br />
}<br />
</code><br />
Ein weiterer konkreter DoubleViewController: diesmal im Konstruktor initialisiert, da wir kein Request-Objekt benötigen. Der PayController verschiebt aus einem speziellen Geldkatalog "Currency" (sp incl.) in einen speziellen CountingStock "MoneyBag" (sp incl.) und symbolisiert das Einwerfen von Geldscheinen/-münzen in den Automat.<br />
<br />
Innerhalb der ''index()''-Methode berechnen wir den bereits gezahlten Preis bequem über die mittels ''sumStock()''-Methode. Den zu zahlenden Betrag allerdings lässt sich nur mit der Information was ausgeliehen werden soll berechnen. Wir benötigen also den DataBasket aus dem RentController. Hierzu bedienen wir uns wieder der ''@Autowired''-Annotation und lassen uns den RentController automatisch injizieren. Das geht, weil dieser mit ''@Controller'' annotiert und somit von Spring instanziiert wurde.<br />
<br />
<code java><br />
public class PayController extends DoubleViewController<Currency<CurrencyItemImpl>, MoneyBag> {<br />
<br />
@Autowired<br />
private RentController rentController;<br />
<br />
public void setRentController(RentController rentController) {<br />
this.rentController = rentController;<br />
}<br />
.<br />
.<br />
private Value calculatePriceToPay() {<br />
return rentController.getDataBasket().sumBasket(<br />
DataBasketConditionImpl.allStockItemsWithDest(rentController.getDestination()),<br />
new BasketEntryValue() { <br />
@Override<br />
public Value getEntryValue(DataBasketEntry dbe) {<br />
return ((VideoCassette)dbe.getValue()).getAssociatedItem(null).getValue();<br />
}<br />
},<br />
new DoubleValue(0));<br />
}<br />
}<br />
</code><br />
Nun können wir über den DataBasket fast genauso komfortabel summieren: "fast", weil eine Mapperklasse mitgegeben werden muss, dessen einzige Aufgabe es ist, das zu summierende ''Value'' aus dem DataBasketEntry herauszuholen.<br />
<br />
In der ModelMap stehen nun neben der FlashMessage, wieviel noch gezahlt werden muss, ein boolscher Wert "pricePayedEnough", der ''true'' wird, wenn genug gezahl wurde, sowie das ATM des Geldkatalogs und das des bereits eingezahlten Geldes. Neu hierbei sind die ''ExtraColumn''s (EC), die als Alternative bzw. Erweiterung neben den bereits bekannten TEDs benutzt werden können, um eine bzw. mehrere Zusatzspalten hinzurendern. ECs werden in einer Liste verpackt direkt dem ViewTag übergeben. Die verwendete EC benutzt die ImageTag-Klasse, die sonst aus dem JSP-Context heraus als JSP-Tag benutzt wird, beispielhaft im JavaSourceCode, um zur jeweiligen Geldeinheit das entsprechende Bild zu rendern.<br />
<code java><br />
public class EuroCurrencyImageEC extends AbstractExtraColumn<CurrencyItemImpl> {<br />
<br />
private String height;<br />
private String width;<br />
<br />
public EuroCurrencyImageEC(String height, String width) {<br />
super("x", null);<br />
this.height = height;<br />
this.width = width;<br />
}<br />
<br />
@Override<br />
public String getCellContent(CurrencyItemImpl identifier) {<br />
ImageTag tag = new ImageTag();<br />
tag.setImage(identifier.getImage());<br />
tag.setAlt(identifier.getName());<br />
tag.setHeight(height);<br />
tag.setWidth(width);<br />
return tag.render().toString();<br />
}<br />
}<br />
</code><br />
Die ''WebContent/jsp/pay.jsp'' könnte so aussehen:<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title><spring:message code="videoautomat.pay" /></title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" /><br />
<sp:Messages messages="${spFlash}" /><br />
<br />
<sp:LoggedIn status="true"><br />
<br />
<sp:DoubleView showNumberField="true" ><br />
<sp:List abstractTableModel="${currency}" extraCols="${currencyExtraColumns}" /><br />
<sp:Table abstractTableModel="${moneyBag}" /><br />
</sp:DoubleView><br />
<br />
<form:form method="get" action="pay/success" id="success"><br />
<c:if test="${pricePayedEnough}"><br />
<input type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
<c:if test="${!pricePayedEnough}"><br />
<input disabled="disabled" type="submit" value="<spring:message code="videoautomat.pay" />" /><br />
</c:if><br />
</form:form><br />
<form:form method="get" action="pay/cancel" id="cancel"><br />
<input type="submit" value="<spring:message code="videoautomat.cancel" />" /><br />
</form:form><br />
<br />
</sp:LoggedIn><br />
<br />
</body><br />
</html><br />
</code><br />
Neu hierbei ist die Verwendung von Kontrollstrukturen aus der JSTL-TagLibrary (''c''-Namespace) mit dessen Hilfe hier der boolsche Wert, ob genug bezahlt worden ist, abgeprüft wird und entsprechend der ''pay''-Button de-/aktiviert wird.<br />
<br />
Der Pay- sowie Cancel-Button brauchen natürlich noch entsprechende Actions im PayController:<br />
<code java><br />
@RequestMapping("/success")<br />
public ModelAndView success(ModelAndView mav, HttpServletRequest request) {<br />
/* videoautomat.rent.successful = rent was process successful, payback is {0} EUR */<br />
MessagesUtil.addFlash(mav, messageSource.getMessage("videoautomat.rent.successful", <br />
new Object[]{calculatePricePaid().subtract(calculatePriceToPay()),<br />
EUROCurrencyImpl.ABBREVIATION},<br />
RequestContextUtils.getLocale(request)));<br />
/* add videocassettes to uservideostock */<br />
rentController.getDataBasket().commit();<br />
<br />
/* clean the moneybag by setting fresh instance */<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
<br />
AutomatUser currentUser = (AutomatUser) UserManager.getInstance().getCurrentUser(request.getSession());<br />
mav.addObject("currentUserVideoStockATM", new ATMBuilder<UserVideoStock>(currentUser.getVideoStock()).getATM());<br />
mav.setViewName("rentSuccessful");<br />
return mav;<br />
}<br />
<br />
@RequestMapping("/cancel")<br />
public ModelAndView cancel(ModelAndView mav, HttpServletRequest request) {<br />
setDestination(new MoneyBagImpl("moneyPayed", VideoShop.getCurrency(), true));<br />
return rentController.cancel(mav, request);<br />
}<br />
</code><br />
Die ''success''-Methode bestätigt den erfolgreichen Ausleihvorgang inkl. Rückgeldbetrag mit einer Erfolgsnachricht und ''committ''et den DataBasket vom RentController (verschiebt die VideoCassette-Instanzen persistent vom AutomatVideoStock zum UserVideoStock). '''Achtung:''' Außerdem setzt sie eine neue MoneyBag-Instanz - das ist wichtig, denn ein Controller mit Session-Scope bleibt erhalten bis die Sission endet(ServerEinstellung), d.h. beim eventl. 2.Ausleihvorgang während der gleichen Session wäre sonst der Bezahlbetrag vom 1.Ausleihvorgang noch enthalten!<br />
Abschließend soll in der JSP der UserVideoStock angezeigt werden, also holen wir uns den User der aktuellen Session und fügen ein mittels ATMBuilder erstelltes ATM von seinem UserVideoStock der ModelMap hinzug.<br />
<br />
''WebContent/jsp/rentSuccessful.jsp'':<br />
<code xml><br />
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><br />
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %><br />
<%@ taglib uri="http://salespointframework.org/web/taglib" prefix="sp" %><br />
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br />
<html xmlns="http://www.w3.org/1999/xhtml"><br />
<head><br />
<meta http-equiv="Content-Type" content="text/html; charset=uft-8" /><br />
<link rel="stylesheet" type="text/css" href="<c:url value="/static/resources/css/style.css" />" /><br />
<title>SalesPoint2010-BlankWebapplication</title><br />
</head><br />
<body><br />
<br />
<sp:Messages messages="${spErrors}" styleName="error"/><br />
<sp:Messages messages="${spFlash}" styleName="flash"/><br />
<br />
<sp:LoggedIn status="true"><br />
<sp:Table abstractTableModel="${currentUserVideoStockATM}" /><br />
</sp:LoggedIn><br />
<br />
<a href="<c:url value="/"/>">back</a><br />
<br />
</body><br />
</html><br />
</code><br />
=Abschlussbetrachtung=<br />
In diesem Tutorial wurden die wichtigsten Punkte anhand des Videoautomatenbeispiels angesprochen. Das runterladbare Webprojekt enthält ein paar weitere Funktionalitäten wie z.B. das Zurückgeben von Videos, eine Nutzerprofilseite sowie eine Adminseite. Ein Blick in diesen Quellcode sollte bis auf die zusätzliche Nutzung von CSS nichts neues offenbaren.</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-04-08T23:00:56Z<p>Lk: /* Framework-Versionen */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*[http://files.kraiz.de/sp/salespoint-2010-1.0.jar salespoint-2010-1.0.jar]<br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*[http://files.kraiz.de/sp/salespoint-2010-dependencies.jar salespoint-2010-dependencies.jar] <br />
<br />
Für eine Webapplikation steht ein vorkonfiguriertes Projekt zum Download, welches als Ausgangsbasis benutzt werden sollte:<br />
*[http://files.kraiz.de/sp/salespoint-2010-blankweb.war salespoint-2010-blankweb.war]<br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* gezippt für [http://files.kraiz.de/sp/salespoint-2010-1.0-javadoc.zip offline] Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]<br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop-src.zip salespoint-2010-videoautomat-desktop-src.zip] - Quellcode<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop.jar salespoint-2010-videoautomat-desktop.jar] - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-web.war salespoint-2010-videoautomat-web.war] - Webprojekt inklusive Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DownloadsDownloads2010-04-08T22:59:29Z<p>Lk: /* Framework-Versionen */</p>
<hr />
<div>==Framework-Versionen==<br />
Die aktuelle Version:<br />
*[http://files.kraiz.de/sp/salespoint-2010-1.0.jar salespoint-2010-1.0.jar]<br />
<br />
Salespoint 2010 benötigt diverse Datenbanktreiber, die alle in einer weiteren jar-Datei zusammengefasst wurden:<br />
*[http://files.kraiz.de/sp/salespoint-2010-dependencies.jar salespoint-2010-dependencies.jar] <br />
<br />
<br /><br /><br />
Es wird empfohlen ihre JVM mit folgenden parametern zu starten:<br />
<code bash><br />
-Xms64m -Xmx256m<br />
</code><br />
<code text><br />
Für die Nutzung wird mindestens das JRE/JDK 6 Update 10 benötigt.<br />
Download unter http://java.sun.com<br />
</code><br />
<br />
[[Ältere Framework-Versionen]]<br />
<br />
==Dokumentation zum Framework==<br />
<br />
Javadoc:<br />
<br />
* gezippt für [http://files.kraiz.de/sp/salespoint-2010-1.0-javadoc.zip offline] Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]<br />
<br />
==Videoautomat==<br />
Das Implementationsbeispiel eines Videoautomatens soll eine Einführung in das Framework geben. Hier werden erste Schritte im Umgang mit dem SalesPoint-Framework detailiert erklärt. <br />
<br />
* Desktopvariante<br />
** [[Videoautomat Desktop]] - Desktopvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop-src.zip salespoint-2010-videoautomat-desktop-src.zip] - Quellcode<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-desktop.jar salespoint-2010-videoautomat-desktop.jar] - Auführbare JAR-Datei<br />
*** Starten mit:<code bash>java -Xms64m -Xmx256m -jar filename</code><br />
* Webvariante<br />
** [[Videoautomat Web]] - Webvariante<br />
** [http://files.kraiz.de/sp/salespoint-2010-videoautomat-web.war salespoint-2010-videoautomat-web.war] - Webprojekt inklusive Quellcode</div>Lkhttp://www.st.inf.tu-dresden.de/SalesPoint/v4.0/index.php/DokumentationDokumentation2010-04-08T22:57:34Z<p>Lk: /* API Spezifikation */</p>
<hr />
<div>==Technischer Überblick==<br />
<br />
Der technische Überblick soll einen Blick hinter die Kulissen ermöglichen, um ein besseres Verständnis für das Framework zu entwickeln. Die Technische Referenz bietet darüber hinaus einen detaillierten Einblick in das Innenleben von SalesPoint.<br />
<br />
* [[Technischer Überblick]] eine kurze Einführung in das Framework <br />
* [http://st.inf.tu-dresden.de/SalesPoint/v3.3/download/beleg_de.pdf Großer Beleg] die Grundlage für den technischen Überblick (pdf-Datei)<br />
* [[Technische Referenz]] eine rudimentäre Beschreibung des internen Aufbaus von SalesPoint<br />
* [[Persistence Layer]] eine Beschreibung der Peristenzschicht des Frameworks<br />
* [[Web Erweiterung]] eine Beschreibung der Salespointeigenen Hilfmittel für den Betrieb als Webapplikation<br />
<br />
==API Spezifikation==<br />
Unterstützung bei der Arbeit mit dem Framework soll die API-Spezifikation (Application-programming Interface, Javadoc) bieten. Hier sind alle Klassen, deren Attribute, die enthaltenen Methoden, sowie deren Dokumentation zu finden.<br />
<br />
* gezippt für [http://files.kraiz.de/sp/salespoint-2010-1.0-javadoc.zip offline] Benutzung<br />
* [http://files.kraiz.de/sp/javadoc/ Online Version]<br />
<br />
==HowTos==<br />
Hierunter verbergen sich stichwortartige Hilfen mit Codebeispielen welche Anpassungsmöglichkeiten des Frameworks beschreiben. <br />
* [[HowTos]]</div>Lk