Jetspeed-Praixprojekt: Auctioner = eBay MyNetscape

Zum ersten, zum Zweiten, …

Stefan Kuhn und Fabian Theis

Für Open Source-basierte Portal-Projekte bietet sich das Framework Jetspeed von Apache als leistungsfähige Umgebung an. Wer schon einmal im Web danach gesucht hat, weiß, das anspruchsvollere Jetspeed-Beispiele mit Sourcecode im Web rar sind. Deshalb wollen wir, nachdem wir in früheren Ausgaben des Java Magazins das Framework ausführlich vorgestellt haben, uns hier in die Praxis stürzen und Ihnen ein komplexeres Beispiel einer Portlet-basierten Auktions-Site à la eBay demonstrieren.

Dazu wird ein Online-Auktionsportal namens Auctioner vergleichbar Ebay erstellt, das Funktionen für Produktübersicht, Produkteintragung und Bieten umfasst. Diese Features sollen allesamt in Portlets verpackt werden, so dass sich Benutzer ganz nach Belieben ihre Weboberfläche innerhalb unseres Auctioners zusammenstellen können. Abschließend werden wir neben der reinen HTML-Ausgabe auch ein WML-Portlet erstellen, mit dem man vom Handy aus Produkte ersteigern kann.

Dieser Artikel basiert auf einem komplexeren Jetspeed- und Turbine-Beispiel, das im Rahmen des Buches Web-Applikationen und Portale mit Apache-Frameworks [Portale] erstellt wurde; dort werden auch die Grundlagen zu Velocity, Turbine, Torque und Jetspeed ausführlich dargestellt.

Die Grundidee des Auctioner ist, dass die Auktionseigenschaften im Rahmen eines Portals als Webapplikation angeboten werden sollen. Die Funktionalität des Auctioners soll folgende Punkte umfassen:

  • Anlegen von neuen Auktionen
  • Betrachten verschiedener Auktionen, nach Kategorien sortiert
  • Bieten bei einer ausgewählten Auktion
  • Regelmäßige Überprüfung auf abgelaufene Auktionen; nach Ablauf eMail an Bieter und Verkäufer

Diese Features sollen allesamt in Module verpackt werden. Dazu wird ein Portlet zum Erstellen neuer Auktionen und ein weiteres, das für die Produktansicht sowie das Ersteigern benutzt werden kann. In den Abpictureungen 3, 4 und 5 kann man sehen, wie die Applikation später aussehen soll.

Die Realisierung der Portaleigenschaften geschieht mit dem Jetspeed-Framework, welches bereits in früheren Ausgaben des Java Magazins vorgestellt wurde [Jetspeed JM01] [Jetspeed JM02]. Über Jetspeed wird intern damit auch Turbine und Torque verwendet [Turbine JM]. Für detaillierte Erklärungen dieser einzelnen Frameworks und Tools sei an deren Dokumentation, obige Java Magazin Artikel und natürlich das Buch verwiesen.

Um das Beispiel nachvollziehen zu können, benötigen Sie folgendes:

  • Den Jetspeed-Quellcode (jetspeed-1.4b1-release-src.zip) [Jetspeed]
  • Das Apache Build-Tool Ant [Ant]
  • Einen J2EE-kompatiblen Servlet Container, z. B. Tomcat [Tomcat]
  • Sofern der verwendete Servlet Container diese noch nicht enthält (beispielsweise Tomcat 3.x), die Java-Bibliothek activation.jar aus dem Javabeans Activation Framework (JAF) von Sun [JAF]. Dieses muss im Klassenpfad für die Webanwendung bereitliegen.

Dummerweise haben die Jetspeed-Entwickler in der 1.4b1-Version eine nicht-öffentliche Developer-Version von Torque verwendet, die mit keiner der zugänglichen Torque Beta Versionen kompatibel ist. Daher bleibt uns nichts anderes übrig, als direkt die Source-Distribution von Jetspeed zu benutzen, und darin unser Projekt zu realisieren; diese Distribution enthält eine funktionierende Torque-Version. In späteren Versionen soll das Projektmanagement mit dem Tool Maven [Maven], ebenfalls Subprojekt des Turbine-Projektes, deutlich vereinfacht werden.

