6
52 www.JAXenter.de javamagazin 5 | 2010 as könnte als Vorlage besser geeignet sein als Twitter [1]? Einerseits dürfte es kaum je- manden in unserer Branche geben, der diese Art des Zwitscherns nicht kennt. Andererseits steht Twitter trotz über- schaubarem Funktionsumfang gera- dezu idealtypisch für heutige moderne Webapplikationen. Wir werden mit Chatter – unserem Nachbau – die wich- tigsten Features auf vereinfachte Weise umsetzen: Registrieren und Anmelden, anderen Benutzern folgen und natürlich das Nachrichtenverschicken und -an- zeigen unter Verwendung der Web-2.0- Features Ajax und Comet. Doch bevor wir uns die Hände schmutzig machen, ein kurzer Überblick über Liſt. Von den Ursprüngen bis heute Im Jahr 2006 startete David Pollak [2], inspiriert von vielfältigen Erfahrungen mit Programmiersprachen und Frame- works, die Arbeit an Liſt, ursprünglich noch unter dem Namen Scala with Sails. Sein Ziel war und ist es noch heute, ein einfach zu benutzendes, sicheres und höchst produktives Webframework zu schaffen. Dazu bedient sich Liſt der bes- ten Konzepte anderer Frameworks und ergänzt sie um eigene innovative Ideen. Beispiele für dieses Rosinenpicken sind an Wicket [3] angelehnte Templates, CRUD-Unterstützung wie bei Rails [4] oder Djangos [5] „More than just CRUD“ bei der Benutzerverwaltung. Wer sich mit Scala beschäftigt, kommt an Lift nicht vorbei: Wie kaum ein anderes Produkt hat dieses innovative Webframework dazu beigetragen, das Scala-Ökosystem aufzubauen. In diesem Artikel zeigen wir anhand eines konkreten Fallbeispiels die Gründe für diesen Erfolg. von Heiko Seeberger und Johannes Hohenbichler Twitter nachgebaut mit Lift Lift-Webframework Web

Java Magazin - Lift

Embed Size (px)

Citation preview

Page 1: Java Magazin - Lift

52 www.JAXenter.dejavamagazin 5|2010

as könnte als Vorlage besser geeignet sein als Twitter [1]? Einerseits dürfte es kaum je-

manden in unserer Branche geben, der diese Art des Zwitscherns nicht kennt. Andererseits steht Twitter trotz über-schaubarem Funktionsumfang gera-dezu idealtypisch für heutige moderne Webapplikationen. Wir werden mit Chatter – unserem Nachbau – die wich-tigsten Features auf vereinfachte Weise umsetzen: Registrieren und Anmelden,

anderen Benutzern folgen und natürlich das Nachrichtenverschicken und -an-zeigen unter Verwendung der Web-2.0-Features Ajax und Comet. Doch bevor wir uns die Hände schmutzig machen, ein kurzer Überblick über Lift.

Von den Ursprüngen bis heute

Im Jahr 2006 startete David Pollak [2], inspiriert von vielfältigen Erfahrungen mit Programmiersprachen und Frame-works, die Arbeit an Lift, ursprünglich

noch unter dem Namen Scala with Sails. Sein Ziel war und ist es noch heute, ein einfach zu benutzendes, sicheres und höchst produktives Webframework zu schaffen. Dazu bedient sich Lift der bes-ten Konzepte anderer Frameworks und ergänzt sie um eigene innovative Ideen. Beispiele für dieses Rosinenpicken sind an Wicket [3] angelehnte Templates, CRUD-Unterstützung wie bei Rails [4] oder Djangos [5] „More than just CRUD“ bei der Benutzerverwaltung.

Wer sich mit Scala beschäftigt, kommt an Lift nicht vorbei: Wie kaum ein anderes Produkt hat dieses innovative Webframework dazu beigetragen, das Scala-Ökosystem aufzubauen. In diesem Artikel zeigen wir anhand eines konkreten Fallbeispiels die Gründe für diesen Erfolg.

von Heiko Seeberger und Johannes Hohenbichler

Twitter nachgebaut mit Lift

Lift-WebframeworkWeb

Page 2: Java Magazin - Lift

Web

www.JAXenter.de 53

