Wie in meinem letzten Beitrag angekündigt, wollte ich mich tiefergehend mit den Möglichkeiten des Grails Portlet Plugins beschäftigen. Da die Dokumentation nicht besonders aussagekräftig ist, galt es zunächst herauszufinden, ob man nicht nur Deskriptoren und Portlet Views generieren sondern auch Domain-Objekte im Portlet verwenden kann.
Dafür habe ich zu Testzwecken ein Domainobjekt mit dem Namen Blogpost erstellt:
grails create-domain-class de.communardo.liferay.grails.portlet.Blogpost
Die Domain Klasse Blogpost bekam schnell ein paar Properties:
package de.communardo.liferay.grails.portlet
class Blogpost {
String title
String author
String content
}
Danach fix die Views generiert, als Vorlage für den Portlet View:
grails generate-portlet-views de.communardo.liferay.grails.portlet.Blogpost
Den folgenden Teil habe ich in die view.gsp für das Portlet übernommen:
<h1>Blogpost List</h1>
<div class="list">
<table>
<thead>
<tr>
<g:sortableColumn property="id" title="Id" />
<g:sortableColumn property="author" title="Author" />
<g:sortableColumn property="content" title="Content" />
<g:sortableColumn property="title" title="Title" />
</tr>
</thead>
<tbody>
<g:each in="${blogpostInstanceList}" status="i" var="blogpostInstance">
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td><g:link action="show" id="${blogpostInstance.id}">${fieldValue(bean:blogpostInstance, field:'id')}</g:link></td>
<td>${fieldValue(bean:blogpostInstance, field:'author')}</td>
<td>${fieldValue(bean:blogpostInstance, field:'content')}</td>
<td>${fieldValue(bean:blogpostInstance, field:'title')}</td>
</tr>
</g:each>
</tbody>
</table>
</div>
Danach habe ich die Liste mit Blogposts in der renderView Closure der Portlet Klasse mit den vorhandenen Blogposts befüllt:
def renderView = {
[ blogpostInstanceList: Blogpost.list()]
}
Für die Testdaten kam die bootstrap.groovy wie gerufen:
class BootStrap {
def init = { servletContext ->
new Blogpost(author:"Ralf Borchers",title:"Lifray Potlets mit Grails", content:"Es geht...").save()
}
...
}
Dadurch werden die Daten beim Starten der Anwendung standardmäßig in einer HSQL DB gespeichert.
Damit war die Vorbreitung schon abgeschlossen und mein Portlet konnte redeployed werden:
grails war
Daraufhin war das Portlet kaputt und warf eine IllegalstateException:
java.lang.IllegalStateException: Root context attribute is not of type WebApplicationContext: com.liferay.portal.spring.context.PortalApplicationContext@10e35d5: display name [Root WebApplic
ationContext];
Ursache dafür scheint zu sein, dass das Hot Deployment von Spring Portlets nicht so richtig sauber funktioniert. Siehe auch hier. Nach dem Neustart startete das Portlet fehlerfrei und ich konnte stolz meinen Test-Blogposts im Portlet betrachten.
Zusammenfassend lässt sich sagen, dass man für komplexere Portlets eine Realisierung in Form eines Grails Portlets in Erwägung ziehen sollte. Man profitiert von vielen Features des Grails Frameworks und kann die Produktivität bei der Portletentwicklung sicher enorm steigern.
Seit meinem letzten Grails Projekt verfolge ich aufmerksam jede Neuigkeit zum Thema Groovy und Grails. Aber auch die Entwicklung von Portlets für den freien Portal Server Liferay liegt im Bereich meines Interesses. Als ich dann auf groovyblogs.org (empfehlenswerte Quelle!) laß, dass ein Grails Plugin released wurde, mit dem man Portlets für Liferay erstellen kann, war klar, dass ich das ausprobieren muss. Die Vorstellung davon, Portlet- und Deploymentdeskriptoren nicht alle händisch erstellen zu müssen, Actions sowie Views generieren zu lassen und dazu noch in Groovy entwickeln zu können erschien mir durchaus als vielversprechend.
Doch als Erstes mussten die Hürden einer Installation und eines lauffähigen Deployments genommen werden, was ich im Folgenden dokumentieren möchte:
1. Liferay ab Version 5.2 installieren
2. Grails ab Version 1.1.1 installieren
3. Umgebungsvariable für Liferay setzen:
LIFERAY_HOME=/your_path_to/liferay-portlet-5.2.1
4. Grails Application erstellen:
grails create-app demoportlets
5. In App Verzeichnis wechseln und Portlet Plugins installieren:
grails install-plugin portlets
grails install-plugin portlets-liferay
grails install-plugin liferay-exploded
6. Portlet erstellen: Hierbei sollte man darauf achten, keine Großbuchstaben im Namen (z.B. demoportlet) zu verwenden, da die gsp`s zur Laufzeit in einem Verzeichnis ohne Großbuchstaben gesucht werden.
grails create-portlet demoportlet
Ergebnis dieses Befehls ist, dass im Verzeichnis “portlets” eine lauffähige Portletklasse mit dem Namen DemoPortlet.groovy mit folgendem Inhalt liegt:
import javax.portlet.*
class DemoportletPortlet {
def title = 'Portlet Title'
def description = '''
Description about the portlet goes here.
'''
def displayName = 'Display Name'
def supports = ['text/html':['view', 'edit', 'help']]
// Liferay server specific configurations
def liferay_display_category = 'MyCategory'
def actionEdit = {
//TODO Define action phase
portletResponse.setPortletMode(PortletMode.VIEW)
}
def renderEdit = {
//TODO Define render phase. Return the map of the variables bound to the view
['mykey':'renderEdit called']
}
def actionView = {
//TODO Define action phase
}
def renderView = {
//TODO Define render phase. Return the map of the variables bound to the view
['mykey':'renderView called']
}
def actionHelp = {
//TODO Define action phase
portletResponse.setPortletMode(PortletMode.VIEW)
}
def renderHelp = {
//TODO Define render phase. Return the map of the variables bound to the view
['mykey':'renderHelp called']
}
}
7. Ich hatte das Problem, dass beim Generieren der Views ein Fehler flog, weil native2ascii nicht gefunden werden konnte. Das kann man beheben, in dem man JAVA_HOME auf ein JDK zeigen lässt. Alternativ kann man folgende Option in der config.groovy ausschalten um den Fehler zu umgehen:
grails.enable.native2ascii = false
8. Views generieren – netter Befehl, um edit.gsp, view.gsp und help.gsp vorzugenerieren:
grails generate-portlet-views demoportlet
9. Bauen und Deployen: An der Stelle hatte ich die größten Probleme, da auf meinem Rechner standardmäßig Java 1.6 läuft, Liferay jedoch seine eigene JRE mit der Version 1.5 mitbringt. Deswegen lautet meine Empfehlung: Bauen mit Java 1.5!
grails war
Danach kann man das fertige war-File aus dem App-Verzeichnis nach “liferay/deploy” kopieren, was bei einem laufenden Liferay dazu führen sollte, dass das Portlet automatisch deployed wird.
Alternativ kann man zum Deployen auch das Liferay Exploded Plugin verwenden, was bei mir aber leider nicht funktioniert hat und beim Deployen mit einem Fehler quittiert wurde:
grails liferay-deploy
und folgend grails liferay-update
Nach dem Deployen sollte man das Portlet in der Kategorie “MyCategory” finden und auf einer beliebigen Seite instantiieren können.

DemoPortlet
Getestet habe ich das Deployment in Liferay Version 5.2.1 und 5.2.3, was dank Java 1.5 auch für beide Versionen funktioniert hat.
Für das ganze Prozedere habe ich am Ende doch länger gebraucht als erwartet und war einige Male kurz davor aufzugeben. Jetzt bin ich aber wieder zuversichtlich und werde mich demnächst damit befassen, was man denn nun alles von der Grails-Funktionaliät im Portlet verwenden kann. Das Portlet selber bringt immerhin 23 MB auf die Waage und enthält sämtliche Libraries die Grails auch sonst so benötigt, wie Core, Crud, Gorm, Webflow etc. Das lässt zumindest hoffen, dass man auch Domainobjekte im Portlet verwenden kann.
Links:
http://grails.org/plugin/portlets
http://grails.org/plugin/portlets-liferay
http://grails.org/plugin/liferay-exploded
Um in die Ausführung von Skripten zur Laufzeit einzugreifen gibt es das Feature der Scripting Events. Fast jedes Grails-Skript beinhaltet ein oder mehrere Einstiegspunkte (Hooks), welche man abgreifen und dann im Kontext des gerade laufenden Skriptes eigene Aktionen ausführen kann. Dazu wird einfach im Ordner /scripts die Datei _Events.groovy erstellt und von dort aus lässt sich nun auf einfache Weise überall “einhaken”.
Im letzten Projekt nutzten wir dieses Feature um zusätzliche Properties in der web.xml zu ersetzen und das funktionierte auch wunderbar in der lokalen Windows-Entwicklungsumgebung. Leider aber nicht auf der Build-Maschine, welche unter einem Unix-Betriebssystem lief. Der Fehler war nach langem Probieren ausgemacht – beim Anlegen des Skriptes wurde fälschlicherweise der Name _events.groovy anstatt _Events.groovy vergeben. Anscheinend ist Windows etwas weniger Restriktiv was den Aufruf der Datei anhand des Namens betrifft, ein Umbennen schaffte letztendlich abhilfe.
Bei Fehlern in Groovy Server Pages (kurz Gsp), erhält man erfreulicher Weise einen Stacktrace direkt auf der Seite in der jener Fehler aufgetreten ist. Weniger hilfreich ist allerdings, dass die angegebenen Zeilenummern in denen der Fehler auftrat nicht mit denen des geschriebenen Quellcodes zusammenpassen, wie dies z. B. bei Groovy-Klassen der Fall ist. Hier ein kleines Beispiel mit einer provozierten LazyInitializationException (../books/show/1):

Zufällig bin ich durch einen Blog-Beitrag auf einen Parameter gestoßen, welcher den kompilierten Code einer Gsp direkt im Browser ausgibt (beschrieben ist dies auch in der Grails API im GroovyPagesServlet). Mit showSource=true erhält man den Quellcode samt Zeilennummern für die ausgewählte Url angezeigt. Für das obere Beispiel würde dies dann so aussehen (../books/show/1?showSource=true):

Das Ganze funktioniert natürlich nur im “Development-Mode”, eine Sicherheitslücke für den produktiven Einsatz muss daher nicht befürchet werden
Grails verwendet intern Spring MVC als Web Framework, wodurch man auch in den Genuss von Spring Webflow kommt. Die Konversationen lassen sich Grails-üblich mit wenig Aufwand implementieren. Wenn man jedoch innerhalb einzelner States alle States des Webflows in einem View ausgeben möchte, z.B. um den Fortschritt innerhalb des Flows zu zeigen, findet man keine Hilfe in der Grails Dokumentation. Nach dem ich herausbekommen hatte, wie man den FlowExecutionContext bekommt und mich ein wenig mit der Spring API außeinander gesetzt hatte, bin ich zu der folgenden Lösung gekommen:

Um daraus eine richtige Navigation zu machen, müsste man dem jeweiligen Link zu jedem State noch einen Parameter hinzufügen, dessen Wert man in dem Startzustand des Workflow Controllers auswertet und auf den Folgezustand mappt:

Die Kombination von Hibernate und komplexen Datenstrukturen bringt einige Herausforderungen mitsich und daher ist dem ein oder anderen die Exception aus dem Titel sicher geläufig. In einigen Fällen hat man an solchen Punkten eine fast fertige Anwendung und es geht an die Implementierung von Jobs oder neuen Anforderungen. Nun lassen sich diese meist nicht, oder sehr schwer, mit den vorhandenen Methoden abbilden und man fängt an, durch die Hintertür einige Abfragen zusammenzubauen um an die entsprechenden Daten zu kommen. Gerade auch bei Jobs werden mitunter Daten geladen, welche querfeldein durch die bestehenden Methoden der Anwendung geschickt werden. Leider bleibt hier desöfteren ein wichtiger Teil auf der Strecke – die Verbindung zur Hibernate Session.
Grundsätzlich sind alle Ladevorgänge aus der Datenbank “Lazy-Loading”. Dies soll verhindern, dass man die halbe Datenbank in den Speicher lädt falls die Beziehungen zwischen den Tabellen eng verwoben sind. Praktisch bedeutet das, Hibernate lädt bei Abfrage von Objekten zugehörige Referenzen (Collections/ Unterobjekte) als Proxies.
Ein Proxy-Objekt enthält dabei nur die Id des Objektes in der Datenbank und beim ersten Zugriff wird es automatisch nachgeladen. Der Nachteil an der Sache ist aber nun, wenn die Hibernate-Session geschlossen wird, befindet sich das gerade geladenen Objekt im Zustand detached.
Das bedeutet, ein Objekt ist nicht mehr in Synchronisation mit der Datenbank und demzufolge können auch für die Proxy-Objekte keine Daten mehr nachgeladen werden – die Anwendung wirft eine LazyInitializationException.
Nun gibt es mehrere Möglichkeiten etwas dagegen zu tun, schauen wir uns zunächst mal die Einfachste an. In der Methode in der auf Parameter eines Objektes zugegriffen wird, prüft man einfach ob die Verbindung zur Session noch da ist und falls nicht, dann wird das Objekt eben wieder drangehangen (attached).
def someAction(Author author) {
....
if(!author.isAttached()) {
author.attach()
}
}
Oft hat man jedoch nicht die Möglichkeit in den Methoden das Objekt wieder an die Session zu hängen. Entweder ist die benutzte API bereits fertig oder man muss sehr viele Methoden anpassen.
Nun gibt es dafür aber eine ebenfalls einfache Lösung: In Grails kann man den FetchMode, also die Strategie zum Nachladen der referenzierten Objekte, direkt in der jeweiligen Domain-Klasse definieren. Der folgende Code demonstriert das anschaulich:
class Author {
String firstname
String surname
static hasMany = [ books : Book ]
static fetchMode = [ books : 'eager' ]
}
class Book {
String name
static belongsTo = Author
static hasMany = [categories : Category]
static fetchMode = [categories : 'eager']
}
class Category {
String name
static belongsTo = Book
}
Der Vorteil dieser Lösung ist, dass man sich wahrscheinlich an keiner Stelle der Anwendung mehr Gedanken um das Nachladen der Bücher des Authors samt zugehöriger Kategorien machen muss. Hat man viele Bücher bzw. Kategorien zieht man sich jeoch, wie bereits oben erwähnt, ziemlich viele Daten in den Speicher – auch in Situationen in denen man beispielsweise nur alle Authoren auflisten möchte.
Wesentlich eleganter als die obere Strategie, ist es die benötigten Objekte beim Laden der Daten mit anzuzeigen. Über den Hibernate CriteriaBuilder lässt sich der FetchMode bequem für jedes Property einzeln definieren. “Nested Properties”, also verschachtelte Objekte werden durch den Punkt-Operator in der Query referenziert. Die nachfolgende Abfrage holt alle Authoren, samt Bücher und Kategorien:
import org.hibernate.FetchMode as FM
....
def criteria = Author.createCriteria()
def result = criteria.list {
fetchMode("books", FM.EAGER)
fetchMode("books.categories", FM.EAGER)
}
In einigen Fällen hat man hier das Problem, dass die Datensätze mehrfach in der Liste erscheinen. Hibernate führt beim “Eager-Fetch” einen Outer Join durch und deshalb duplizieren sich die Einträge für gefundene Unterobjekte . Eine Lösung dafür ist criteria.listDistinct{} anstatt criteria.list{} zu verwenden.
Ein Nachteil dieser Lösung soll auch nicht verschwiegen werden: Zwar bekommt man die Referenzen in den Speicher geladen welche man möchte, nur muss man vorher genau wissen was man an anderer Stelle benötigt. Wird eine Methode mit dieser Query aufgerufen und es werden gar nicht alle Bücher/Kategorien gebraucht, verschenkt man auch hier wieder Performance.
Anmerkung: Dasselbe kann man natürlich auch mit HQL bzw. DynamicFinder machen, hier übergibt man den FetchMode dann als Parameter der Funktion.
Die Validierung von Attributen direkt in Domain-Klassen durchzuführen ist nicht nur praktisch, sondern spart auch an mancher Stelle aufwändiges Exception-Handling. Mit einfachen Mitteln ist man in der Lage die Korrektheit der übergebenen Variablen zu überprüfen, bei Bedarf auch gleich mit einer lokalisierten Fehlermeldung.
Zur Einführung wird zunächst eine simple Klasse mit entsprechenden Constraints erzeugt:
class User {
String login
String surname
String lastname
static constraints = {
login(size:5..8,blank:false)
surname(minSize:3, blank:false)
lastname(minsize:3, blank:false)
}
}
Beim Aufruf der Methoden save() bzw. validate() werden die gesetzten Werte über die Constraints überprüft und ein eventueller Fehler samt dem zugehörigen Klassen-Attribut im Objekt selbst hinterlegt. Beim standardmäßigen Aufruf einer .gsp können diese Fehler dann ausgegeben werden. Im folgenden Listing ist diese Funktion am Beispiel einer Service-Klasse dargestellt:
class UserService {
...
User user = new User(login:"myLogin", surname:"hans", lastname:"m")
if(!user.validate()) {
user.errors.hasErrors.each {
println "-> Error: " + it.getField()
}
}
user.save()
...
}
Das Speichern des User-Objektes würde im oberen Fall fehlschlagen und auf der Konsole der Name des invaliden Feldes auftauchen. Da Domain-Klassen in der Regel aber etwas komplexer sind, zeigt das nächste Listing die Auslagerung des Nutzernamens in einer separaten Klasse:
class UserName {
String surname
String lastname
static constraints = {
surname(minSize:3, blank:false)
lastname(minsize:3, blank:false)
}
}
class User {
String login
UserName name
static constraints = {
login(size:5..8,blank:false)
name(nullable:false)
}
}
Am 13./14.9.2008 fand erstmals die Berlin.jar an der FHTW Berlin statt. Die Java Konferenz wurde durch die Java User Group Berlin Brandenburg sehr gut organisiert. Verteilt über beide Konferenztage gab es in fünf parallelen Tracks zahlreiche interessante Vorträge, sowie HandsOn Sessions und Workshops für die mehr als 250 Besucher. Einige Redner sind bereits von anderen Konferenzen bekannt. So gab Eberhard Wolff gleich zu Beginn Einblick in das Lösungsangebot von SpringSource, der Firma hinter dem Spring Framework. Im Anschluss gab Torsten Fink einen Überblick über die JBoss/SOA-Plattform mit allerlei Verweisen auf den Einsatz in der Praxis. Nachdem am Grillstand für das leibliche Wohl gesorgt wurde, brachte Alexander Greif anhand einer im Rahmen seines Vortrages erstellten Anwendung den Zuhörern die Funktionsweise der Grails Plattform näher. Abgerundet wurde der Tag durch Oliver Böhms Vortrag zu aspektorientierter Softwareentwicklung. Dabei wurde der Frage „Gibt es ein Leben nach Java und OO?“ nachgegangen. Mein Fazit: Aspektorientierte Programmierung ist eine sinnvolle Ergänzung (!) zur objektorientierten Entwicklung, aber sicher kein grundsätzlich neuer Ansatz. Den Weg in die Praxis hat sie schon seit längerer Zeit gefunden, wie z.B. das Spring Framework beweist.
Leider waren damit der erste Konferenztag und unser Besuch der Berlin.jar schon vorbei. Am Tag zwei haben wir u.a. die Vorträge der ubigrate GmbH (Drahtwanderung: WIIr machen den NäXTen Schritt) und der buschmais GbR (Integrationsmuster am Beispiel von Apache Camel, Paradigmenhochzeit: Felix und ServiceMix in trauter Zweisamkeit vereint, Modellierung statischer Domänenmodelle mit Xtext) verpasst, aber vielleicht gibt es ja schon bald ein Wiedersehen bei der JUG Saxony
Dieses wird es auf alle Fälle im kommenden Jahr bei der zweiten Ausgabe der Berlin.jar geben.
Nach dem Neuaufsetzen einer bestehenden Grails-Anwendung, tauchte nachfolgender Fehler während der Ansprache der Datenbank durch die Applikation auf:
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: bigint ~~ bigint
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:1512)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1297)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:188)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:437)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:353)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:257)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:92)
... 2 more
Zunächst vermutete ich ein JDBC Treiber-Problem, jedoch konnte der Fehler nach etwas Recherche und Debugging auf das Object-Mapping eingegrenzt werden. Das Kernproblem ließ sich dann letzendlich seitens der Datenbank bzw. deren Ansprache durch das Grails ORM identifizieren – ich hatte versehentlich eine falsche Version der PostgreSQL Datenbank installiert. Da Grails Hibernate als Persistenz-Framework verwendet (GORM) und dieses nur Postgres bis zur Version 8.1 unterstützt, quittierte meine Application die meisten Datenbankoperationen mit obiger Meldung.
Links: Von Hibernate unterstützte Datenbanken
Dieser Artikel soll einen Überblick über das Session Objekt in Grails geben und Lösungsmöglichkeiten bei Anwendungsfehlern aufzeigen.
In allen Controllern ist die Session bereits standardmäßig im Application-Scope und wartet gebrauchsfertig mit dem Namen “session” auf Verwendung. Das Objekt implementiert im wesentlichen die Standardfunktionalität der javax.servlet.HttpSession und hält ergänzend die Zugriffsmöglichkeiten einer GroovyMap bereit.
session.username = ‘John Doe’
println session["username"] oder
println session.username
Das Objekt selbst wird im Hintergrund über request.getSession() initialisiert, daher hat man auch nach einem session.invalidate() bzw. einem Timeout beim nächsten Actionaufruf (Achtung: Nicht im selben Controller!) eine neue Session. Am Beispiel einer Nutzer-Authentifizierung soll der Zugriff verdeutlich werden:
//Im Interceptor ist die Authentifizierung definiert, die vor jeder Action ausgeführt werden soll
def beforeInterceptor = [action:this.&auth,except:'login']//Falls kein Nutzer in der Session gefunden wird, erfolgt eine Weiterleitung zur Login-Action
def auth() {
if(!session.user) {
redirect(action:’login’)
return false
}
}
def login = {
// zeige Login-Seite
}
Wie oben bereits angesprochen, muss man sich kein neues Session Objekt nach einem Timeout besorgen. Leider hat dieser Mechanismus aber den Nachteil, dass man dadurch nicht merkt ob eine neue Session durch den Timeout einer Vorhergehenden erzeugt wurde und Rückschlüsse darauf lassen nur eine neue ID und die fehlenden Session-Attribute zu.
Braucht ein Nutzer z.B. zu lange um eine Formular auszufüllen, bekommt er beim nächsten Klick den Zugriff verweigert oder der aktuelle Bearbeitungssatus wird zurückgesetzt. Sicher ist dies in den meisten Fällen eines Session-Timeouts der Fall, aber hier ist der unberechtigte Zugriff mit dem Timeout gleichgesetzt und der Nutzer kann schlecht explizit auf eine abgelaufene Session hingewiesen werden.
Eine Möglichkeit herauszufinden ob die aktuelle Session noch existiert und eine entsprechende Meldung auszugeben, wäre einen Parameter zu setzen und diesen vor einer Action abzufragen:
static beforeInterceptor = [action:this&checkSession, except:'login']
def checkSession = {
if(!session.timeout)
render(“Session Timeout!”)
}
Die Möglichkeiten eines Interceptors sind vielfältig, sollten aber gerade bei Ausführung nach Methodenaufrufen mit bedacht eingesetzt werden.
Beispiel: Man definiert in einer Basisklasse den Zugriff auf die Session per AfterInterceptor (z.B. für einen Logger) und leitet diese dann in einem Controller ab. In einer neuen Action wird nun die Session ungültig gemacht und als Ergebnis löst der Aufruf der Session-Variable im Interceptor automatisch eine IllegalStateException aus.
Dieser Aufruf kann auch schon die blosse Ausgabe der Session sein, da das Objekt im Controller nicht mehr exisiert. Im nachfolgenden Codeauszug wird der eben beschriebene Fall veranschaulicht:
class BasisController {
def afterInterceptor = {
//wirft eine IllegalStateException
println session
}
…
}class UserInterface extends BasisController {
//Erzeugt nach dem render eine IllegalStateException
def logout = {
session.invalidate()
render(view:’logout’)
}
}
Ein Workaround wäre, im Interceptor nicht die Session-Variable des Controller zu nutzen. Stattdessen sollte man die Session direkt aus dem Request holen:
class BasisController {
def afterInterceptor = {
//gibt die Session aus oder null, wenn diese ungültig wurde
println request.getSession(false)
}
…
}
Ein großer Nachteil der automatischen Sessionvergabe war bislang auch die Tatsache, dass bei einzelnen Controlleraufrufen (z.B. durch einen externen Service), ebenfalls eine Session erzeugt wurde, obwohl diese gar nicht benötigt wurde. So hatte man bei einem Aufruf durch einen Webservice nach jedem Request immer eine Session, welche unbenutzt auf den Timeout wartete.
Seit Grails 1.0.1 wird nur nach der expliziten Nutzung des Session Objektes im Controller eine neue Session angelegt. Siehe dazu http://jira.codehaus.org/browse/GRAILS-1238.
Releaseparty at Atlassian? Confluence 3.2 BETA and 3.1.2 with soms bugfixes were released yesterday. [...]
Tino Schmidt's Vortrag zu Enterprise Mashups auf der webciety, 4.3 Remix the Web http://bit.ly/d26rtA [...]
neuer Blogpost: February Cumulative Update (2010) http://bit.ly/cwxZGE [...]
Webinar am 16.03.: „Communote Enterprise Microblogging - Funktionen und Einsatzbereiche im Unternehmen“ http://bit.ly/96eexF [...]