Auf Wunsch können Sie auch das erweiterte Beispiel (inklusive Produkt- und Auktionsansicht und Benutzerbewertungen) mitsamt Quellcode von der Buchhomepage [Webapps] als Source-Distribution oder als war-Archiv herunterladen, das dann nur noch in das Tomcat-Webapps-Verzeichnis hineinkopiert werden muss.

ant deploy

die Anwendung erst kompiliert und in ein in build.properties angegebenes Webapps-Verzeichnis des Servlet Containers kopiert werden.

Das Kompilieren und Deployen kann nun kurz getestet werden; anschließend sollten ein paar Designänderungen wie Einbinden eines eigenen Logos und Änderung von Kopf- und Fußzeile vorgenommen werden, um sich noch einmal kurz den Jetspeed-Aufbau vor Augen zu führen. Zusätzlich haben wir für die Beispielanwendung noch zwei HTML-Portlets mit Begrüßungstexten und ein RSS Portlet für einen dynamischen Newsfeed eingebunden, die das Portal später lebendiger aussehen lassen. Wir verzichten an dieser Stelle auf Beschreibung und Vorgehensweise zur Erstellung solcher Portlets und verweisen auf Dokumentation und ältere Jetspeed-Artikel im Java Magazin.

Abb. 1: Datenbankschema des Auctioners

Zur Modellierung und für den Datenbankzugriff benutzen wir das in Turbine und damit in Jetspeed enthaltene Tool Torque. Torque liest alle Datenbankinformationen aus einem sogenannten XML-Datenbankschema aus, erstellt zugehörige Objektmodell (OM)- und Peer-Klassen, die Zeileneinträge beziehungsweise Tabellen der Datenbank repräsentieren.

Nachdem Torque für uns ein Grundgerüst an Objektmodell erstellt hat, ist es an uns, dieses mit Leben zu füllen entsprechend den obigen Anforderungen an ein Auktionshaus. Dazu werden Methoden zu den leeren Entitätsklassen AuctionerUser, Auktion und Kategorie hinzugefügt; hierbei ist zu beachten, dass bei erneutem Torqueaufruf diese nicht mehr erzeugt werden, es werden ausschließlich die Base*-Klassen überschrieben. Auch werden wir einige Funktionen zu den entsprechenden Peer-Klassen hinzufügen, in denen statische Methoden zum Auffinden, Persistieren und Löschen der Objekte aus der Datenbank enthalten sind.

Die neu hinzuzufügenden Methoden sind in Abpictureung 2 im Objektmodell gezeigt. Da in diesem Artikel der Hauptaugenmerk der Portleterstellung selbst liegt, werden wir die einzelnen Methoden hier nicht weiter erläutern – hierfür sei an deren Javadoc-Dokumentation verwiesen. Alternativ hätten wir übrigens die gesamte Funktionalität natürlich auch mit EJBs modellieren können.

Abb. 2: Ergänzung des Objektmodells vom Auctioner

Nachdem das Geschäftsmodell steht, können wir passende Views, in unserem Fall Portlets, erstellen. Für die Programmierung des Auctioners haben wir uns für die Verwendung von Velocity-Portlets mit Action-Klassen entschieden. Velocity ist eine scriptbasierte Template Engine aus dem Jakarta Projekt [Velocity JM]. VelocityPortlets unterstützen den Programmierer bei der Anwendung des Model-View-Controlling Musters; sie benutzen Velocity (und intern Turbine) zur View-Erstellung. Die Portlets des Projekts bestehen also jeweils aus einem oder mehreren Velocity-Templates und einer Java-Action-Klasse, die die Controller-Funktionalität übernimmt. Das Modell wurde oben bereits erstellt.

Unser erstes Portlet soll dem Anlegen neuer Auktionen durch die Benutzer dienen. Dazu registrieren wir das Portlet in einer neu anzulegenden Datei local-portlets.xreg unter webapp/WEB-INF/conf (Listing 1).

module.packages=de.instantsolutions.auctioner.modules,
org.apache.jetspeed.modules

Wie in Turbine üblich, wird durch diese Erweiterung des Modulpfades erreicht, dass Action-Klassen in beiden Klassenpfaden gefunden werden. Natürlich liegen Actions dann in dem Unterpackage actions, und sinnigerweise legen wir Portlet-Actions wiederum unter actions.portlets ab.