Lift-Webframework

javamagazin 5|2010

Heute sehen wir eine große und sehr le-bendige Lift-Community mit mehr als zehn aktiven Committern und etliche produktive Anwendungen auf Basis von Lift, z. B. Foursquare.com [6] oder ESME [7]. Derzeit befindet sich bereits die Ver-sion 2.0 in der Entwicklung, die vermut-lich auf Scala 2.8 aufsetzen wird.

Ein modularer Baukasten

Lift bringt als „Full Stack Framework“ alles mit, um eine komplette Webappli-kation zu entwickeln. Dazu gehören typischerweise Templates, Menüs und Zugriffskontrolle, Persistenz, CRUD-Support und eine komplette Benutzer-verwaltung. Aber auch ausgefallenere Features wie PayPal-Anbindung, Open-ID- oder OAuth-Schnittstellen sowie Support für JSON-APIs werden ange-boten. Doch es gibt keinen Grund, vor dieser Fülle an Features zu erschrecken. Lift ist modular aufgebaut und wir kön-nen wählen, was wir verwenden möch-ten und was nicht. So können wir z. B. die mit lift-mapper mitgelieferte Benutzer-verwaltung verwenden oder auch eine eigene bzw. vorhandene.

Ein funktionales Framework

Wozu eigentlich ein weiteres Webframe-work? Löst Lift unsere täglichen Aufga-ben anders und vor allem auch besser als andere? Dank Scala: Ja. Eine kürzlich durchgeführte Umfrage auf JAXenter [8] hat ergeben, dass für die Leser Scala die JVM-Sprache mit dem größten Po-tenzial ist. Und dieses Potenzial hebt Lift, indem es insbesondere die funktiona-len Features, Traits sowie das mächtige Typsystem von Scala nutzt. Ein gutes Beispiel hierfür: Wenn ein Formular-feld übertragen oder ein Button geklickt wird, dann soll etwas passieren, und das geben wir in Lift einfach in Form von Funktionen an. Dies entspricht viel bes-ser unserem intuitiven Verständnis, als z. B. die Kapselung in Form- und Con-troller-Klassen, wie wir sie bei typischen OO-Frameworks finden.

Chatter in Action

Bevor wir loslegen, sollten wir uns eine Vorstellung davon machen, was wir im Folgenden Schritt für Schritt entwi-ckeln. Das geht am besten, indem wir

unter weiglewilczek.com/labs/chatter einen Blick auf die laufende Anwendung in Endausbaustufe werfen. Einfach mit E-Mail-Adresse registrieren und dann loslegen. Zum Testen am besten mit zwei unterschiedlichen Browsern, um die Ak-tualisierung der Nachrichten via Comet sehen zu können.

Den kompletten Sourcecode gibt es unter der Eclipse Public License bei github.com/weiglewilczek/chatter zum Browsen oder Herunterladen. Die ein-zelnen Schritte, in denen wir Chatter entwickeln werden, sind mit Tags (z. B. 0.1-template) versehen, sodass Sie die Entwicklung im Sourcecode nachvoll-ziehen können. Selbstverständlich sind Tickets für Fehler oder Erweiterungs-wünsche sehr gern gesehen.

Das Projekt aufsetzen

Lift nutzt selbst Maven [9] und bietet uns mit verschiedenen Maven Arti-facts eine sehr angenehme Möglichkeit, unser Projekt aufzusetzen. Selbstver-ständlich können wir Lift-Projekte auch in der IDE unserer Wahl oder mit Ant entwickeln, aber für Chatter bleiben wir Maven treu, weil das einiges verein-facht, insbesondere das Management der Abhängigkeiten. Wir verwenden Lift 2.0-SNAPSHOT , d. h. den aktuel-len Entwicklungsstand für die Version 2.0 sowie Maven 2.2.1. Mit dem Arti-fact lift-archetype-basic erhalten wir ein einfaches Projekttemplate. Da wir hier grundlegende CSS- und Scala-Kennt-nisse voraussetzen, reichern wir unser Projekt ohne weitere Erläuterungen um ein Stylesheet sowie Hilfsklassen für die Locale-abhängige Datumsforma-tierung an. Nach ein wenig Tuning von POM und Projektstruktur erhalten wir unser Projekttemplate.

