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
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)
}
}
So ein SequenceGenerator ist schon eine feine Sache, vorallem bei der Verwendung von Relationalen Datenbanksystemen á la Oracle. Musste man sich ohne die Vorzüge des ORM (Object-Relational-Mapping) noch mit der händischen Erstellung von Sequenzen zur Incrementierung von Werten herumschlagen, erledigt Hibernate das Ganze voll automatisch mit zwei Zeilen Code.
In meinem letzten Projekt zeigte sich jedoch ein interessantes Phänomen: Ich erstellte eine Klasse mit Annotation und einer Autoincrement-Sequence zum Hochzählen der Id.
@Entity
@SequenceGenerator(name = "user_seq", sequenceName = "user_id_seq")
public class User implements Serializable { ... }
Zunächst schien alles zu funktionieren, als ich aber einige Zeit später in die Datenbank schaute entdeckte ich seltsame Sprünge zwischen den Ids:
|
id |
name |
age |
|
50 |
Fred |
31 |
|
51 |
Harry |
25 |
|
100 |
Mike |
13 |
|
101 |
Frank |
46 |
|
102 |
Richard |
52 |
|
150 |
Ted |
31 |
|
151 |
Kyle |
29 |
|
152 |
Steve |
34 |
|
153 |
Michael |
42 |
Nach einigem Probieren stellte ich fest, dass die Sprünge stets nach dem Neustart der Anwendung auftraten. Das Problem konnte ich schließlich nach etwas Recherche im Internet ausmachen. Für den SequenceGenerator existiert ein Parameter allocationSize, welcher die definierte Sequenz um den angegeben Wert erhöht und anschließend diese Nummern vergibt. Erst wenn alle verbraucht sind, wird die Sequenz erneut aufgerufen. Der Nachteil dieser performanten Vorgehensweise ist, dass bei einem Neustart der Anwendung die zwischengspeicherten Werte verloren gehen und dadurch Lücken zwischen den Id’s entstehen. Standardmäßig ist diese Einstellung auf 50 gesetzt, was auch den Abstand in der Datenbank erklärt. Die nachfolgende Einstellung schafft also Abhilfe:
@Entity
@SequenceGenerator(name = "user_seq", sequenceName = "user_id_seq" allocationSize=1)
public class User implements Serializable { ... }
Nun wird zur Generierung des nächsten Primary-Keys immer die Datenbanksequenz bemüht, was die Performance zwar etwas mindert, jedoch bleiben die Lücken aus.
Links: http://www.galileocomputing.de/artikel/gp/artikelID-328
Wer sich als Java-Entwickler bereits ein wenig mit der Sprache Groovy beschäftigt hat, wird während der Projektarbeit sicher schon einige Verwendungsmöglichkeiten für Groovy-Sprachkonstrukte gefunden haben. In den meisten Fällen betrifft dies eher relativ triviale Funktionen, wie Operationen im Dateisystem oder das Auswerten von Variablen eines Typs. Da aber gerade in der Erstellung von Prototypen die Evaluierung von Kernprozessen im Vordergrund steht, kann die ergänzende Verwendung von Skriptsprachen á la Groovy etwas mehr Geschwindigkeit und Flexibilität in die Entwicklung bringen.
Um das eigene Projekt fit für den Groovy-Code zu machen, muss zunächst einmal die groovy-all-1.5.4.jar in den Build-Path aufgenommen werden. In der Regel findet sich diese Library im Verzeichnis der Groovy-Installation im Ordner \embeddable. Verwendet man Eclipse, so funktioniert das in etwa so ‘Project > Properties > Java Build Path > Libraries > Add External JARs…‘, wobei man es vorziehen sollte die Bibliothek in ein eigenes Projektverzeichnis zu kopieren (/lib) und von dort aus zu verlinken.
Theoretisch könnte es jetzt auch schon losgehen, aber ich denke die Wenigsten wollen beim Programmieren auf lieb gewonnene Features wie z.B Syntax Highlighting verzichten. Es gibt für die gängigsten IDE’s bereits Groovy-Plugins und ein paar Links dazu finden sich am Ende des Beitrages, wer mit Eclipse arbeitet kann mal einen Blick hierauf werfen. Die Plugins an sich sind zwar noch nicht 100% ausgereift und Autocompletion vermisst man stellenweise auch, aber hey – ich habe vor kurzem mit dem vi programmiert, auch das ging
Um einen schnellen Einstieg zu bekommen, soll ein kleines Beispielprojekt in Form eines Autohandels den Integrationsprozess veranschaulichen. Nachfolgend findet sich ein Listing zweier Interfaces die als Schnittstelle zur Groovy-Welt dienen.
package de.j2g.interfaces;
public interface ICar {
public String getMake();
public String getModel();
public String getType();
public double getPrice();
}
package de.j2g.interfaces;
import java.util.ArrayList;
public interface IGarage {
public ArrayList<ICar> getCars();
}
Nachdem die Struktur steht, geht es an die Implementierung der Groovy-Klassen. In einem separaten Package wird die Klasse Car.groovy erzeugt, welche das Interface ICar implementiert:
package de.j2g.groovy
import de.j2g.interfaces.ICar;
class Car implements ICar {
String make
String model
String type
double price
}
Nein, hier wurde nicht vergessen die Methoden des Interfaces hinzuschreiben. Da Groovy die Getter- und Setter-Methoden zur bei Kompilierung selbst hinzufügt, müssen lediglich die Instanzvariablen definiert werden. Als Nächstes kommt die Implementierung der Garage.groovy:
package de.j2g.groovy
import de.j2g.interfaces.IGarage;
import de.j2g.interfaces.ICar;
import java.util.ArrayList;class Garage implements IGarage {
public ArrayList<ICar> getCars() {def cars = new ArrayList<ICar>()
def car1 = new Car()
car1.make = “Ford”
car1.model = “SHELBY GT500″
car1.type = “Sports Car”
car1.price = 89000.00def car2 = new Car()
car2.make = “Ford”
car2.model = “Taurus”
car2.type = “Full-Size”
car2.price = 21000.00cars.add(car1)
cars.add(car2)return cars
}
}
Der Groovy-Teil steht, jetzt fehlt noch der Aufruf aus der Java-Welt. In der Main-Klasse werden als erstes die benötigten Groovy-Klassen über den mitgelieferten GroovyClassLoader geladen. Da nur die Methode getCars() der Garage.groovy angesprochen wird, muss auch nur diese Klasse explizit geladen werden. Alle abhängigen Klassen, in diesem Fall nur die Car.groovy, werden automatisch mitkompiliert und geladen. Ist die Klasse geladen, erzeugt man davon eine neue Instanz und castet diese auf das zuvor angelegte Interface IGarage.
package de.j2g.main;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;import java.util.ArrayList;
import org.codehaus.groovy.control.CompilationFailedException;
import de.j2g.interfaces.ICar;
import de.j2g.interfaces.IGarage;public class CarDealer {
public static void main(String[] args) {
System.out.print(“Loading groovy classes… “);
ClassLoader parent = CarDealer.class.getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);Class gGarage = null;
try {
gGarage = loader.loadClass(“de.j2g.groovy.Garage”);
} catch (CompilationFailedException e) {
…
} catch (ClassNotFoundException e) {
…
}GroovyObject goGarage = null;
try {
goGarage = (GroovyObject) gGarage.newInstance();
} catch (Exception e) {
…
}System.out.println(” done!”);
IGarage garage = (IGarage) goGarage;
ArrayList<ICar> cars = garage.getCars();for (ICar car : cars) {
System.out.print(“\n” + car.getMake() + ” “);
System.out.println(car.getModel());
System.out.println(car.getPrice() + ” €”);
System.out.println(car.getType());
}
}
}
Das Ausführen der Main-Class sollte folgende Ausgabe auf die Konsole bringen:
Loading groovy classes... done!
Ford SHELBY GT500
89000.0 €
Sports Car
Ford Taurus
21000.0 €
Full-Size
Die Funktionen im oberen Beispiel hätte man sicher auch ebenso schnell in Java umsetzen können. Etwas Programmieraufwand konnte aber schon bei der Car.groovy gespart werden – es war nicht nötig die Getter- und Setter zu definieren. Um nun die Effizienz der Skriptsprache noch etwas deutlicher zu machen, wird im Folgenden die Klasse Garage.groovy um einen FileReader erweitert:
cars.txt
Toyota;Camry;34000;Full-Size
VW;Golf;25000;Economy
Garage.groovy
public ArrayList<ICar> getCars() {
def cars = new ArrayList<ICar>()
def carFile = new File(‘D:/cars.txt’)carFile.eachLine {
line ->
def carArray = line.split(‘;’)
def car = new Car()
car.make = carArray[0]
car.model = carArray[1]
car.price = Integer.parseInt(carArray[2])
car.type = carArray[3]
cars.add(car)
}
return cars;
}
Einfacher geht es kaum noch, wenig Code zur Funktionsimplementierung und optionales Exception-Handling machen Groovy zum idealen Begleiter für die Agile Entwicklung!
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.
Auf der offiziellen Grails Homepage gibt es bereits ein sehr gutes How-To für die Integration von Grails in Eclipse. Da die Konfiguration aber nicht immer reibungslos verläuft und vor allem das Einbinden bestehender Grails Projekte in die IDE nach wie vor Probleme bereiten kann, sollen nachfolgend noch einmal die wichtigsten Schritte und Stolpersteine erläutert werden.
Basis für die Grails Entwicklung ist das (noch) nicht ganz ausgereifte Groovy-Plugin für Syntax Highlighting, Basic Code Completion und Kompilierung der Klassen. Die aktuelle Version gibt es zum Download unter
http://dist.codehaus.org/groovy/distributions/update/
Nach der Installation des Plugins muss eine Variable zum GRAILS_HOME gesetzt werden (Window -> Preferences -> Java -> Classpath Variables -> New…).
Anschließend sollte man noch bei Disable Groovy Compiler Generating Class Files (Window -> Preferences -> Groovy) einen Haken setzen.
Bestehendes Projekt
Falls das zu importierende Projekt bereits einmal in Eclipse bearbeitet wurde, dann lässt es sich leicht über Import... -> Existing Projects Into Workspace hinzufügen.
Neues Projekt
Ist das Projekt eben erst angelegt oder noch nie mit Eclipse bearbeitet worden, erkennt die IDE nicht ohne Weiteres das es sich um ein Groovy Projekt handelt und welchen Builder es einsetzen muss. Folgende Schritte führen zum Erfolg:
Sollte man trotzdem noch nicht in den Properties die Groovy Einstellungen sehen, hilft eventuell Add Groovy Nature im Kontextmenü anzuklicken. Sind in der Package Ansicht die entsprechenden Grails Source Folder nicht als Source Folder angezeigt, sondern lediglich als normale Ordner, muss man folgende Einträge in der Datei .classpath ergänzen:
...
<classpathentry kind="src" path="src/java"/>
<classpathentry kind="src" path="src/groovy"/>
<classpathentry kind="src" path="grails-app/conf"/>
<classpathentry kind="src" path="grails-app/controllers"/>
<classpathentry kind="src" path="grails-app/domain"/>
<classpathentry kind="src" path="grails-app/services"/>
<classpathentry kind="src" path="grails-app/taglib"/>
<classpathentry kind="src" path="test/integration"/>
<classpathentry kind="src" path="test/unit"/>
…
Bevor man den Server das erste Mal starten kann, muss folgender Befehl im Projekt-Root auf der Kommandozeile ausgeführt werden: grails dev package
Um nun den Server zu starten, klickt man auf Run As -> Open Run Dialog... und im erscheinenden Fenster auf Java Application -> <Projektname>. Dort sind bereits alle Einstellungen gesetzt und ein Klick auf Run startet den Jetty Server. Beenden kann man die Applikation indem man einfach den Prozess terminiert.
Zwar ist es nun problemlos möglich während der Entwicklung den Jetty Server zu kontrollieren, jedoch stellt uns das System keine Möglichkeit zur Verfügung, auch andere Targets aufzurufen. Der Aufwand, beim Erstellen und Generieren von Domains, Controllern und initialen Views die Konsole zu bemühen, bleibt also bestehen. Um dem Problem zu begegnen, nutzen wir die Funktionen des External Tools Dialog. Im nachfolgenden Beispiel wird eine Domain-Class generiert:
Run -> External Tools -> Open External Tools Dialog... grails create-domainclassC:\Development\Application\grails-1.0-RC4\bin\grails.bat${project_loc}create-domainclass ${string_prompt:the new domain class} Das erste Argument (create-domainclass) ist der Aufruf des Targets, das zweite Argument erzeugt ein Popup indem man die Domain-Klasse angibt. Ein zusätzliches Argument in der Variable ${string_prompt} manipuliert den Abfragedialog, sodass der Nutzer weiss was er angeben muss.
Falls beim Ausführen eines Targets der Fehler Error starting Sun’s native2ascii ausgegeben wird, so kann dies zwei Ursachen haben:
grails dev package auf der Konsole ausgeführt hat
Mithilfe des Site-Plugins ist es möglich ohne großen Aufwand eine Projekt Homepage mit allen wichtigen Informationen zu erstellen. Neben generierten Code Audits, Unit-Test Reports oder SourceCode Reviews können auch eigene Inhalte problemlos mit eingebunden werden, was die Projektsite zu einer universellen Informationsplattform für Entwickler macht.
Leider gibt es bei Projekten mit mehrenen Modulen häufig Probleme in der Verlinkung der generierten Html-Seiten. Da die Super-Pom zwar alle Sub-Modules kennt aber nicht umgekehrt, sind die Module auf der Projekt-Page auch nur in eine Richtung verlinkt. Möchte man nun zu einem Parent-Modul navigieren, muss man entweder die Browser-Hierarchie bemühen oder die Startseite erneut aufrufen.
Die Lösung für dieses Problem ist die site.xml. In dieser Datei kann das Aussehen und die Navigation der Projektseite individuell angepasst werden. Den generellen Aufbau der site.xml findet man hier. Damit nun die Verlinkung funktioniert muss in jedem Modul im Verzeichnis src/ ein Ordner site, zusammen mit der Datei site.xml angelegt werden. Eine leere site.xml erzeugt eine blank-page, daher muss für die Standardfunktionalität folgender Content eingebunden werden:
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Maven">
<body>
<menu ref="parent"/> <!-- Erzeugt Link zum Parent-Modul-->
<menu ref="modules"/>
<menu ref="reports"/>
</body>
</project>
Mit mvn site-deploy werden nun die zugehörigen Projektseiten erzeugt. Ein Aufruf der Startseite überrascht noch nicht, es werden wie gewohnt alle Module angezeigt.

Auf den Seiten der Sub-Modules hingegen, erscheint nun ein Link zum jeweiligen Parent-Modul oben in der Navigationsleiste.

Die direkte Verlinkung erfüllt zwar ihren Zweck, ist aber gerade bei mehreren Hierarchiestufen nicht gerade praktisch. Besser wäre da doch ein Krümelpfad mit dem man beliebig zwischen den Seiten wechseln kann. Wieder hilft dabei die site.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Maven">
<body>
<breadcrumbs>
<item name="<ModulName>" href="index.html" mce_href="index.html"/>
</breadcrumbs>
<menu ref="modules"/>
<menu ref="reports"/>
</body>
</project>
In dem Item-Tag wird dabei nur der Name des aktuellen Moduls eingetragen und als href die index.html. Maven baut dann automatisch den Pfad synchron zur Modulhierarchie zusammen. Die nachfolgenden Abbildungen zeigen eine Projektseite mit drei Hierarchieebenen.



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 [...]