Das Haupt-Velocity-Template zum Auktionen-Anlegen, auctionnew-main.vm, das im von Jetspeed eingestellten Portlet-Template-Pfad webapp/WEB-INF/templates/vm/portlets/html liegen muss, ist in Listing 2 gezeigt. Zum größeren Teil handelt es sich dabei um einfachen HTML-Code. Interessant sind folgende Punkte: Die Liste der Auktionen wird über eine Schleife dynamisch gebaut. Die Herkunft dieser Werte folgt weiter unten. Datei wird die Kategorie ausgewählt, die der Benutzer als seine Lieblingskategorie gekennzeichnet hat. Gespeichert ist sie als Paramter in der Portlet-Registry:

package
de.instantsolutions.auctioner.modules.actions.portlets;

import org.apache.jetspeed.modules.
actions.portlets.VelocityPortletAction;
import org.apache.jetspeed.portal.portlets.VelocityPortlet;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;

import de.instantsolutions.auctioner.om.Auktion;

public class NewAuctionPortletAction
extends VelocityPortletAction {
private static final String LIEBLINGSKATEGORIE_KEY =
"lieblingskategorie";

protected void buildNormalContext(VelocityPortlet portlet,
Context context, RunData data) {
context.put(
LIEBLINGSKATEGORIE_KEY,
portlet.getPortletConfig().
getInitParameter(LIEBLINGSKATEGORIE_KEY));
}

public void doMain(RunData data, Context context) {
setTemplate(data, "auctionnew-main.vm");
}

public void doAddnew(RunData data, Context context)
throws Exception {
Auktion auktion = new Auktion();

//    klappt wegen alter Turbine2.2 beta noch nicht :(
//    data.getParameters().setProperties(auktion);
auktion.setProduktName(data.getParameters().
getString("produktName"));
auktion.setBeschreibung(data.getParameters().
getString("beschreibung"));
auktion.setGebot(data.getParameters().getInt("gebot"));
auktion.setInkrement(data.getParameters().
getInt("inkrement"));
auktion.setKategorieId(data.getParameters().
getString("kategorieId"));

auktion.setAblaufdatumstring(
data.getParameters().getString("ablaufday"),
data.getParameters().getString("ablauftime"));
auktion.setVerkaeuferLogin(data.getUser().getUserName());
auktion.save();

setTemplate(data, "auctionnew-done.vm");
}
}

Zuerst beschäftigen wir uns mit der doAddnew-Methode. Diese entspricht dem Buttonnamen des Formulars, nach dem Abschicken wird also diese Methode durchgeführt [Turbine JM]. Der Code der Methode ist recht einfach: Es wird ein neues Auktion-Object angelegt und ihm die Werte zugewiesen, die aus dem Formular gelesen werden – dies könnte man ganz einfach durch data.getParameters().getString() erledigen, aber der Turbine-Torque-Versionskonflikt in Jetspeed 1.4b1 wirft beim Setzen der Kategorie einen Fehler, der sich nur so vermeiden lässt – , dann als Verkäufer der eingeloggte Benutzer genommen und schließlich das Objekt abgespeichert.

Am Ende der doAddnew-Methode wird ein neues Template namens auctionnew-done.vm gesetzt, das sehr einfach gehalten ist:


Neue Auktion angelegt!

Es gibt eine Erfolgsmeldung aus und stellt einen Link zur Verfügung. Durch diesen wird die Methode doMain der NewAuctionPortletAction aufgerufen; das einzige, was diese Methode macht, ist wieder das ursprüngliche Template auszuwählen, sodass die nächste Auktion angelegt werden kann.

Durch diese einfach gehaltene Template-Action-Interaktion wird ohne großen Aufwand die Anzeige von HTML und die Bearbeitung von Anfragen realisiert, was dem Pull-Modell aus Turbine entsprecht, nur ohne (vom Programmierer erstellbare) Screen-Klassen. Man kann in diesem vorhandenen Gerüst unmittelbar den eigentlichen Code schreiben. Dies wird durch Turbine noch zusätzlich erleichtert, da Torque einen einfachen Datenbankzugriff erlaubt – zum Speichern eines Objektes in der Datenbank genügt es, dessen Attribute zu setzen und die save()-Methode aufzurufen. Außerdem hat Turbine die gesamte Benutzerverwaltung durchgeführt – wir haben keinen Code für das Login schreiben müssen und können einfach den eingeloggten Benutzer abfragen.