Um Chatter lokal laufen zu lassen, genügt es, mvn jetty:run im Projektver-zeichnis abzusetzen. Dann können wir unter http://localhost:8080 auf die Start-seite zugreifen (Abb. 1).

Eine Anmerkung: Selbstverständ-lich unterstützt Lift internationalisierte Anwendungen, aber um die Dinge noch einfacher zu halten, blenden wir das hier aus und halten uns konsequent an Eng-lisch.

Lift konfigurieren

Natürl ich müssen wir unserer Webapplikation beibringen, dass sie „geliftet“ ist, und das tun wir über ei-nen Servlet-Filter in web.xml. Scala ist so flexibel und ausdrucksstark, dass Lift selbst auf Konfigurationsdateien in XML verzichten kann. Stattdessen wird Lift in der Scala-Klasse Boot kon-figuriert, die per Default im Verzeichnis liftweb.bootstrap erwartet wird, bei uns aber via Servlet-Filter-Parameter nach com.weiglewilczek.chatter verschoben ist. Neben einigen nützlichen Vorgaben für Formatierung und Internationa-lisierung enthält Boot eine besonders wichtige Zeile: LiftRules addToPackages getClass.getPackage. Hiermit bestim-men wir, dass Lift im Package com. weiglewilczek.chatter nach Sub-Pa-ckages für Snippets und Comet-Klassen – dazu später mehr – suchen soll.

Templates für das UI

Wie erstellen wir eigentlich Webseiten mit Lift? Am einfachsten mit Templates: Das sind XHTML-Seiten, die keiner-lei Logik enthalten. Doch wie kommen dann die dynamischen Elemente auf die Seite? Das funktioniert ähnlich wie bei Wicket, nur dass wir bei Lift nicht über IDs, sondern über spezielle Elemente mit eigenen Namespaces gehen, die so-zusagen als Platzhalter für den dynami-schen Inhalt stehen.

Listing 1 : index.xhtml

<lift:surround with="default" at="content">

<div>Welcome to Chatter</div>

</lift:surround>

Abb. 1: Initia-le Startseite

Page 3: Java Magazin - Lift

Web

54 www.JAXenter.de

Lift-Webframework

javamagazin 5|2010

Besonders praktisch: Templates kön-nen verschachtelt werden. Im Verzeich-nis templates-hidden liegt unser „Wur-zeltemplate“ default.xhtml. Es enthält das HTML-Skelett für all unsere Seiten. Über ein spezielles Element aus dem lift Namespace definieren wir einen Anker-punkt, um den eigentlichen Inhalt ein-zubinden: <lift:bind name="content"/>.

Doch was wird dort eingebunden? Das ist der Inhalt aus dem Template, das wir aufrufen, also z. B. von der Seite in-dex.xhtml. Diese ist, wie in Listing 1 dar-gestellt, nur ein Fragment einer XHTML -Seite. Mittels lift:surround geben wir das Template und den Ankerpunkt an, sodass die Willkommensnachricht in das Default-Template eingebettet wird.

Interaktionen mit Snippets

Nun wollen wir als Erstes eine Eingabe-möglichkeit für Nachrichten schaffen, d. h. wir benötigen ein Formular. Wie bereits erwähnt, setzt Lift zur Bearbei-tung von Formularen auf Funktionen. In unserem Fall wollen wir erst einmal nur die Eingabe loggen. Dazu nehmen wir ein Formular in unser Template für die Startseite auf (Listing 2). Wir umgeben den Inhalt mit dem speziellen Element

lift:ChatterInput. Damit referenzieren wir das Snippet ChatterInput, eine Sca-la-Klasse, die für das Dynamisieren des Inhalts zuständig ist. Im Inhalt selbst fin-den wir die statischen HTML-Elemente und zwei spezielle Platzhalterelemente im chatter Namespace. Wir haben die freie Wahl bei der Benennung des Name-spaces und müssen nur darauf achten, dass das betroffene Snippet ihn auflöst.Genau das tut ChatterInput in der ren-der-Methode (Listing 3). Durch die bind-Methode werden die Elemente chatter:text und chatter:submit durch die HTML-Elemente für eine Textarea und einen Submit-Button ersetzt. Der Methode textarea übergeben wir einen leeren String als Default-Wert und eine Funktion, die ausgeführt wird, sobald das Formular zum Server geschickt wird. In unserem Fall loggen wir einfach die Eingabedaten. Die Methode submit erwartet eine Bezeichnung für den Sub-mit-Button sowie ebenfalls eine Funkti-on. Hier tun wir allerdings nichts.