Bevor wir dieses Portlet erfolgreich testen können, muss noch erklärt werden, woher die Variable $auctioner im Template aus Listing 1 kommt.

Das auctioner-Tool

Im obigen Velocity-Template wurde aus allen Kategorien eine Dropdownliste erstellt. Hierzu benötigt man eine java.util.List, die die Kategorien enthält und über die mit dem Velocity-Kommando #foreach (… in …) iteriert werden kann. Natürlich könnte man in der Action-Klasse dieses Objekt anlegen und in den Context schreiben. Da wir ähnliche Funktionen jedoch noch in anderen Portlets benötigen, wollen wir sie an einer zentralen Stelle anlegen. Wir benutzen hierzu das Pull-Tool-Konzept von Turbine, das es ermöglicht, Klassen in der TurbineResources.properties zu registrieren, die dann in allen Contexts zu Verfügung stehen.

Wir wollen ein Request-Tool schreiben, daher muss das Interface ApplicationTool implementiert werden; wir erinnern uns, dass Request-Tools bei Init dann das jeweilige RunData-Objekt des Requests von Turbine gesetzt bekommen, sodass auch auf User- und Formulardaten zugegriffen werden kann. Die Klasse de.instantsolutions.auctioner.tools.AuctionerTool muss dafür das Interface ApplicationTool implementieren; die Methode

public List getKategorien() throws TorqueException {
return KategoriePeer.getKategorien();
}

liefert dann eine Liste aller Kategorien zurück, indem sie auf die entsprechende OM-Methode zurückgreift. Weitere Methoden liefern den aktuellen AuctionerUser zurück – $data.user gibt ja den JetspeedUser zurück. Außerdem enthält das Tool zwei Methoden, die den heutigen und den morgigen Tag entsprechend formatiert zurückgeben.

In den TurbineResources.properties registrieren wir diese Klasse als Pull-Tool mit Namen auctioner durch

Unter der Bezeichnung $auctioner steht sie dann in jedem Velocity-Template zur Verfügung. Der Methodenaufruf im NewAuction-Portlet kann mit $auctioner.kategorien erfolgen, da in Velocity die Vorsilbe get zusammen mit den nachfolgenden Klammern () weggelassen werden kann – Attributzugriff anstelle eines JavaBean-Get-Zugriffes.

Damit ist das erste Portlet funktionsfähig. Nach dem Erstellen des .war-Files und dem Deployment können sie sich als Benutzer einloggen und das Portlet hinzufügen (bei den Standardbenutzern ist ja das Portlet noch nicht auf der Seite enthalten). Das Anlegen von Auktionen ist damit jetzt möglich.

Hier werden zwei Fälle unterschieden: entweder wurde noch keine Kategorie ausgewählt oder eben doch. Im ersten Fall werden die verfügbaren Kategorien angezeigt. Hierfür dient wieder das auctioner-Tool, das ja auch hier im Context zu finden ist. Dessen getKategorien()-Methode selektiert per Torque alle verfügbaren Kategorien. Jede Kategorie wird als Link angezeigt, welcher mit dem Jetspeed-werkzeug JetspeedLink erstellt wird. Dieser Link enthält zwei Parameter, die Id der Kategorie und ein Methode aus der Action-Klasse, die wir weiter unten betrachten.

Falls bereits eine Kategorie ausgewählt wurde, wird entweder angezeigt, dass die Kategorie keine Auktionen enthält oder aber eine Liste der Auktionen der Kategorie. Bei dieser Liste werden die Auktionen wieder per JetspeedLink als Links gestaltet. Hier wird die AuktionId und eine Methode der Action-Klasse angegeben. Diese Methode sieht so aus:

Hier wird zuerst die gewählte Auktion in den Context gesetzt und dann ein neues Template gewählt. Dieses Template – auctionall-detail.vm – ist in Listing 5 gezeigt.

Wie wird nun die temporäre Variable kategorie des Benutzers gesetzt? Hierfür dient die Methode doSetkategorie() der Action-Klasse, die aufgerufen wird, wenn der Benutzer eine Kategorie ausgewählt hat. Diese setzt durch