Ready-to-use-Benutzerverwaltung

Als Nächstes wollen wir uns darum küm-mern, uns als Benutzer zu registrieren

bzw. anzumelden. Lift bringt mit dem Persistenzmodul lift-mapper für relati-onale Datenbanken eine komplette Be-nutzerverwaltung mit anpassbarem UI und Persistenz mit. Wir verwenden den Trait MegaProtoUser (zugegeben, das ist ein komischer Name), der Attribute wie E-Mail, Passwort, Vor- und Nachname etc. besitzt und missbrauchen den Vor-namen als einzigen Namen, indem wir den Anzeigenamen ändern (Listing 4).

Im dazugehörigen Companion Object, zu dem wir den Trait MetaMe-gaProtoUser mixen, konfigurieren wir unter anderem, welche Attribute wir verwenden, dass wir auf E-Mail-Validie-rung verzichten und dass die Seiten für Registrierung, Log-in etc. von unserem Default-Template umgeben sein sollen (Listing 5).

Wie aktivieren wir nun unsere Be-nutzerverwaltung? Indem wir in Boot zum einen eine Datenbankverbindung konfigurieren und zum anderen unse-re User-Klasse mittels Schemifier.sche-mify in das Datenbankschema bringen (Listing 6). Das funktioniert so ähnlich wie in Hibernate die Einstellung ddl.auto. Wir verwenden hier H2 [10] im In-Process-Modus als Datenbank, aber lift-

Listing 2 : index.xhtml

<lift:surround with="default" at="content">

<lift:ChatterInput form="post">

<div>What's happening?</div>

<div><chatter:text/></div>

<div class="right"><chatter:submit/></div>

<hr/>

</lift:ChatterInput>

</lift:surround>

Listing 3 : ChatterInput.scala

class ChatterInput extends Logging {

def render(xhtml: NodeSeq) = {

def handleUpdate(text: String) {

logger debug "Sending message: %s".format(text)

}

bind("chatter", xhtml,

"text" -> textarea("", handleUpdate _),

"submit" -> submit("Update", () => ()))

}

}

Listing 4 : User.scala

class User extends MegaProtoUser[User] {

override def firstNameDisplayName = "Name"

override def getSingleton = User

Listing 5 : User.scala

object User extends User with MetaMegaProtoUser[User] {

override def signupFields = firstName :: email :: password :: Nil

override def skipEmailValidation = true

...

Listing 6 : Boot.scala

...

val dbVendor =

new StandardDBVendor(Props get "db.driver" openOr "org.h2.Driver",

Props get "db.url" openOr "jdbc:h2:chatter",

Props get "db.user",

Props get "db.password") {

override def maxPoolSize = Props getInt "db.pool.size" openOr 3

}

DB.defineConnectionManager(DefaultConnectionIdentifier, dbVendor)

Schemifier.schemify(true, Log infoF _, User)

...

Listing 7 : Boot.scala

...

val ifLoggedIn = If(() => User.loggedIn_?,

() => RedirectResponse(User.loginPageURL))

val homeMenu = Menu(Loc("home", List("index"),"Home", ifLoggedIn))

val menus = homeMenu :: User.menus

LiftRules setSiteMap SiteMap(menus: _*)

...

Page 4: Java Magazin - Lift

Agile Prozesse mit agilen Praktiken haben sich in vielen Projekten bewährt. Zu den Erfolgskriterien agiler Projekte zählt die gelunge-ne Zusammenarbeit. Daher sind die Teams von überschaubarer Größe. Idealerweise arbeiten sie zusammen in einem Zimmer, an einer gemeinsamen Werkbank. Aber lässt sich dieses dynamische Setting auch auf andere, größere Projekte übertragen, damit der Erfolg der agilen Projekte auch auf diese abstrahlt?

Das Problem: Je höher man Projekte skaliert, desto weiter entfernt man sich von den bekannten Erfolgsvoraussetzungen: Das Team ist nicht mehr überschaubar und häu�g über mehrere Standorte verteilt. Außerdem gehören die Mitglieder oft verschiedenen Orga-nisationen an, arbeiten beispielsweise bei einer Partner�rma oder einem Zulieferer.

Diese Veränderung der Rahmenbedingungen führt zu Problemen in der Kommunikation und behindert die Abstimmung innerhalb des Teams. Der Informations�uss ist gestört, und damit verliert man einen der wichtigsten Vorteile der agilen Prozesse. Bewährte Tech-niken wie das Arbeiten mit Taskboards funktionieren nicht mehr.

Moderne Werkzeuge entschärfen ProblemeDiese Probleme lassen sich mit modernen Werkzeugen zur agilen Planung entschärfen. Ganz wichtig dabei ist ein gemeinsames Repository, in dem sich alle Artefakte des Projektes be�nden und den Teammitgliedern zur Verfügung stehen. Damit sind die Backlogs mit ihren Stories, Epics und Tasks für alle immer sichtbar. Produktverantwortliche und Teammitglieder werden bei der Ver-waltung des Backlogs deutlich entlastet. Auch das Taskboard lässt sich elektronisch p�egen, was den Projektfortschritt wieder für alle sichtbar macht. Gra�ken über den Zustand des Projektes wie zum Beispiel Burndown-Charts lassen sich automatisiert erstellen und spiegeln immer einen aktuellen Projektstand wider.

Damit lassen sich agile Vorgehensmodelle, wie zum Beispiel Scrum, bereits sehr ef�zient einsetzen. Dennoch bleibt es bei der Fülle von Informationen für die Teammitglieder schwierig, die für sie wichtigen und relevanten Informationen zu verfolgen und zu erfas-sen. Arbeiten zum Beispiel mehrere Personen an unterschiedlichen Standorten an einer Story, so fällt es allen Beteiligten schwer, sich ständig zu synchronisieren. Kontinuierlich treffen im Repository verfeinerte Anforderungen seitens der Business-Owner ein. Um diese Änderungen alle zur Kenntnis zu nehmen, müssten die Team-mitglieder fortlaufend das Repository durchforsten.

Viel praktischer und leichter ist es hingegen, wenn die Teammitglie-der Änderungen im Repository per RSS-Feed erhalten. In diesem Fall bekommen sie sofort eine Nachricht im eigenen Event-Log, wenn sich in der Story etwas ändert. Sie sind also immer auf dem aktuellen Stand der Informationen. Dieser Event-Log ist individu-ell auf den Mitarbeiter zugeschnitten: Er kann selbst auswählen, über welche Veränderungen er informiert werden will. Auf diese Weise �ießen die Informationen, die in agilen Projekten in den Daily Stand-Ups weitergegeben werden, auch standortübergreifend.

Anforderungen „just in time“ festhaltenDie Stories und Tasks spielen im Repository eine zentrale Rolle beim Informationsaustausch. Hier können die Anforderungen „just in time“ festgehalten werden. Zusätzlich bieten sie Rubri-ken für Diskussionen. So kann man im virtuellen Team Probleme und Fragestellungen gleich im Kontext der Stories erörtern. Die entsprechenden Änderungen sehen die Beteiligten sofort in ihrem Event-Log.

Instant Messaging ist heute an vielen Arbeitsplätzen Standard. Integriert in die IDE lässt sich auch hier ein Kontext zu Objekten

des Repositories herstellen. Beim Chatten erkennt das System automatisch Schlüsselwörter wie „Task“ oder „Story“ und erzeugt zusammen mit einer Nummer den Link zum entsprechenden Work-Item. Der Chat-Partner kann diesen Link anklicken und hat dann ebenfalls das Work-Item auf dem Schirm. Der Chat kann im Kontext weiter gehen.

Eigenschaften in die IDE integrierenDies alles ist nur dann bequem möglich, wenn all diese Eigen-schaften in die IDE der Entwickler eingebettet sind. Das leistet IBM Rational Team Concert durch die Integration des Clients sowohl in Eclipse als auch in Visual Studio .NET.

Continuous Integration ist eine weitere wichtige Praxis in agilen Projekten. Sie hat bei großen, verteilten Projekten eine besondere Bedeutung, denn hier können Integrationsprobleme besonders unangenehm und aufwändig werden. Daher ist ein Source-Code-Management-System unabdingbar, das auch standortübergreifend einsetzbar ist. Durch ein solches System können alle Entwickler auf den Quellcode zugreifen und diesen leicht integrieren.

Bei Fehlern alten Zustand wiederherstellenIn Rational Team Concert �ndet man eine enge Verzahnung mit dem Build-Management-System. Kontinuierlich und einfach lassen sich lokale und globale Builds erstellen. Bei Fehlern können die Mitarbeiter über einen Snapshot des Repositories den alten Zu-stand ohne viel Aufwand wiederherstellen.

Dieses gemeinsame Repository macht auch eine leistungsfähige Verknüpfung zwischen Quellcode und Stories bzw. Work-Items möglich. Per Knopfdruck �ndet man heraus, warum welche Pro-grammzeile von wem verändert wurde. Der umgekehrte Fall ist fast noch wichtiger: Welche Änderungen waren nötig, um eine Story zu realisieren oder um einen Fehler zu beheben? Nur so lassen sich behobene Fehler auch in mehreren Versionen sicher nachkorrigie-ren.

Agile Vorgehensweisen lassen sich nur skalieren, wenn den Teammit-gliedern dafür die geeigneten Werkzeuge zur Verfügung stehen. IBM hat diese Erfahrung bei der Entwicklung von Rational Team Concert selbst gemacht und genutzt. Denn das IBM-Entwicklerteam hat bei der Arbeit bereits Alphaversionen des Produkts verwendet und die frischen Erfahrungen nahtlos in der Weiterentwicklung und Vollen-dung der Software umgesetzt. Dabei ist mit Rational Team Concert eine vollständig interaktive Entwicklungsumgebung für größere, unternehmensweite Teams entstanden. Ein Werkzeug, mit dem sich Agilität skalieren lässt.

Interesse?Dann setzen Sie sich noch heute mit uns in Verbindung. Ansprechpartner: Werner Schoepe Telefon: +49-211-476-2163 E-Mail: [email protected]

Weitere Infos und Downloads �nden sich unter: www.ibm.com/software/products/de/de/rtc-standard

oder direkt auf der Community Site: www.jazz.net

Treffen Sie Werner Schoepe auf der JAX 2010 – zu seinem Vortrag „Die neue Herausforderung: Skalierung von agilen Projekten!“ am Montag, 3. Mai, 11:45 – 12:30 UhrOder auf dem IBM-Stand.

Agile Projekte – stabil oder fragil?Um agile Projekte zu skalieren, brauchen Teams die passenden Werkzeuge

Anzeige

Page 5: Java Magazin - Lift

Web

56 www.JAXenter.de

Lift-Webframework

javamagazin 5|2010

mapper unterstützt natürlich alle gängi-gen relationalen Datenbanken.

Menüs und Zugriffskontrolle

Jetzt müssen wir dafür sorgen, dass nur angemeldete Benutzer Chatter verwen-den können. Dazu müssen wir erst noch das Menü und die Seiten der Benutzer-verwaltung anzeigen. Das konfigurieren wir wiederum in Boot, indem wir eine Site map definieren (Listing 7). Diese

kümmert sich um Menüs und optio-nale Zugriffskontrolle. Hier definieren wir das Home-Menü, sodass wir nur als angemeldete Benutzer Zugriff haben, sowie das Menü für die Benutzerverwal-tung, das wir von unserer User-Klasse quasi geschenkt bekommen.

Chat mit Ajax und Comet

So weit, so gut, aber das Wichtigste fehlt noch: Wir wollen Nachrichten verschi-

cken bzw. empfangene Nachrichten anzeigen. Dazu ergänzen wir als Erstes unser Formular um einen Bereich zur Anzeige der Nachrichten (Listing 8). Ähnlich wie ein Snippet, referenzieren wir mit dem Element lift:comet und des-sen type-Attribut eine Klasse, die sich um Comet kümmert. Weiter finden wir im Template einen Block mit statischen HTML-Elementen und Platzhaltern zur Anzeige einer Nachricht.

Listing 8

...

<lift:comet type="ChatterMessages">

<messages:list>

<div>

<span class="messageName"><message:name/></span>

<span class="messageDate"><message:date/></span>

<message:text/>

</div>

</messages:list>

</lift:comet>

...

Listing 9 : ChatterMessages.scala

class ChatterMessages extends CometActor with

CometListener with Logging {

private var messages = List[ChatterMessage]()

override def render = {

def bindMessages(template: NodeSeq): NodeSeq = messages flatMap { m =>

bind("message", template,

"name" -> m.name,

"date" -> format(m.date, boxedSessionLocale),

"text" -> m.text)

}

logger debug "Rendering messages: %s".format(messages)

bind("messages", defaultXml, "list" -> bindMessages _)

}

override def lowPriority = {

case message: ChatterMessage => {

logger debug "Received Chatter message: %s".format(message)

messages ::= message

reRender(false)

}

}

override def registerWith = ChatterServer

}

Listing 10 : ChatterInput.scala

...

ajaxForm(After(100, SetValueAndFocus(messageId, "")),

bind("chatter", xhtml,

"name" -> user.shortName,

"text" -> textarea("", handleUpdate _, "id" -> messageId),

"submit" -> submit("Update", () => ())))

...

Listing 11 : ChatterInput.scala

...

def handleUpdate(text: String) {

val message = ChatterMessage(user.id.is, user.shortName, now, text.trim)

logger debug "Sending Chatter message to server: %s".format(message)

ChatterServer ! message

}

...

Listing 12 : ChatterServer.scala

object ChatterServer extends LiftActor with ListenerManager with Logging {

private var message: ChatterMessage = _

override def lowPriority = {

case message @ ChatterMessage(_, _, _, text) if !text.isEmpty => {

logger debug "Received Chatter message: %s".format(message)

this.message = message

updateListeners()

}

}

override protected def createUpdate = message

}

Listing 13 : UserFollowingUser.scala

object UserFollowingUser extends UserFollowingUser

with LongKeyedMetaMapper[UserFollowingUser] {

def following_?(u: User, userId: Long) =

find(By(user, u), By(following, userId)).isDefined

def findAllFollowers = User.currentUser map { u =>

User findAll In(User.id, user, By(following, u))

} openOr Nil

...

}

class UserFollowingUser extends LongKeyedMapper[UserFollowingUser]

with IdPK with Logging {

val user = new MappedLongForeignKey(this, User) {}

val following = new MappedLongForeignKey(this, User) {}

...

}

Listing 14 : ChatterMessages.scala

override def shouldUpdate = {

case ChatterMessage(userId, _, _, _) =>

if (user.id.is == userId) true else following_?(user, userId)

}

Page 6: Java Magazin - Lift

Web

www.JAXenter.de 57

Lift-Webframework

javamagazin 5|2010

Unsere Comet-Klasse ChatterMes-sages, dargestellt in Listing 9, kümmert sich um das Empfangen und die Anzei-ge der Nachrichten. Sie ist ein CometAc-tor und ein CometListener: Mit der Me-thode lowPriority werden eingehende Nachrichten entgegengenommen und threadsafe verarbeitet. Für die Anzeige ist die Methode render verantwortlich, die sich den Nachrichtenblock aus dem Template schnappt und damit jede ein-zelne Nachricht darstellt. Wie beim Snippet weiter oben, werden die Platz-halter durch die anzuzeigenden Daten ersetzt, hier Absendername, Datum und Text der Nachricht. Durch registerWith meldet sich unsere Comet-Klasse beim ChatterServer (siehe unten) an.

Nun müssen wir noch das Versen-den von Nachrichten ergänzen. Zuerst machen wir aus unserem Eingabefor-mular ein Ajax-Formular, denn wir wollen nicht bei jedem Versenden einer Nachricht die Seite neu laden. Dies geht mit Lift wirklich einfach (Listing 10), denn Lift abstrahiert Ajax durch ein Sca-la-API. Hier umgeben wir bloß unseren existierenden Code im ChatterInput-Snippet mit der Methode ajaxForm, der wir zusätzlich noch ein JavaScript-Kom-mando mitgeben, das die Textarea kurz nach dem Verschicken der Nachricht leert.

Dann müssen wir, statt nur zu loggen, nun tatsächlich eine Nachricht verschi-cken. Dazu erweitern wir die handleUp-date-Methode im ChatterInput-Snippet (Listing 11). Wir erzeugen eine Chat-terMessage mit Absendername, Datum und Text und schicken sie an den Chat-terServer (Listing 12). Letzterer ist ein LiftActor und ein ListenerManager, d. h. er kann Nachrichten empfangen, die er an (zunächst noch) alle Listener schickt, indem er updateListeners aufruft.

Persistenz mit Mapper

OK, damit sind wir fast schon fertig. Nun müssen wir Chatter nur noch so erweitern, dass Nachrichten nicht an alle geschickt werden, sondern nur an unsere Freunde, d. h. diejenigen, die uns folgen. Dazu müs-sen wir erst einmal die Möglichkeit schaf-fen, anderen zu folgen, und dazu brau-chen wir ein Template following.xhtml, das sowohl die Benutzer auflistet, denen

wir schon folgen, als auch die Möglichkeit bietet, weiteren Benutzern zu folgen. Der Vollständigkeit halber möchten wir na-türlich auch sehen, wer uns folgt, sodass wir auch ein Template followers.xhtml be-nötigen. Die Interaktionen setzen wir mit dem Follow-Snippet um. Diese Templates und dieses Snippet sind ganz ähnlich ge-strickt wie die bisherigen, sodass wir hier auf Listings verzichten – bei Interesse bitte einfach in den Sourcecode schauen.

Natürlich müssen wir diese Informa-tionen persistieren: Die Mapper-Klasse UserFollowingUser kümmert sich um die-se Referenzen von einem Benutzer auf an-dere und das dazu gehörende Companion Object bietet uns die nötigen Methoden für unser Follow Snippet bzw. die Chat-terMessages (Listing 13). Wir erweitern nun noch unsere ChatterMessage um die ID des Absenders und teilen dem Chatter-Server über die Methode shouldUpdate in ChatterMessages mit, ob wir eine Nach-richt empfangen wollen: Das wollen wir genau dann, wenn wir selbst der Absender sind oder wenn wir dem Absender folgen (Listing 14). Damit hätten wir es geschafft: Chatter erstrahlt in voller Pracht (Abb. 2).

Fazit

Dieser schnelle Überblick über einige der wichtigsten Features von Lift zeigt, wie produktiv wir mit Lift Webapplika-tionen entwickeln können: Es werden nicht nur wichtige und moderne Tech-nologien wie Ajax und Comet einfach zugänglich gemacht, sondern es stehen außerdem viele typische Funktionen wie

Menüs, Zugriffskontrolle, Persistenz, Benutzerverwaltung und vieles mehr fertig zur Verfügung. Das macht klar, warum Lift heute eine herausragende Position im Scala-Ökosystem einnimmt. Bleibt nur die Frage, wann Sie sich mit Lift befassen?

Abb. 2: Chatter in Endaus-baustufe

Heiko Seeberger ist geschäftsführender Gesell-schafter der Weigle Wilczek GmbH und verantwortlich für die technologische Stra-tegie des Unternehmens mit

den Schwerpunkten Java, Scala, OSGi, Eclipse RCP und Lift. Zudem ist er aktiver Open Source Committer, Autor zahlreicher Fachartikel und Redner auf einschlägigen Konferenzen.Johannes Hohenbichler ist Soft-wareentwickler bei der Weigle Wilczek GmbH und definiert seinen technologi-schen Schwerpunkt im Bereich Eclipse RCP, OSGi, Scala und Lift.

Links & Literatur

[1] http://www.twitter.com

[2] http://liftweb.net/team.html

[3] http://wicket.apache.org

[4] http://rubyonrails.org

[5] http://www.djangoproject.com

[6] http://foursquare.com

[7] http://incubator.apache.org/esme

[8] http://it-republik.de/jaxenter/ quickvote/results/1/poll/75

[9] http://maven.apache.org

[10] http://www.h2database.com