Wenn eine Auktion ihr Enddatum überschritten hat, muss diese als abgeschlossen gespeichert werden. Da der Ablauf einer Auktion nicht durch eine Benutzeraktion bewirkt wird, sondern durch Zeitablauf, muss ein Timer benutzt werden. Turbine stellt hierfür den sogenannten Scheduler zur Verfügung, den wir verwenden wollen.

Nun zur Erstellung des eigentlichen Jobs, den wir AblaufCheckJob nennen wollen. Da er ein Assembler ist, muss er im Modulpfad unter scheduledjobs liegen. Diese Klasse erweitert ScheduledJob; ähnlich wie in einer Thread-Klasse findet sich der eigentliche Code in der run()-Methode:

Dieser ist hier denkbar einfach, abgesehen von einigen Logging-Aktivitäten wird nur AuktionPeer.checkForAbgelaufen() aufgerufen, welche die eigentliche Arbeit macht, sprich eventuell abgelaufene Auktionen als abgelaufen markiert und die entsprechenden Bieter und Verkäufer kontaktiert.

Als Scheduler Service in Turbine benutzen wir den nicht-persisten TurbineNonPersistentSchedulerService. Dazu ersetzen wir SchedulerService in TurbineResources.properties durch

Abb. 4: Auctioner-Anwendung nach Userlogin

hinzu. Damit wird im Customizer dieses Portlet auch bei der WML-Anpassung angeboten. Die PortletAction, also der Controller in unserem MVC-Modell, muss nicht verändert werden – wir fügen einfach eine weitere View in Form von WML-Ausgabe hinzu. Dazu erstellen wir unter webapp/WEB-INF/templates/vm/portlets/wml zwei Dateien auctionall-main.vm und auctionall-detail.vm, deren logischer Inhalt dem der jeweiligen gleichnamigen HTML-Templates entspricht – es wird nur HTML-Syntax durch WML-Syntax [WML] ersetzt.

Die neu erstellten WML-Portlets lassen sich nun nach Einloggen (in der HTML-Maske) durch Customize-WML hinzufügen; allerdings sind die ursprünglichen WML-PSML-Dateien in der derzeitigen Jetspeedversion fehlerhaft, es muss der FlowPortletController in

type="ref" parent="Velocity"

ersetzt werden.

In Abpictureung 5 sind die fertigen WML-Seiten in einem WAP-Browser, hier dem WAP-Emulator des Nokia Mobile Internet Toolkit [Nokia] gezeigt. Zuerst muss man sich als Benutzer einloggen, dann erhält man eine Übersicht über alle Portlets. Wählt man das Auktionen-Portlet aus, so bekommt man die übliche Kategorienübersicht. Anschließend kann man in der Detailansicht sogar bei Auktionen über das Handy mitbieten.

Abb. 5: Auktionen betrachten und bieten mit einem WAP-Browser

Wir haben ein Auktionsportal erstellt, mit dem man Auktionen anlegen und bei Auktionen mit bieten kann. Nach der Modellierung der eigentlichen Geschäftslogik mit Torque wurden sukzessive die einzelnen Portlets, basierend auf dem VelocityPortlet, erstellt, zunächst mit einer HTML-View. Unter anderem haben wir dafür die Turbine Services Scheduler zur Abfrage nach abgelaufenen Auktionen und Pull-Tool zur problemlosen Bereitstellung von Daten im Velocity-Context benutzt. Schließlich haben wir angedeutet, dass als zweite View für ein Portlet eine WML-Darstellung erzeugt, so dass man auch per Handy bei Auktionen mitbieten kann.

Viele weitere Funktionen ließen sich nun einbauen. In [Webapps] haben wir zusätzliche Funktionalitäten zum Bewerten von Benutzern nach erfolgreichen Auktionen hinzugefügt. Für solche Erweiterungen sowie detailliertere Erklärungen zu Jetspeed und Turbine sei an das Buch verwiesen, das demnächst im Software & Support Verlag erscheint [Portale]. Dennoch haben wir in dieser einfach gehaltenen Anwendung gesehen, wie Turbine den Programmierer bei der Aufteilung und Entwicklung von Modell- und Präsentationsschicht unterstützt, und Jetspeed dann das ganze in einen Portalrahmen verschnürt.

Übrigens finden Sie einige der Java Magazin-Artikel, auf die in diesem Beitrag verwiesen wird, unter www.javamagazin.de.

Geschrieben von
Stefan Kuhn und Fabian Theis
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.