Groovy als Web-Backend: Unterschied zwischen den Versionen

Aus Jonas Notizen Webseite
Zur Navigation springen Zur Suche springen
(Grails-Skripte und Grails-Befehle Kapitel geschrieben. "Gradle und Grails"-Seitentitel (inkl Link) mit TODO message eingefügt)
K (→‎Die Web-Ebene: Angefangen Texte zu verbessern / Sachen hinzuzufügen etc. Aufgehört bei "Aktionen aneinander-reihen")
Zeile 630: Zeile 630:
 
Jedes Skript erbt (unter anderem)
 
Jedes Skript erbt (unter anderem)
  
* von [https://docs.grails.org/3.3.9/ref/Command Line/create-script.html GroovyScriptCommand], welches eine API für viele nützliche Aufgaben bereitstellt, sowie  
+
*von [https://docs.grails.org/3.3.9/ref/Command Line/create-script.html GroovyScriptCommand], welches eine API für viele nützliche Aufgaben bereitstellt, sowie
* von [https://docs.grails.org/3.3.9/api/org/grails/cli/profile/commands/templates/TemplateRenderer.html TemplateRenderer], welches die zur Code-Generierung genutzten <code>render</code>/<code>template</code>-Methoden zur Verfügung stellt. (Siehe Beispiel unten)
+
*von [https://docs.grails.org/3.3.9/api/org/grails/cli/profile/commands/templates/TemplateRenderer.html TemplateRenderer], welches die zur Code-Generierung genutzten <code>render</code>/<code>template</code>-Methoden zur Verfügung stellt. (Siehe Beispiel unten)
 +
 
  
  
Zeile 662: Zeile 663:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== [https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_gradle Gradle-Tasks aufrufen] ===
+
===[https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_gradle Gradle-Tasks aufrufen]===
 
Mithilfe der injezierten <code>gradle</code>-Variable können auch Gradle-Tasks getriggert werden:<syntaxhighlight lang="groovy">
 
Mithilfe der injezierten <code>gradle</code>-Variable können auch Gradle-Tasks getriggert werden:<syntaxhighlight lang="groovy">
 
gradle.compileGroovy()
 
gradle.compileGroovy()
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== [https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_ant Ant-Tasks aufrufen] ===
+
===[https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_ant Ant-Tasks aufrufen]===
 
Man kann auch Ant-Tasks aus Skripten heraus aufrufen, was beim schreiben von Codegenerierung und Automatisierungsaufgaben sehr nützlich sein kann:
 
Man kann auch Ant-Tasks aus Skripten heraus aufrufen, was beim schreiben von Codegenerierung und Automatisierungsaufgaben sehr nützlich sein kann:
  
Zeile 712: Zeile 713:
 
</syntaxhighlight><br />
 
</syntaxhighlight><br />
  
= [https://docs.grails.org/3.3.9/guide/commandLine.html#creatingCustomCommands Grails Befehle] =
+
=[https://docs.grails.org/3.3.9/guide/commandLine.html#creatingCustomCommands Grails Befehle]=
  
== Unterschied Befehl und Skript ==
+
==Unterschied Befehl und Skript==
 
Im Gegensatz zu Skripten bewirken Befehle den Start der Grails-Umgebung, d.H. man hat den vollen Zugriff auf den Anwendungskontext und die Laufzeit.
 
Im Gegensatz zu Skripten bewirken Befehle den Start der Grails-Umgebung, d.H. man hat den vollen Zugriff auf den Anwendungskontext und die Laufzeit.
  
=== Änderungen in Grails 3.2 ===
+
===Änderungen in Grails 3.2===
 
Seit Grails 3.2.0 haben Befehle ähnliche Fähigkeiten wie Skripte in Bezug auf das Abrufen von Argumenten, die Erzeugung von Vorlagen, den Dateizugriff und die Modellerstellung.
 
Seit Grails 3.2.0 haben Befehle ähnliche Fähigkeiten wie Skripte in Bezug auf das Abrufen von Argumenten, die Erzeugung von Vorlagen, den Dateizugriff und die Modellerstellung.
  
Zeile 726: Zeile 727:
 
</syntaxhighlight><br />
 
</syntaxhighlight><br />
  
== Befehle erstellen ==
+
==Befehle erstellen==
 
Ähnlich wie bei Skripten kann man mit dem Befehl <code>$ grails create-command NAME</code> das Skelett eines Skriptes unter <code>src/main/commands/</code> erstellen lassen.
 
Ähnlich wie bei Skripten kann man mit dem Befehl <code>$ grails create-command NAME</code> das Skelett eines Skriptes unter <code>src/main/commands/</code> erstellen lassen.
  
== Befehl ausführen ==
+
==Befehl ausführen==
 
Selbst-geschriebene Befehle können  
 
Selbst-geschriebene Befehle können  
  
* mithilfe von <code>$ grails run-command NAME</code> oder
+
*mithilfe von <code>$ grails run-command NAME</code> oder
* mithilfe des Gradle-Tasks "runCommand" <code>gradle runCommand -Pargs="NAME"</code>  
+
*mithilfe des Gradle-Tasks "runCommand" <code>gradle runCommand -Pargs="NAME"</code>  
** Wenn der Grails-Server ein Unterprojekt ist (z.B. in einem Projekt, das mit Angular erstellt wurde), kann der Unterprojekt-Befehl immer noch aus dem Gradle-Wrapper im übergeordneten Projekt aufgerufen werden: <code>./gradlew server:runCommand -Pargs="my-example"</code>
+
**Wenn der Grails-Server ein Unterprojekt ist (z.B. in einem Projekt, das mit Angular erstellt wurde), kann der Unterprojekt-Befehl immer noch aus dem Gradle-Wrapper im übergeordneten Projekt aufgerufen werden: <code>./gradlew server:runCommand -Pargs="my-example"</code>
  
 
aufgerufen werden.
 
aufgerufen werden.
  
= [https://docs.grails.org/3.3.9/guide/commandLine.html#gradleBuild Gradle und Grails] =
+
=[https://docs.grails.org/3.3.9/guide/commandLine.html#gradleBuild Gradle und Grails]=
 
TODO
 
TODO
  
Zeile 748: Zeile 749:
  
  
=[https://docs.grails.org/3.3.10/guide/single.html#theWebLayer Die Web-Ebene]=
+
=[https://docs.grails.org/3.3.9/guide/theWebLayer.html Die Web-Ebene]=
==Controller==
+
<br />
Ein [http://docs.grails.org/latest/guide/theWebLayer.html#controllers Controller] verarbeitet Anforderungen und erstellt oder bereitet die Antwort vor.  
+
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#controllers Controller]==
Ein Controller kann die Antwort direkt generieren oder an eine Ansicht (View, .gsp) delegieren.
+
Ein Controller verarbeitet Anforderungen und erstellt oder bereitet die Antwort vor. Ein Controller kann die Antwort direkt generieren oder an eine Ansicht (View, <code>.gsp</code>) delegieren.
  
 
===Controller erstellen===
 
===Controller erstellen===
Mit dem Befehl <code>$ grails create-controller (PACKET.)KLASSEN-NAME </code> erstellt man das Skelett eines Controllers,  
+
Alle Controller befinden sich im Stammbaum unter <code>grails-app/controllers/APP_NAME</code>. Die einzige Voraussetzung für eine Klasse innerhalb dieser Hierarchie ist es, mit <code>Controller.groovy</code> zu enden.
welches in <code>grails-app/controllers/APP-NAME/DEIN/PACKET/KLASSEN-NAME.groovy</code> gespeichert wird.  
 
''(Dieser Befehl ist nur zur vereinfachten Erstellung, man kann es auch manuell oder mit einer IDE machen)''
 
  
===Aktionen===
+
 
Die Standard [http://docs.grails.org/latest/guide/single.html#urlmappings URL-Mapping]-Konfiguration versichert dass der Name des Controllers  
+
Mit dem Befehl <code>$ grails create-controller (PAKET.)KLASSEN_NAME </code> erstellt man das Skelett eines Controllers, welches dann entsprechend unter <code>grails-app/controllers/APP_NAME/PAKET/KLASSEN_NAME.groovy</code> gespeichert wird.
sowie jede Methode zum entsprechendem URI-Pfad gebunden wird.<br>
+
 
Das folgende Beispiel ist hiermit unter ".../mahlzeit/index" erreichbar:
+
* ''Dieser Befehl ist nur zur vereinfachten Generierung gedacht, man kann es auch manuell oder mit einer IDE machen.''
 +
 
 +
<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_creating_actions Aktionen]===
 +
Die Standard [http://docs.grails.org/latest/guide/single.html#urlmappings URL-Mapping]-Konfiguration versichert, dass der Name des Controllers sowie jede Methode zum entsprechendem URI-Pfad gebunden wird.<br>Das folgende Beispiel ist hiermit unter <code>"localhost:8080/mahlzeit/index"</code> erreichbar:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
package myapp
 
package myapp
  
 
class MahlzeitController {
 
class MahlzeitController {
 +
    // Standard-Aktion
 
     def index() { }
 
     def index() { }
 +
   
 +
    // Aktion "/list"
 +
    def list {
 +
        //..
 +
        // Controller-Logik
 +
        //..
 +
        return model
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.  
 
Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.  
  
====Standard-Aktion====
+
====[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_the_default_action Standard-Aktion]====
Wenn der Nutzer keine bestimmte Aktion in seiner Anfrage stehen hat ''(Also nur den Namen des Controllers, wie "/mahlzeit/" anstatt zB. "/mahlzeit/login") ''
+
Wenn der Nutzer keine bestimmte Aktion in seiner Anfrage stehen hat ''(Also zB. nur den Namen des Controllers, wie "<code>/mahlzeit/</code>" anstatt zB. "<code>/mahlzeit/login</code>") ''versucht Grails eine Standard-Aktion ausfindig zu machen, welche sich dieser Anfrage zur Verfügung stellt.  
versucht Grails eine Standard-Aktion ausfindig zu machen, welche diese Anfrage annimmt.  
 
  
 
*Wenn es nur eine Aktion ''(aka. Methode)'' gibt, wird diese als Standard-Aktion anerkannt
 
*Wenn es nur eine Aktion ''(aka. Methode)'' gibt, wird diese als Standard-Aktion anerkannt
Zeile 780: Zeile 792:
 
Alternativ kann man auch eine eigenen Standard setzten, indem man <code>static defaultAction = "DEINE-METHODE"</code> in den Quellcode des Controllers einfügt.
 
Alternativ kann man auch eine eigenen Standard setzten, indem man <code>static defaultAction = "DEINE-METHODE"</code> in den Quellcode des Controllers einfügt.
  
 +
<br />
 
==Scope-Variablen==
 
==Scope-Variablen==
 
"Scope-Variablen" sind HashMap ähnliche Strukturen, in welche man Variablen speichern kann. Für Controller stehen folgende Scope-Variablen zur Verfügung:
 
"Scope-Variablen" sind HashMap ähnliche Strukturen, in welche man Variablen speichern kann. Für Controller stehen folgende Scope-Variablen zur Verfügung:
  
 
===[http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]: Statisch, für alle gleich===
 
===[http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]: Statisch, für alle gleich===
Dieser Scope lässt uns Daten speichern, welche '''über die gesamte Web-App gleich statisch erreichbar sind'''.<br>
+
Dieser Scope lässt uns Daten speichern, welche '''über die gesamte Web-App gleich statisch erreichbar sind (Applikationskontext).''' <br>Das [http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]-Objekt ist eine Instanz vom Standardmäßigen Java(EE)-Objekt [https://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html ServletContext].
Das [http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]-Objekt ist eine Instanz  
 
vom Standardmäßigen Java(EE)-Objekt [https://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html ServletContext].
 
  
 
Es ist nützlich um:
 
Es ist nützlich um:
Zeile 795: Zeile 806:
  
 
===[http://docs.grails.org/latest/ref/Controllers/session.html session]: Für jede Sitzung anders===
 
===[http://docs.grails.org/latest/ref/Controllers/session.html session]: Für jede Sitzung anders===
Das [http://docs.grails.org/latest/ref/Controllers/session.html session]-Objekt ist eine Instanz  
+
Das [http://docs.grails.org/latest/ref/Controllers/session.html session]-Objekt ist eine Instanz der Klasse [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSession.html HttpSession] der Java(EE) Servlet-API.
von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSession.html] der Java(EE) Servlet-API.
 
  
 
Es ist nützlich um '''Attribute der derzeitigen Sitzung eines Klientens zu speichern, wie zB. der Login (Name/Passwort).'''
 
Es ist nützlich um '''Attribute der derzeitigen Sitzung eines Klientens zu speichern, wie zB. der Login (Name/Passwort).'''
  
 
===[http://docs.grails.org/latest/ref/Servlet%20API/request.html request]: Mitgesendete Informationen===
 
===[http://docs.grails.org/latest/ref/Servlet%20API/request.html request]: Mitgesendete Informationen===
Das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt ist eine Instanz  
+
Das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt ist eine Instanz von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest] der Java(EE) Servlet API.
von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest] der Java(EE) Servlet API.
 
  
 
Es ist nützlich um:
 
Es ist nützlich um:
Zeile 810: Zeile 819:
 
*Informationen des aktuellen Klienten zu erhalten
 
*Informationen des aktuellen Klienten zu erhalten
  
Grails fügt einige zusätzliche Funktionen zum [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt hinzu,  
+
Grails fügt einige zusätzliche Funktionen zum [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt hinzu, ''die das standardmäßige [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest]-Objekt nicht hat,'' hinzu:
''die das standardmäßige [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest]-Objekt nicht hat,'' hinzu:
 
  
 
*<code>XML</code> - Eine Instanz der [http://groovy.codehaus.org/api/groovy/util/slurpersupport/GPathResult.html GPathResult] welches erlaubt einkommende XML-Anfragen zu verarbeiten (Parsen) - Nützlich für REST
 
*<code>XML</code> - Eine Instanz der [http://groovy.codehaus.org/api/groovy/util/slurpersupport/GPathResult.html GPathResult] welches erlaubt einkommende XML-Anfragen zu verarbeiten (Parsen) - Nützlich für REST
Zeile 836: Zeile 844:
  
 
===([http://docs.grails.org/latest/ref/Controllers/params.html params]): (Veränderbare,) Mitgesendete CGI Informationen===
 
===([http://docs.grails.org/latest/ref/Controllers/params.html params]): (Veränderbare,) Mitgesendete CGI Informationen===
Eine veränderbare, mehrdimensionale HashMap von Anforderungsparametern (CGI).<br>
+
Eine veränderbare, mehrdimensionale HashMap aller Anforderungsparametern (CGI).<br>Obwohl das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt auch Methoden zum lesen der Anforderungsparametern verfügt, ist der <code>params</code>-Scope manchmal nützlich für die [http://docs.grails.org/latest/guide/theWebLayer.html#dataBinding Daten-Bindung an Objekte].
Obwohl das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt auch Methoden zum lesen der Anforderungsparametern enthält,  
 
ist diese manchmal nützlich für [http://docs.grails.org/latest/guide/theWebLayer.html#dataBinding Daten-Bindung an Objekte].
 
  
Nehmen wir an wir haben folgende Anfrage: <code>hello?foo=bar</code>. In unserer Controll-Action "hello" können wir den Wert von <code>foo</code> nun ganz einfach
+
Beispiel:
ausgeben lassen:
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
println params.foo
+
def save() {
 +
    def book = new Book(params) // bind request parameters onto properties of book
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
===[http://docs.grails.org/latest/ref/Controllers/flash.html flash]: Speicher zwischen 2 Anfragen===
 
===[http://docs.grails.org/latest/ref/Controllers/flash.html flash]: Speicher zwischen 2 Anfragen===
Eine temporäre Speicherzuordnung, in der Objekte innerhalb der Sitzung für die nächste Anforderung und nur für die nächste Anforderung gespeichert werden.  
+
[[Datei:PostRedirectGet DoubleSubmitSolution.png|mini|Diagramm von Post/Redirect/Get. Quelle: [[commons:File:PostRedirectGet_DoubleSubmitSolution.png|Wikimedia]]]]
Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.
+
Eine temporäre Speicherzuordnung, in der Objekte innerhalb der Sitzung für die nächste Anforderung, und auch nur für die nächste Anforderung, gespeichert werden. Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.  
 
 
Mit diesem Muster jk Sie HTTP-Weiterleitungen verwenden (Nützlich für [http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost Redirect after Post])
 
und Werte beibehalten, die vom Flash-Objekt abgerufen werden können. Beispiel:
 
<syntaxhighlight lang="groovy">
 
class BookController {
 
  
     def index() {
+
Dies ist z.B. nützlich, um eine Nachricht direkt vor der Umleitung zu setzen: (Siehe [http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost Redirect after Post]-Konzept/Problemstellung)<syntaxhighlight lang="groovy">
         flash.message = "Welcome!"
+
def delete() {
         redirect(action: 'home')
+
     def b = Book.get(params.id)
 +
    if (!b) {
 +
         flash.message = "User not found for id ${params.id}"
 +
         redirect(action:list)
 
     }
 
     }
 +
    ... // remaining code
 +
}
  
    def home() {}
+
def list(){
 +
    // use "flash.message" in the rendered gsp-template or something like that
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight>Bemerke: Die Namen und Typen die man dem <code>flash</code>-Objekt setzt können willkürlich sein.
==Scope eines Controllers==
+
<br />
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_scoped_controllers Scope eines Controllers]==
 
Neu erstellte Controller haben den Standard-Scope-Wert "singleton". Verfügbare Controller-Scopes sind:
 
Neu erstellte Controller haben den Standard-Scope-Wert "singleton". Verfügbare Controller-Scopes sind:
  
Zeile 876: Zeile 885:
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Man kann den Scope eines Individuellen Controllers auch manuell ändern, indem man <code>static scope = "DEIN-SCOPE"</code> in den Quellcode des Controllers einfügt.
 
Man kann den Scope eines Individuellen Controllers auch manuell ändern, indem man <code>static scope = "DEIN-SCOPE"</code> in den Quellcode des Controllers einfügt.
 +
  
 
==[https://docs.grails.org/3.3.10/guide/single.html#modelsAndViews Model und Ansicht (Model and View)]==
 
==[https://docs.grails.org/3.3.10/guide/single.html#modelsAndViews Model und Ansicht (Model and View)]==
 
Ein "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.
 
Ein "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.
  
*Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie von zB. einer <code>.gsp</code>-Datei erreichbar sind.<br>
+
*Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie innerhalb einer View (z.B. einer <code>.gsp</code>-Datei) erreichbar sind.
 +
*Zu beachten ist, dass bestimmte Variablennamen im Modell nicht verwendet werden können:
 +
**<code>attributes</code>
 +
**<code>application</code>
  
Es gibt einige Wege um ein Model zu übergeben:
+
 
 +
Es gibt einige Wege um ein Model zu übergeben/zu erzeugen:
  
 
*Explizit: Wenn seine Methode gleich wie die View-Datei heißt, übergibt man das Model einfach wie beim returnen mit folgendem Syntax: <code>[Var1: Data1, Var2: Data2, ...]</code>
 
*Explizit: Wenn seine Methode gleich wie die View-Datei heißt, übergibt man das Model einfach wie beim returnen mit folgendem Syntax: <code>[Var1: Data1, Var2: Data2, ...]</code>
Zeile 891: Zeile 905:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Eine erweiterte/bessere Weiße wäre es:
+
* Ein fortgeschrittenerer Ansatz ist die Rückgabe einer Instanz der Spring [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/ModelAndView.html ModelAndView]-Klasse zu benutzen:
 
 
*Springs [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/ModelAndView.html ModelAndView]
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
import org.springframework.web.servlet.ModelAndView
 
import org.springframework.web.servlet.ModelAndView
Zeile 905: Zeile 917:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
zu benutzen.
+
<br />
 
 
Zu beachten ist, dass bestimmte Variablennamen in Ihrem Modell nicht verwendet werden können:
 
  
*<code>attributes</code>
+
===View-Datei selber selektieren mit der render()-Methode===
*<code>application</code>
+
In den vorherhigen 2 Beispielen haben wir nirgendwo angegeben an welche [http://docs.grails.org/latest/guide/single.html#gsp View] die Map-Daten übergeben werden soll.<br>Dies liegt an Grails Konventionen. Im folgenden Beispiel würde Grails unter <code>grails-app/views/book/show.gsp</code> nachsuchen: ''(Nach dem Schema <code>grails-app/views/CONTROLLER/AKTION.gsp</code>)''
 
 
===Views selber selektieren mit der render()-Methode===
 
In den vorherhigen 2 Beispielen haben wir nirgendwo angegeben an welche [http://docs.grails.org/latest/guide/single.html#gsp view] die Map-Daten übergeben werden soll.<br>
 
Dies liegt an Grails Konventionen. Im folgenden Beispiel würde Grails unter <code>grails-app/views/book/show.gsp</code> nachsuchen: ''(Nach dem Schema <code>grails-app/views/CONTROLLER/AKTION.gsp</code>)''
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
class BookController {
 
class BookController {
Zeile 933: Zeile 939:
 
''(Anmerkung: Wenn Grails keine <code>.gsp</code>-Datei finden kann, sucht es auch nach <code>.jsp</code>-Dateien.)''
 
''(Anmerkung: Wenn Grails keine <code>.gsp</code>-Datei finden kann, sucht es auch nach <code>.jsp</code>-Dateien.)''
  
===Views für Namespaced-Controller selektieren===
+
<br />
Wenn ein Controller einen eigenen [http://docs.grails.org/latest/guide/single.html#namespacedControllers namespace] gesetzt hat (In diesem Beispiel '<code>business</code>'), schaut Grails
+
===Selektions-Verfahren von Controllern die einem speziellen Namensraum angehören===
 +
Wenn ein Controller einen eigenen [http://docs.grails.org/latest/guide/single.html#namespacedControllers Namensraum] gesetzt hat (In diesem Beispiel '<code>business</code>'), schaut Grails
  
 
*zuerst nach ob die View-Datei unter <code>grails-app/views/business/...</code> zu finden ist
 
*zuerst nach ob die View-Datei unter <code>grails-app/views/business/...</code> zu finden ist
Zeile 969: Zeile 976:
 
     }
 
     }
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight><br />
 
+
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#redirectsAndChaining Weiterleitungen]==
==[https://docs.grails.org/3.3.10/guide/single.html#redirectsAndChaining Redirects]==
+
Aktionen können mithilfe der Controller-Methode <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umgeleitet werden. Beispiel:<syntaxhighlight lang="groovy">
Aktionen können mithilfe der Controller-Methode <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umgeleitet werden.
 
Beispiel:
 
<syntaxhighlight lang="groovy">
 
 
class OverviewController {
 
class OverviewController {
  
Zeile 988: Zeile 992:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
  
 
Auf welche Seite <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umleitet, kann man Grails auf mehrere Weisen mitteilen:
 
Auf welche Seite <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umleitet, kann man Grails auf mehrere Weisen mitteilen:
Zeile 1.009: Zeile 1.014:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Parameter können von einer zu der anderen Aktion mithilfe des <code>params</code>-Arguments übergeben werden:
+
* Domain-Klassen-Instanz: (Grails konstruiert einen Link unter Verwendung der Domänenklassen-ID, falls vorhanden.)
 +
<syntaxhighlight lang="groovy">
 +
// Redirect to the domain instance
 +
Book book = ... // obtain a domain instance
 +
redirect book
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Parameter können von einer zu der anderen Aktion mithilfe des <code>params</code>-Arguments übergeben werden.
 +
 
 +
Diese Daten befinden sich dann auch im <code>params</code>-Scope-Objekt der durch den Redirects resultierenden Anfrage der Aktion <code>myaction</code>.
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
redirect(action: 'myaction', params: [myparam: "myvalue"])
 
redirect(action: 'myaction', params: [myparam: "myvalue"])
 
</syntaxhighlight>
 
</syntaxhighlight>
Diese werden dann in den Controller-Scope <code>[http://docs.grails.org/latest/ref/Controllers/params.html params]</code> (Siehe oben) gespeichert.
+
<syntaxhighlight lang="groovy">
 +
redirect(action: 'myaction', params: params) // Since both the "params"-Parameter and the "params"-Controller-Scope are Map's, this is possible
 +
</syntaxhighlight>
 +
 
  
 +
Mithilfe von <code>fragment</code> kann der Hash-Abteil der URL angegeben werden: (Im Standard URL-Mapping würde die folgende Methode in eine Weiterleitung auf <code>/myapp/test/show#profile</code> resultieren)<syntaxhighlight lang="groovy">
 +
redirect(controller: "test", action: "show", fragment: "profile")
 +
</syntaxhighlight><br />
 
===Aktionen aneinander-reihen===
 
===Aktionen aneinander-reihen===
 
<code>[http://docs.grails.org/3.1.1/ref/Controllers/chain.html chain()]</code> fungiert ähnlich wie <code>redirect()</code>,  
 
<code>[http://docs.grails.org/3.1.1/ref/Controllers/chain.html chain()]</code> fungiert ähnlich wie <code>redirect()</code>,  
Zeile 1.046: Zeile 1.068:
 
chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
 
chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
  
 
==Die [https://docs.grails.org/latest/ref/Controllers/render.html render()]-Methode==
 
==Die [https://docs.grails.org/latest/ref/Controllers/render.html render()]-Methode==

Version vom 22. Mai 2020, 01:13 Uhr

Inhaltsverzeichnis

Einführung

Quellen


Über Grails

Grails Logo

Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks. Diese sind aber meist komplizierter als sie sein müssten, und folgen meist nicht dem DRY-Prinzip ("Don't Repeat yourself").


Dynamische Frameworks wie Django (Python) und Rails (Ruby) haben neue moderne Einblicke gesetzt, wie man über Web-Applikationen denken sollte.

Grails baut auf diesen Konzepten auf und reduziert die Komplexität beim Erstellen von Webanwendungen auf der Java-Plattform erheblich. Das Besondere daran ist jedoch, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.

Zu den hervor-zu-hebenden Features zählen:

All dies wird durch die Leistungsfähigkeit der Groovy-Sprache und den umfassenden Einsatz domänenspezifischer Sprachen (DSLs) wesentlich vereinfacht.


Installation von Grails 3.3

Installations-Anforderungen

Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert [1].


Automatische Grails-Installation mithilfe des SDKMAN's-Projekts

SDKMAN! ist ein Werkzeug zur Verwaltung paralleler Versionen mehrerer Software Development Kits auf den meisten Unix-basierten Systemen. Es bietet eine bequeme Befehlszeilenschnittstelle (CLI) und API zum Installieren, Wechseln, Entfernen und Auflisten von Kandidaten.

Nachdem man SDKMAN! mithilfe der offiziellen Anleitung erfolgreich installiert hat kann man mit dem folgenden Befehl Grails auf sein System installieren:

$ sdk install grails 3.3.9

Falls dies die erste Installation von Grails mithilfe des SDKMAN! ist, wird diese Version direkt als Standard in allen Umgebungen gesetzt und auch direkt angewendet.

App erstellen

$ grails create-app APP-NAME erstellt im derzeitigen Verzeichnis die Grundlage für die neue App.

Befehle ausführen

Wenn man jetzt in den erstellten Projekt-Ordner wechselt ($ cd APP-NAME) gibt es 2 Möglichkeiten, Grails-Befehle auszuführen:

  • $ grails BEFEHL
    • Nachteil: Für jeden Befehl muss eine neue JVM-Instanz erstellt werden (= Bei jedem Befehl muss man eine gewisse Zeit extra warten)
    • Nachteil: Keine Auto-Vervollständigung mithilfe der Tabulator-Taste
  • Mithilfe von $ grails in die Interaktive-Konsole wechseln, und dort seine Befehle (Ohne grails vorne dran) ausführen
    • Vorteil: Es wird nur einmal die JVM gestartet / benötigte Projekt und Grails-Daten geladen (= Weniger Ladezeit)
    • Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste


D.h.: Wenn man in einer Dokumentation grails <<command>> kann man:

  • Direkt grails <<command>> in seiner Shell eingeben
  • Zuerst mithilfe von grails in die Interaktive Grails-Shell eindringen und danach den <<command>> eingeben.


Grails-Integrationen für diverse IDE's

IntelliJ IDEA Logo

IntelliJ-IDEA

IntelliJ IDEA ist eine ausgezeichnete IDE für die Entwicklung von Grails 3.0. Sie ist in 2 Editionen erhältlich:

  • der kostenlosen Community-Edition und
  • der kostenpflichtigen "Ultimate" Edition. (Tipp: Es gibt auch kostenlose Studenten-Pakete)

Die Community Edition kann für die meisten Dinge verwendet werden, obwohl GSP-Syntax-Highlighting nur ein Teil der "Ultimate" Edition ist.

TextMate, Sublime, VIM, ...

Siehe offizielle Dokumentation

Konventionen über Konfiguration

"Konventionen über Konfiguration" bedeutet, dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird.
Je nach Speicherort einer Klasse erhält diese spezifische Variablen/Methoden "injeziert". (Beispiel: constraints, hasMany).

Da der Ort einer Klasse entscheidet, wofür/wie sie von Grails genutzt wird, sollte man sich mit dem Aufbau einer Grails-Applikation bekannt werden lassen:

App starten

Grails-Apps können durch den eingebauten Tomcat-Server mit dem Befehl $ grails run-app "von Grund auf" gestartet werden.

  • Standard-Port dieses Web-Servers ist 8080, kann aber durch den Parameter -port=[PORT] abgeändert werden.
  • Unter welcher URL die App erreichbar ist wird in der Konsole angezeigt. (Standardmäßig http://localhost:8080/APP-NAME/ - kann aber auch durch die Laufzeit-Konfigurationsvariable server.contextPath geändert werden.)

App testen

Alle $ grails create-* Befehle generieren eine Unit oder Integrations-Testing Klasse im src/test/groovy Ordner.

Wenn man nun die Logik hinter den Tests hinzugefügt hat, kann man diese mit $ grails test-app starten.

App bereitstellen

Grail-Apps können auf mehrere Weisen bereitgestellt werden.
Wenn man einen Traditionellen Container (Wie Apache's Tomcat, Jetty, etc.) benutzt, kann man ganz einfach ein sog. "Web Application Archive" (.war-Datei) erstellen, welches alle App-Daten in eine Archiv-ähnliche Datei zusammenfasst. (Vergleichbar mit einem .jar-Archiv)

Grails und Tomcat

Grails bindet standardmäßig einen Tomcat-8 Container mit jeder WAR-Datei ein.
Da dies aber zu Problemen führen kann, wenn der Server bereits einen aktiven Tomcat-Container mit einer anderen Version besitzt, kann man diese Abhängigkeit (Dependency) innerhalb der build.gradle-Datei von compile auf provided umstellen:

provided "org.springframework.boot:spring-boot-starter-tomcat"

Die Tomcat-Version kann man ebenfalls unter build.gradle innerhalb der dependencies {}-Sektion wie folgt auf zB. Tomcat-7 ändern:

ext['tomcat.version'] = '7.0.59'

WAR-Datei erstellen und starten

Mit dem Grails-Befehl $ grails war baut Grails die ".war-Version" seiner App im Verzeichnis build/libs des Projekts auf.

Mit dem Java-Befehl java -Dgrails.env=prod -jar [DEINE-WAR-DATEI].war kann man nun ganz einfach seine erstellte .war-Datei ausführen.
Beispiel: java -Dgrails.env=prod -jar build/libs/mywar-0.1.war

Unterstützte Java EE-Container

Grails 3-3 läuft auf jeden Container der die Java Servlet API in der Version 3.0 (oder darüber) unterstützt. Darunter gelten:

  • Tomcat 7
  • GlassFish 3
  • Resin 4
  • JBOSS 6
  • Jetty 8
  • Oracle Weblogic 12c
  • IBM WebSphere 8.0


Automatisches generieren von Artifakten für eine Domänen-Klasse (Scaffolding)

Mit Scaffolding kann man einige grundlegende CRUD (Create, Read, Update, Delete)-Schnittstellen für eine Domänen-Klasse generieren, darunter:

  • Die benötigten Views (.gsp)
  • Die dazugehörigen Controller-Klassen für CRUD-Operationen
  • Die dazugehörigen Unit-Tests

Alle oben genannten Skelette können mithilfe von $ grails generate-all (PACKET.)KLASSEN-NAME ganz einfach erstellt werden.



Konfiguration

Es mag merkwürdig erscheinen, dass man dieses Thema in einem Framework angeht, welches sich dem "Konvention-über-Konfiguration"-Leitfaden widmet.

Mit den Standardeinstellungen von Grails kann man tatsächlich eine Anwendung entwickeln, ohne irgendeine Konfiguration vorzunehmen (wie der schnell-Einstieg zeigte), aber es ist wichtig zu lernen, wo und wie man die Konventionen bei Bedarf außer Kraft setzen kann.


Möchte man dann aber zB. seine eigene MySQL-Verbindung einrichten, kann man dies mithilfe simpler Konfigurationsdateien einstellen.

Grundlegende Konfiguration

Grails Konfigurations-Dateien sind in 2 Sektionen aufgeteilt:

  • Build (build.gradle)
  • Runtime (grails-app/conf/application.yml)

Standard-Variablen

Für die Konfigurations-Dateien stehen folgende Variablen zur Verfügung:

Variable Beschreibung
userHome Ordner-Pfad beim starten des Servers
grailsHome Ordner-Pfad indem Grails installiert ist. (WENN GRAILS_HOME gesetzt wurde, wird diese genutzt)
appName Der Applikations-Name, gelesen von build.gradle
appVersion Die Applikations-Version, gelesen von build.gradle


Grails 2.0 Konfigurations-Syntax verwenden

Wenn man es vorzieht, eine Groovy-Konfiguration im Stil von Grails 2.0 zu verwenden, dann ist es möglich, die Konfiguration mit dem Groovy-ConfigSlurper-Syntax zu spezifizieren.

  • ConfigSlurper ist eine Utility-Klasse zum Lesen von Konfigurationsdateien, die in Form von Groovy-Skripten definiert sind.
    • Wie es bei Java *.properties-Dateien der Fall ist, erlaubt ConfigSlurper eine Punktnotation. Zusätzlich erlaubt es aber auch Closure-Scoped Konfigurationswerte und beliebige Objekttypen.

Zwei Groovy-Konfigurationsdateien sind hierbei verfügbar:

Konfigurationswerte mithilfe des GrailsApplication-Objekts abrufen

Um innerhalb von Controllern/Tag-Libraries auf die Laufzeit-Konfiguration zuzugreifen, gibt es eine spezielle öffentliche Variable namens grailsApplication vom Typ GrailsApplication.

Dessen config-Eigenschaft vom Typen grails.config.Config bietet nützliche Funktionen um Werte aus der Konfigurationsdatei zu erhalten:

class MyController {

    def hello(Recipient recipient) {
        // ...
        // Eigenschaft 'foo.bar.max.hellos' (Vom Typen "Integer") abrufen, falls nicht gesetzt "5" nehmen
        def max = grailsApplication.config.getProperty('foo.bar.max.hellos', Integer, 5)

        // Eigenschaft 'foo.bar.greeting' (Ohne definierten Typen, aka. "String"), falls nicht gesetzt "Hello"
        def greeting = grailsApplication.config.getProperty('foo.bar.greeting', "Hello")

        def message = (recipient.receivedHelloCount >= max) ? "Sorry, you've been greeted the max number of times" :  "${greeting}, ${recipient}"

        render message
    }
}

Die config-Eigenschaft des grailsApplication-Objekts ist eine Instanz der Config-Schnittstelle und bietet eine Reihe nützlicher Methoden zum Auslesen der Konfiguration der Anwendung.

Insbesondere die getProperty-Methode (siehe oben) ist nützlich, um Konfigurationseigenschaften effizient abzurufen, während der Eigenschaftstyp angegeben wird (der Standardtyp ist String) und/oder ein Standard-Fallback-Wert bereitgestellt wird.


Zu beachten ist, dass die Config-Instanz eine zusammengeführte Konfiguration ist, die auf dem PropertySource-Konzept von Spring basiert und die Konfiguration aus

  • der Umgebung,
  • den Systemeigenschaften und
  • der lokalen Anwendungskonfiguration (bspw. application.yml)

liest und sie zu einem einzigen Objekt zusammenführt.


Das GrailsApplication-Objekt kann auch ganz einfach in service's und andere Grails-Artifakte wie folgt injected werden:

import grails.core.*

class MyService {
    GrailsApplication grailsApplication

    String greeting() {
        def recipient = grailsApplication.config.getProperty('foo.bar.hello')
        return "Hello ${recipient}"
    }
}


Konfigurationswerte mit Variablen verknüpfen

Die Value-Annotation von Spring kann dazu verwendet werden, einer Variable zur Laufzeit mit dem jeweiligen Konfigurationswert einzuspeisen

import org.springframework.beans.factory.annotation.*

class MyController {
    // Beim starten wird die Variable auf den Wert von der Konfig gesetzt
    @Value('${foo.bar.hello}')
    String recipient

    def hello() {
        render "Hello ${recipient}"
    }
}

Im Groovy-Code müssen Sie für den Wert der Value-Annotation einfache Anführungszeichen um die Zeichenfolge verwenden, andernfalls wird sie als GString und nicht als Spring-Ausdruck interpretiert.


Logging-Konfiguration

Standardmäßig wird die Protokollierung in Grails 3.0 mithilfe des Logback-Frameworks (Offizielle Dokumentation) verarbeitet und kann mit der Datei unter grails-app/conf/logback.groovy konfiguriert werden.

Logger-Namen

Grails-Artifakte (Controller, Services, ...) wird automatisch eine log Methode injeziert.

  • Vor Grails 3.3.0 folgte der Name des Loggers für Grails Artefakt der Konvention grails.app.<type>.<className>, wobei type für den Artefakt-Typ steht, z.B. Controller oder Dienste, und className für den voll qualifizierten Namen des Artefakts.
  • Ab Grails 3.3.x wurden die Logger-Namen vereinfacht.

Das nächste Beispiel veranschaulicht die Änderung:

Logger Name

(Grails 3.3.x oder höher)

Logger Name

(Grails 3.2.x oder niedrieger)

BookController.groovy in grails-app/controllers/com/company OHNE @Slf4j-Annotation com.company.BookController grails.app.controllers.com.company.BookController
BookController.groovy in grails-app/controllers/com/company MIT @Slf4j-Annotation com.company.BookController com.company.BookController


Logging-Konfigurationsdatei bestimmen

Um zu bestimmen, von welcher Datei Logback seine Konfigurationswerte lesen soll, gibt es 3 Möglichkeiten:

  • Innerhalb der Runtime-Konfiguration:
logging:
    config: /Users/me/config/logback.groovy
  • Mithilfe einer Umgebungsvariable:
$ export LOGGING_CONFIG=/Users/me/config/logback.groovy
$ ./gradlew bootRun
  • Mithilfe einer System-Eigenschaft:
$ ./gradlew -Dlogging.config=/Users/me/config/logback.groovy bootRun


Anfrage-Parameter im Log-Stacktrace verstecken

Wenn Grails einen Stacktrace protokolliert, kann die Protokollnachricht die Namen und Werte aller Anforderungsparameter für die aktuelle Anforderung enthalten. Parameter, die in dem Protokoll nicht aufgeführt werden sollen, können innerhalb der Konfigurationsvariable grails.exceptionresolver.params.exclude in Form einer hinterlegt werden:

grails:
    exceptionresolver:
        params:
            exclude:
                - password
                - creditCard

Die Protokollierung von Anforderungsparametern kann ganz abgeschaltet werden, indem der Konfigurationswert grails.exceptionresolver.logRequestParameters auf false gesetzt wird.

Der Standardwert ist

  • true, wenn die Anwendung im Modus DEVELOPMENT läuft, und
  • false für alle anderen Umgebungen.


Umgebungs-Abhängige Konfigurationswerte

Wenn man die Konfigurationen direkt in die Datei einfügt (Am Anfang der Zeile, aka. als Parent-Node) gilt diese für alle gestarteten Umgebungen.


Die Dateien application.yml und application.groovy im Verzeichnis grails-app/conf können das Konzept der "Konfiguration pro Umgebung" verwenden, wobei entweder YAML oder die von ConfigSlurper bereitgestellte Syntax verwendet wird.

Als Beispiel betrachte man die folgende von Grails bereitgestellte Standard application.yml-Definition:

environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: update
            url: jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
        properties:
           jmxEnabled: true
           initialSize: 5
        ...

Der obige Code kann im Groovy-Syntax (application.groovy) wie folgt ausgedrückt werden: (Bemerke, wie die allgemeine Konfiguration auf der obersten Ebene bereitgestellt wird und dann ein Umgebungsblock-pro-Umgebung Einstellungen für die Eigenschaften dbCreate und url der DataSource angeben.)

dataSource {
    pooled = false
    driverClassName = "org.h2.Driver"
    username = "sa"
    password = ""
}
environments {
    development {
        dataSource {
            dbCreate = "create-drop"
            url = "jdbc:h2:mem:devDb"
        }
    }
    test {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:mem:testDb"
        }
    }
    production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:prodDb"
        }
    }
}

Befehl in gewünschter Umgebung ausführen

Tipp: Grails hat die Kababilität, Befehle mit einer gesetzten Umgebung auszuführen: grails <<environment>> <<command name>>. Beispiel: grails test war

Umgebung programmatisch auslesen

Innerhalb des Codes, z.B. in einem Gant-Skript oder einer Bootstrap-Klasse, kann man die Umgebung mit Hilfe der Environment-Klasse erkennen:

import grails.util.Environment

...

switch (Environment.current) {
    case Environment.DEVELOPMENT:
        configureForDevelopment()
        break
    case Environment.PRODUCTION:
        configureForProduction()
        break
}

DataSource

Weil Grails auf Java aufbaut sollte man ein bisschen Verständnis von JDBC (Java Database Connectivity, die Datenbankschnittstelle der Java-Plattform) besitzen.

Treiber einfügen

Wenn man eine andere Datenbank als H2 nimmt benötigt man noch einen passenden JDBC-Treiber. Für MySQL wäre dies Connector/J.
Solche Treiber kommen normalerweise in Form eines Java-Archivs. Am besten fügt man diese Treiber jedoch anhand ihres Maven-Repos in seine dependencies ein: (Gefunden in build.gradle)

dependencies {
    runtime 'mysql:mysql-connector-java:5.1.29'
}

Wenn man die JAR erfolgreich eingebunden hat (und diese auch erfolgreich aufgelöst wird) kann man sich jetzt mit der Weise bekannt machen wie Grails die Datenbank-Konfigurationen handhabt.

dataSource einrichten

In Grails 2.0 wurde die Datei grails-app/conf/DataSource.groovy zum einstellen der Datenbank-Konfigurationen verwendet.

Seit Grails 3.0 muss eine Runtime-Konfigurationsdatei wie die application.yml verwendet werden.

Für die Konfigurations-Eigenschaft dataSource stehen folgende Eigenschaften zur Einstellung bereit:

  • driverClassName - Klassenname des JDBC-Treibers (zB. "com.mysql.jdbc.Driver")
  • username, password - Login-Daten, um die JDBC-Verbindung aufzubauen
  • url - Die JDBC-URL der Datenbank
  • dbCreate - Gibt an, ob die Datenbank automatisch aus dem Domänen-Modell generiert werden soll: "create-drop", "create", "update" oder "validate".
    • create - Falls sich das Datenbank-Schema verändert hat, wird die Tabelle entleert und mithilfe des neuen Schemas erstellt
    • create-drop - Gleich wie create, leert die Datenbank (!) hingegen bei jedem neustart (Egal ob sich das Schema geändert hat oder nicht, nützlich zum Testen)
    • update - Aktualisiert das Model der Tabelle, falls es sich verändert hat (Die Daten bleiben erhalten, Die Domainklassen-Tabelle muss existieren) Notiz: Solch eine Weise veträgt natürlich nicht viele Änderung des Schemas. (zB. Beim ändern des Namens einer Spalte bleibt die alte Spalte mit den existierenden Daten und die neue wird einfach hinzugefügt)
    • validate - Warnt vor Änderungen, aber verändert das Datenbank-Schema bei Veränderung nicht
  • pooled - Ob ein Pool von Verbindungen aufgebaut werden sollte (Standard: true)
  • type - Die Verbindungspoolklasse, wenn Sie Grails zwingen möchten, sie zu verwenden, wenn mehr als eine verfügbar ist.
  • logSql - Leitet SQL-Logs zum stdout um
  • formatSql - Ob SQL-Logs formatiert werden sollen
  • dialect - Eine Zeichenfolge oder Klasse, die den Ruhezustand-Dialekt darstellt, der für die Kommunikation mit der Datenbank verwendet wird. Informationen zu verfügbaren Dialekten findet man im Paket org.hibernate.dialect.
  • readOnly - Wenn true, ist die DataSource schreibgeschützt, was dazu führt, dass der Verbindungspool für jede Verbindung setReadOnly(true) aufruft
  • transactional - Wenn false, wird die DataSource-Transaktionsmanager-Bean außerhalb der verketteten BE1PC-Transaktionsmanager-Implementierung belassen. Dies gilt nur für zusätzliche Datenquellen.
  • persistenceInterceptor - Die Standarddatenquelle wird automatisch mit dem Persistenz-Interceptor verbunden, andere Datenquellen werden nicht automatisch verbunden, es sei denn, dies ist auf true gesetzt
  • properties - Extra Einstellungen für den DatenSource-Bean. (Siehe Tomcat-Pool Dokumentation sowie die dazugehörige Javadoc)
  • jmxExport - Wenn false, wird die Registrierung von JMX-MBeans für alle DataSources deaktiviert. Standardmäßig werden JMX-MBeans für DataSources mit jmxEnabled = true in den Eigenschaften hinzugefügt.


Wichtige Notiz zu dbCreate und Datenbank-Migration

Die Eigenschaft dbCreate der DataSource-Definition ist wichtig, da sie vorgibt, was Grails zur Laufzeit hinsichtlich der automatischen Generierung der Datenbanktabellen aus GORM-Klassen tun soll.

Im Entwicklungsmodus (development) ist dbCreate standardmäßig auf "create-drop" eingestellt, aber irgendwann in der Entwicklung (und auf jeden Fall, wenn Sie zur Produktion übergehen) müssen Sie aufhören, die Datenbank jedes Mal, wenn Sie Ihren Server starten, fallen zu lassen und neu zu erstellen.

Es ist verlockend, zu "update"zu wechseln, so dass Sie vorhandene Daten beibehalten und das Schema nur aktualisieren, wenn sich Ihr Code ändert, aber die Aktualisierungsunterstützung von Hibernate ist sehr konservativ. Es werden keine Änderungen vorgenommen, die zu Datenverlusten führen könnten, und umbenannte Spalten oder Tabellen werden nicht erkannt, so dass Sie das alte Schema behalten und auch das neue haben.


Grails unterstützt Datenbankmigrationen mit Liquibase oder Flyway über Plugins.:


Typische MySQL Konfiguration

Eine typische DataSource eines Grails 2.0 Projekts könnte so aussehen:

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://localhost:3306/my_database"
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    username = "username"
    password = "password"
    properties {
       jmxEnabled = true
       initialSize = 5
       maxActive = 50
       minIdle = 5
       maxIdle = 25
       maxWait = 10000
       maxAge = 10 * 60000
       timeBetweenEvictionRunsMillis = 5000
       minEvictableIdleTimeMillis = 60000
       validationQuery = "SELECT 1"
       validationQueryTimeout = 3
       validationInterval = 15000
       testOnBorrow = true
       testWhileIdle = true
       testOnReturn = false
       jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
       defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED
    }
}

Komplexere MySQL Konfiguration

Ein Beispiel einer komplexeren DataSource-Konfiguration:

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://localhost:3306/my_database"
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    username = "username"
    password = "password"
    properties {
       // Documentation for Tomcat JDBC Pool
       // http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
       // https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html
       jmxEnabled = true
       initialSize = 5
       maxActive = 50
       minIdle = 5
       maxIdle = 25
       maxWait = 10000
       maxAge = 10 * 60000
       timeBetweenEvictionRunsMillis = 5000
       minEvictableIdleTimeMillis = 60000
       validationQuery = "SELECT 1"
       validationQueryTimeout = 3
       validationInterval = 15000
       testOnBorrow = true
       testWhileIdle = true
       testOnReturn = false
       ignoreExceptionOnPreLoad = true
       // http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors
       jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
       defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED // safe default
       // controls for leaked connections
       abandonWhenPercentageFull = 100 // settings are active only when pool is full
       removeAbandonedTimeout = 120
       removeAbandoned = true
       // use JMX console to change this setting at runtime
       logAbandoned = false // causes stacktrace recording overhead, use only for debugging
       // JDBC driver properties
       // Mysql as example
       dbProperties {
           // Mysql specific driver properties
           // http://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html
           // let Tomcat JDBC Pool handle reconnecting
           autoReconnect=false
           // truncation behaviour
           jdbcCompliantTruncation=false
           // mysql 0-date conversion
           zeroDateTimeBehavior='convertToNull'
           // Tomcat JDBC Pool's StatementCache is used instead, so disable mysql driver's cache
           cachePrepStmts=false
           cacheCallableStmts=false
           // Tomcat JDBC Pool's StatementFinalizer keeps track
           dontTrackOpenResources=true
           // performance optimization: reduce number of SQLExceptions thrown in mysql driver code
           holdResultsOpenOverStatementClose=true
           // enable MySQL query cache - using server prep stmts will disable query caching
           useServerPrepStmts=false
           // metadata caching
           cacheServerConfiguration=true
           cacheResultSetMetadata=true
           metadataCacheSize=100
           // timeouts for TCP/IP
           connectTimeout=15000
           socketTimeout=120000
           // timer tuning (disable)
           maintainTimeStats=false
           enableQueryTimeouts=false
           // misc tuning
           noDatetimeStringSync=true
       }
    }
}


Die Application-Klasse

Jede neue Grails-Anwendung verfügt über eine Application innerhalb des Verzeichnisses grails-app/init.

Die Application-Klasse ist eine Unterklasse der GrailsAutoConfiguration-Klasse und verfügt über die static void main, d.h. sie kann als reguläre Anwendung ausgeführt werden.

Die Application-Klasse ausführen

Wenn man eine IDE verwendet, kann man ganz einfach mit der rechten Maustaste auf die Klasse klicken und sie direkt von der IDE aus starten, wodurch Ihre Grails-Anwendung gestartet wird.

Dies ist auch für das Debugging nützlich, da Sie direkt von der IDE aus debuggen können, ohne einen Remote-Debugger anschließen zu müssen, wenn Sie den Befehl run-app --debug-jvm von der Befehlszeile aus ausführen.

Man kann die Anwendung auch z.B. in eine lauffähige WAR-Datei packen (nützlich, wenn man plant die Anwendung mit einem containerlosen Ansatz zu implementieren.):

$ grails package
$ java -jar build/libs/myapp-0.1.war



Grails Scripte

Das Befehlszeilensystem von Grails 3.0 unterscheidet sich stark von früheren Versionen von Grails und verfügt über APIs zum Aufrufen von Gradle für build-bezogene Aufgaben sowie zur Codegenerierung.

Wenn man den folgenden Befehl eingibt, durchsucht Grails das Profil-Repository auf der Grundlage des Profils der aktuellen Anwendung. Wenn das Profil für eine Web-Anwendung ist, werden Befehle aus dem Web-Profil und dem Basis-Profil, von dem es erbt, gelesen.

$ grails <<command name>>


Es wird zuerst die Anwendung und dann das Profil nach Befehlen durchsucht. Beispiel am Befehl run-app:

  • PROJECT_HOME/src/main/scripts/run-app.groovy
  • [profile]/commands/run-app.groovy
  • [profile]/commands/run-app.yml

Grails-Scripte erstellen

Mit dem Befehl $ grails create-script NAME kann man ein Grund-Gerüst für sein neues Skript unter src/main/scripts/ erstellen lassen.

Im Allgemeinen sollten Grails-Skripte für die Skripterstellung des Gradle-basierten Build-Systems und für die Code-Generierung verwendet werden. Skripte können keine Anwendungsklassen laden und sollten dies auch nicht tun, da Gradle zur Konstruktion des Anwendungs-Klassenpfades erforderlich ist.


description()

Jedes Skript erbt (unter anderem)

  • von GroovyScriptCommand, welches eine API für viele nützliche Aufgaben bereitstellt, sowie
  • von TemplateRenderer, welches die zur Code-Generierung genutzten render/template-Methoden zur Verfügung stellt. (Siehe Beispiel unten)


Die description()-Methode wird für die Ausgabe vom grails help Befehl verwendet, um den Nutzern zu helfen.
Beispiel am generate-all Befehl:

description( "Generates a controller that performs CRUD operations and the associated views" ) {
  usage "grails generate-all <<DOMAIN CLASS>>"
  flag name:'force', description:"Whether to overwrite existing files"
  argument name:'Domain Class', description:'The name of the domain class'
}


model()

Wenn man model() mit einer Klassen/String/Datei/Resource-Parameter aufruft, erhält man eine neue Instanz der Klasse Model welche hiflreiche Methoden zur Quellcode-Generierung besitzt.

Beispiel:

def domain = model(com.foo.Bar)

domain.className == "FooBar"
domain.fullName == "com.foo.FooBar"
domain.packageName == "com.foo"
domain.packagePath == "com/foo"
domain.propertyName == "fooBar"
domain.lowerCaseName == "foo-bar"

Darüber hinaus steht die asMap-Methode zur Verfügung, mit der alle Eigenschaften in eine Map verwandelt werden können, die an die render-Methode übergeben wird.

Scripts innerhalb eines Scripts aufrufen

Grails hat schon von Anfang an eine große Anzahl an Skripts, welche man ganz einfach aufrufen kann.
Der folgende Code ruft den Befehl testApp mit dem Parametern --debug-jvm auf.

testApp('--debug-jvm')

Gradle-Tasks aufrufen

Mithilfe der injezierten gradle-Variable können auch Gradle-Tasks getriggert werden:

gradle.compileGroovy()

Ant-Tasks aufrufen

Man kann auch Ant-Tasks aus Skripten heraus aufrufen, was beim schreiben von Codegenerierung und Automatisierungsaufgaben sehr nützlich sein kann:

(Ant ist über ein Plugin, dass bei so gut wie jeder Applikation dabei sein sollte, bereits integriert)

ant.mkdir(dir:"path")


Template-Generation + Beispiel-Befehl

Plugins und Anwendungen, die Aufgaben zur Vorlagenerstellung definieren müssen, können dies mit Hilfe von Skripten tun.

Hierzu sollten die Methoden benutzt werden, die vom geerbeten TemplateRenderer bereitgestellt werden.

Ein Beispiel anhand des create-script-Befehls:

description( "Creates a Grails script" ) {
  usage "grails create-script <<SCRIPT NAME>>"
  argument name:'Script Name', description:"The name of the script to create"
  flag name:'force', description:"Whether to overwrite existing files"
}

def scriptName = args[0]
def model = model(scriptName)
def overwrite = flag('force') ? true : false

render  template: template('artifacts/Script.groovy'),
        destination: file("src/main/scripts/${model.lowerCaseName}.groovy"),
        model: model,
        overwrite: overwrite


Wenn ein Skript in einem Plugin oder Profil definiert ist, wird die template(String)-Methode in der Anwendung nach der Vorlage suchen, bevor die von Ihrem Plugin oder Profil bereitgestellte Vorlage verwendet wird. Auf diese Weise können die Benutzer des jeweiligen Plugins oder Profils anpassen, was generiert wird.

Es ist üblich, den Benutzern auf einfache Weise zu ermöglichen, die Vorlagen aus seinem Plugin oder Profil zu kopieren. Hier ist ein Beispiel dafür, wie das Angular-Scaffolding-Script Vorlagen kopiert.

templates("angular/**/*").each { Resource r ->
    String path = r.URL.toString().replaceAll(/^.*?META-INF/, "src/main")
    if (path.endsWith('/')) {
        mkdir(path)
    } else {
        File to = new File(path)
        SpringIOUtils.copy(r, to)
        println("Copied ${r.filename} to location ${to.canonicalPath}")
    }
}


Grails Befehle

Unterschied Befehl und Skript

Im Gegensatz zu Skripten bewirken Befehle den Start der Grails-Umgebung, d.H. man hat den vollen Zugriff auf den Anwendungskontext und die Laufzeit.

Änderungen in Grails 3.2

Seit Grails 3.2.0 haben Befehle ähnliche Fähigkeiten wie Skripte in Bezug auf das Abrufen von Argumenten, die Erzeugung von Vorlagen, den Dateizugriff und die Modellerstellung.

Befehle, die in Grails 3.1.x oder niedriger erstellt wurden, implementieren standardmäßig die Eigenschaft ApplicationCommand, die erfordert, dass der Befehl die folgende Methode implementiert:

boolean handle(ExecutionContext executionContext)

Befehle, die in Grails 3.2.0 oder höher erstellt wurden, implementieren standardmäßig die Eigenschaft GrailsApplicationCommand, die erfordert, dass der Befehl die folgende Methode implementiert: (Auf diese Weise definierte Befehle haben über eine Variable namens executionContext weiterhin Zugriff auf den Ausführungskontext.)

boolean handle()


Befehle erstellen

Ähnlich wie bei Skripten kann man mit dem Befehl $ grails create-command NAME das Skelett eines Skriptes unter src/main/commands/ erstellen lassen.

Befehl ausführen

Selbst-geschriebene Befehle können

  • mithilfe von $ grails run-command NAME oder
  • mithilfe des Gradle-Tasks "runCommand" gradle runCommand -Pargs="NAME"
    • Wenn der Grails-Server ein Unterprojekt ist (z.B. in einem Projekt, das mit Angular erstellt wurde), kann der Unterprojekt-Befehl immer noch aus dem Gradle-Wrapper im übergeordneten Projekt aufgerufen werden: ./gradlew server:runCommand -Pargs="my-example"

aufgerufen werden.

Gradle und Grails

TODO





Die Web-Ebene


Controller

Ein Controller verarbeitet Anforderungen und erstellt oder bereitet die Antwort vor. Ein Controller kann die Antwort direkt generieren oder an eine Ansicht (View, .gsp) delegieren.

Controller erstellen

Alle Controller befinden sich im Stammbaum unter grails-app/controllers/APP_NAME. Die einzige Voraussetzung für eine Klasse innerhalb dieser Hierarchie ist es, mit Controller.groovy zu enden.


Mit dem Befehl $ grails create-controller (PAKET.)KLASSEN_NAME erstellt man das Skelett eines Controllers, welches dann entsprechend unter grails-app/controllers/APP_NAME/PAKET/KLASSEN_NAME.groovy gespeichert wird.

  • Dieser Befehl ist nur zur vereinfachten Generierung gedacht, man kann es auch manuell oder mit einer IDE machen.


Aktionen

Die Standard URL-Mapping-Konfiguration versichert, dass der Name des Controllers sowie jede Methode zum entsprechendem URI-Pfad gebunden wird.
Das folgende Beispiel ist hiermit unter "localhost:8080/mahlzeit/index" erreichbar:

package myapp

class MahlzeitController {
    // Standard-Aktion
    def index() { }
    
    // Aktion "/list"
    def list {
        //..
        // Controller-Logik
        //..
        return model
    }
}

Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.

Standard-Aktion

Wenn der Nutzer keine bestimmte Aktion in seiner Anfrage stehen hat (Also zB. nur den Namen des Controllers, wie "/mahlzeit/" anstatt zB. "/mahlzeit/login") versucht Grails eine Standard-Aktion ausfindig zu machen, welche sich dieser Anfrage zur Verfügung stellt.

  • Wenn es nur eine Aktion (aka. Methode) gibt, wird diese als Standard-Aktion anerkannt
  • Wenn es eine Aktion namens "index" gibt, wird diese als Standard-Aktion anerkannt

Alternativ kann man auch eine eigenen Standard setzten, indem man static defaultAction = "DEINE-METHODE" in den Quellcode des Controllers einfügt.


Scope-Variablen

"Scope-Variablen" sind HashMap ähnliche Strukturen, in welche man Variablen speichern kann. Für Controller stehen folgende Scope-Variablen zur Verfügung:

servletContext: Statisch, für alle gleich

Dieser Scope lässt uns Daten speichern, welche über die gesamte Web-App gleich statisch erreichbar sind (Applikationskontext).
Das servletContext-Objekt ist eine Instanz vom Standardmäßigen Java(EE)-Objekt ServletContext.

Es ist nützlich um:

  • Applikations-Eigenschaften zu speichern
  • lokale Server-Ressourcen zu laden und
  • Informationen vom Servlet-Container zu erhalten.

session: Für jede Sitzung anders

Das session-Objekt ist eine Instanz der Klasse HttpSession der Java(EE) Servlet-API.

Es ist nützlich um Attribute der derzeitigen Sitzung eines Klientens zu speichern, wie zB. der Login (Name/Passwort).

request: Mitgesendete Informationen

Das request-Objekt ist eine Instanz von HttpServletRequest der Java(EE) Servlet API.

Es ist nützlich um:

  • Mitgesendete Request-Header-Felderdaten zu bekommen
  • Anfragen-bezogene Attribute zwischen-zu-speichern und
  • Informationen des aktuellen Klienten zu erhalten

Grails fügt einige zusätzliche Funktionen zum request-Objekt hinzu, die das standardmäßige HttpServletRequest-Objekt nicht hat, hinzu:

  • XML - Eine Instanz der GPathResult welches erlaubt einkommende XML-Anfragen zu verarbeiten (Parsen) - Nützlich für REST
  • JSON - Eine Instanz des JSONObjects welches erlaubt einkommente JSON-Anfragen zu verarbeiten (Parsen) - Nützlich für JSON und REST
  • isRedirected() - Gibt true zurück wenn diese Anfrage nach einem Redirect fordert.
  • get - Gibt true zurück wenn die Anfrage ein GET-Request ist
  • post - Gibt true zurück wenn die Anfrage ein POST-Request ist
  • each - Implementation von Groovys each-Methode um über gewisse Request-Objekte zu iterieren.
  • find - Implementation von Groovys find-Methode um ein gewisses Request-Objekt zu finden (Ohne komplett richtigen Hashkey-Namen)
  • findAll - Implementation von Groovys findAll-Methode um gewisse Request-Objekte zu finden (Ohne komplett richtigen Hashkey-Namen)
  • xhr - Gibt true zurück wenn die Anfrage ein AJAX-Request ist.

Würde der Body der Anfrage folgenden XML-Code besitzen:

<book>
   <title>The Stand</title>
</book>

Können wir dies ganz einfach mithilfe des request-Objekts serialisieren:

def title = request.XML?.book.title // Mit dem "?" versichern wir uns, dass wenn die Variable "XML" "null" ist es zu keiner NullPointerException kommt und "title" einfach auch auf "null" gesetzt wird (Groovy feature :D)
render "The Title is $title"

(params): (Veränderbare,) Mitgesendete CGI Informationen

Eine veränderbare, mehrdimensionale HashMap aller Anforderungsparametern (CGI).
Obwohl das request-Objekt auch Methoden zum lesen der Anforderungsparametern verfügt, ist der params-Scope manchmal nützlich für die Daten-Bindung an Objekte.

Beispiel:

def save() {
    def book = new Book(params) // bind request parameters onto properties of book
}

flash: Speicher zwischen 2 Anfragen

Diagramm von Post/Redirect/Get. Quelle: Wikimedia

Eine temporäre Speicherzuordnung, in der Objekte innerhalb der Sitzung für die nächste Anforderung, und auch nur für die nächste Anforderung, gespeichert werden. Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.

Dies ist z.B. nützlich, um eine Nachricht direkt vor der Umleitung zu setzen: (Siehe Redirect after Post-Konzept/Problemstellung)

def delete() {
    def b = Book.get(params.id)
    if (!b) {
        flash.message = "User not found for id ${params.id}"
        redirect(action:list)
    }
    ... // remaining code
}

def list(){
    // use "flash.message" in the rendered gsp-template or something like that
}

Bemerke: Die Namen und Typen die man dem flash-Objekt setzt können willkürlich sein.


Scope eines Controllers

Neu erstellte Controller haben den Standard-Scope-Wert "singleton". Verfügbare Controller-Scopes sind:

  • prototype (Standard) - Für jeden Request wird ein neuer Controller erstellt
  • session - Für jede Sitzung wird nur ein Controller-Objekt erstellt
  • singleton - Für die gesamte Zeit gibt es nur eine Instanz des Controllers (Wird geraten für Aktionen mit Methoden)

Dieser Standard-Wert kann man unter grails-app/conf/application.yml wie folgt abändern:

grails:
    controllers:
        defaultScope: singleton

Man kann den Scope eines Individuellen Controllers auch manuell ändern, indem man static scope = "DEIN-SCOPE" in den Quellcode des Controllers einfügt.


Model und Ansicht (Model and View)

Ein "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.

  • Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie innerhalb einer View (z.B. einer .gsp-Datei) erreichbar sind.
  • Zu beachten ist, dass bestimmte Variablennamen im Modell nicht verwendet werden können:
    • attributes
    • application


Es gibt einige Wege um ein Model zu übergeben/zu erzeugen:

  • Explizit: Wenn seine Methode gleich wie die View-Datei heißt, übergibt man das Model einfach wie beim returnen mit folgendem Syntax: [Var1: Data1, Var2: Data2, ...]
def VIEW_NAME() {
   [book: Book.get(params.id)] // Gleich wie render(view: "VIEW_NAME", model: [book: Book.get(params.id)])
}
  • Ein fortgeschrittenerer Ansatz ist die Rückgabe einer Instanz der Spring ModelAndView-Klasse zu benutzen:
import org.springframework.web.servlet.ModelAndView

def index() {
    // get some books just for the index page, perhaps your favorites
    def favoriteBooks = ...

    // forward to the list view to show them
    return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
}


View-Datei selber selektieren mit der render()-Methode

In den vorherhigen 2 Beispielen haben wir nirgendwo angegeben an welche View die Map-Daten übergeben werden soll.
Dies liegt an Grails Konventionen. Im folgenden Beispiel würde Grails unter grails-app/views/book/show.gsp nachsuchen: (Nach dem Schema grails-app/views/CONTROLLER/AKTION.gsp)

class BookController {
    def show() {
         [book: Book.get(params.id)]
    }
}

Falls wir aber nicht wollen, das Grails sich den Namen der View-Datei mithilfe des Methoden-Namens sucht, können wir die vielfältige render()-Methode einsetzen:

def show() {
    def map = [book: Book.get(params.id)]
    render(view: "display", model: map)
}

(Anmerkung: Wenn Grails keine .gsp-Datei finden kann, sucht es auch nach .jsp-Dateien.)


Selektions-Verfahren von Controllern die einem speziellen Namensraum angehören

Wenn ein Controller einen eigenen Namensraum gesetzt hat (In diesem Beispiel 'business'), schaut Grails

  • zuerst nach ob die View-Datei unter grails-app/views/business/... zu finden ist
  • und falls nicht sucht es unter dem normalen Namespace grails-app/views/... nach.

Beispiel:

class ReportingController {
    static namespace = 'business'

    def humanResources() {
        // This will render grails-app/views/business/reporting/humanResources.gsp
        // if it exists.

        // If grails-app/views/business/reporting/humanResources.gsp does not
        // exist the fallback will be grails-app/views/reporting/humanResources.gsp.

        // The namespaced GSP will take precedence over the non-namespaced GSP.

        [numberOfEmployees: 9]
    }


    def accountsReceivable() {
        // This will render grails-app/views/business/reporting/numberCrunch.gsp
        // if it exists.

        // If grails-app/views/business/reporting/numberCrunch.gsp does not
        // exist the fallback will be grails-app/views/reporting/numberCrunch.gsp.

        // The namespaced GSP will take precedence over the non-namespaced GSP.

        render view: 'numberCrunch', model: [numberOfEmployees: 13]
    }
}


Weiterleitungen

Aktionen können mithilfe der Controller-Methode redirect() umgeleitet werden. Beispiel:

class OverviewController {

    def login() {}

    def find() {
        if (!session.user)
            redirect(action: 'login')
            return
        }
        ...
    }
}


Auf welche Seite redirect() umleitet, kann man Grails auf mehrere Weisen mitteilen:

  • Der Name der Aktion (Sowie den namens des Controllers, falls sich die Aktion in einem anderen Controller befindet)
// Redirects to the "index()"-Action in the "home"-Controller
redirect(controller: 'home', action: 'index')
  • URI zu einer Resource relativ vom Kontext-Pfad:
// Redirect to an explicit URI
redirect(uri: "/login.html")
  • Eine Komplette URL:
// Redirect to a URL
redirect(url: "http://grails.org")
  • Domain-Klassen-Instanz: (Grails konstruiert einen Link unter Verwendung der Domänenklassen-ID, falls vorhanden.)
// Redirect to the domain instance
Book book = ... // obtain a domain instance
redirect book


Parameter können von einer zu der anderen Aktion mithilfe des params-Arguments übergeben werden.

Diese Daten befinden sich dann auch im params-Scope-Objekt der durch den Redirects resultierenden Anfrage der Aktion myaction.

redirect(action: 'myaction', params: [myparam: "myvalue"])
redirect(action: 'myaction', params: params) // Since both the "params"-Parameter and the "params"-Controller-Scope are Map's, this is possible


Mithilfe von fragment kann der Hash-Abteil der URL angegeben werden: (Im Standard URL-Mapping würde die folgende Methode in eine Weiterleitung auf /myapp/test/show#profile resultieren)

redirect(controller: "test", action: "show", fragment: "profile")


Aktionen aneinander-reihen

chain() fungiert ähnlich wie redirect(), indem es auf eine andere Aktion springt und diese ausführt.
Chaining hingegen erlaubt es uns das Model beizubehalten und Model-Daten zusätzlich zu übergeben.

Beispiel:

class ExampleChainController {

    def first() {
        chain(action: second, model: [one: 1])
    }

    def second () {
        chain(action: third, model: [two: 2])
    }

    def third() {
        [three: 3]
    }
}

Resultiert beim Aufrufen von "/examplechain/first" in das Model

[one: 1, two: 2, three: 3]

Wie beim redirect kann man hier auch weitere Daten in das params-Scope einspeisen:

chain(action: "action1", model: [one: 1], params: [myparam: "param1"])


Die render()-Methode

Die render()-Methode ist sehr flexibel und kann viele Argumente hingegennehmen (Vom anzeigen eines einfachen Textes bis hin zur render eines Views/Templates.).

// renders text to response
render "some text"

// renders text for a specified content-type/encoding
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")

// render a template to the response for the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(template: "book", model: [book: theShining])

// render each item in the collection using the specified template
render(template: "book", collection: [b1, b2, b3])

// render a template to the response for the specified bean
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(template: "book", bean: theShining)

//! render the view with the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(view: "viewName", model: [book: theShining])

// render the view with the controller as the model
render(view: "viewName")

// render some markup to the response
render {
    div(id: "myDiv", "some text inside the div")
}

// render some XML markup to the response
render(contentType: "text/xml") {
    books {
         for (b in books) {
             book(title: b.title, author: b.author)
         }
    }
}

//! render a JSON ( http://www.json.org ) response with the builder attribute:
render(contentType: "application/json") {
    book(title: b.title, author: b.author)
}

//! render with status code
render(status: 503, text: 'Failed to update book ${b.id}')

//! render a file
render(file: new File(absolutePath), fileName: "book.pdf")


Data Binding

"Data-Binding" ist das Verfahren, einkommende Request-Parameter an ein Objekt zu binden.

  • "Data-Binding" sollte auch die Konversion von String auf den jeweiligen Typen übernehmen können, weil alle Request-Parameter ja nur Strings sind aber ein Objekt ja auch Integers (etc) haben kann.
// Klasse 
class Person {
    String firstName
    String lastName
    Integer age
}

// Bindung
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]

def person = new Person(bindingMap)

assert person.firstName == 'Peter'
assert person.lastName == 'Gabriel'
assert person.age == 63

Mit Unterklassen

Dieser Binder kann auch unterklassen verwalten:

// Klassen
class Person {
    String firstName
    String lastName
    Integer age
    Address homeAddress
}

class Address {
    String county
    String country
}

// Bindung
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63, homeAddress: [county: 'Surrey', country: 'England'] ]

def person = new Person(bindingMap)

assert person.firstName == 'Peter'
assert person.lastName == 'Gabriel'
assert person.age == 63
assert person.homeAddress.county == 'Surrey'
assert person.homeAddress.country == 'England'

Mit Arrays

Auch Arrays sind möglich:

// Klassen
class Band {
    String name
    static hasMany = [albums: Album] // One-To-Many Relation von Band-zu-Album (Für Datenbank-Relation wichtige Angabe!)
    List albums 
}

class Album {
    String title
    Integer numberOfTracks
}

// Bindung
def bindingMap = [name: 'Genesis',
                  'albums[0]': [title: 'Foxtrot', numberOfTracks: 6],
                  'albums[1]': [title: 'Nursery Cryme', numberOfTracks: 7]]

def band = new Band(bindingMap)

assert band.name == 'Genesis'
assert band.albums.size() == 2
assert band.albums[0].title == 'Foxtrot'
assert band.albums[0].numberOfTracks == 6
assert band.albums[1].title == 'Nursery Cryme'
assert band.albums[1].numberOfTracks == 7

Mit Maps

Sowie auch mit Maps:

// Klassen
class Album {
    String title
    static hasMany = [players: Player] // One-To-Many Relation von Album-zu-Player (Für Datenbank-Relation wichtige Angabe!)
    Map players 
}

class Player {
    String name
}

// Bindung
def bindingMap = [title: 'The Lamb Lies Down On Broadway',
                  'players[guitar]': [name: 'Steve Hackett'],
                  'players[vocals]': [name: 'Peter Gabriel'],
                  'players[keyboards]': [name: 'Tony Banks']]

def album = new Album(bindingMap)

assert album.title == 'The Lamb Lies Down On Broadway'
assert album.players.size() == 3
assert album.players.guitar.name == 'Steve Hackett'
assert album.players.vocals.name == 'Peter Gabriel'
assert album.players.keyboards.name == 'Tony Banks'

File-Uploads

.gsp-Code:

Upload Form: <br />
<g:uploadForm action="upload">
   <input type="file" name="myFile" />
   <input type="submit" />
</g:uploadForm>
  • g:uploadForm fügt automatisch enctype="multipart/form-data" zum form-Tag hinzu.

Nun gibt es viele Möglichkeiten mit dem File-Upload umzugehen. Eine davon ist mithilfe von Springs MultipartFile.
(Diese Weise ist gut um Dateien zu transferieren und manipulieren weil man direkt die InputStream-Instanz bekommen kann.)

def upload() {
    def f = request.getFile('myFile')
    if (f.empty) {
        flash.message = 'file cannot be empty'
        render(view: 'uploadForm')
        return
    }

    f.transferTo(new File('/some/local/dir/myfile.txt'))
    response.sendError(200, 'Done')
}

Upload-Größen-Limit von Grails

Grails Standard-Wert für die maximale Dateigröße liegt bei 128KB. Wenn dieses Limit überschritten wird taucht folgender fehler auf:

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException

Dieses Limit kann man in seiner application.yml folgendermaßen anpassen:

grails:
    controllers:
        upload:
            maxFileSize: 2000000
            maxRequestSize: 2100000
  • maxFileSize: Maximale größe einer Datei bei einem Request
  • maxRequestSize: Maximale Größe eines gesamten Requests

URL-Mappings

In der Dokumentation ist die Konvention des URL-Mappings (standartmäßig) wie folgt eingestellt: /controller/action/id.
Dies kann man aber auch sehr leicht für spezielle Seiten in der Datei grails-app/conf/UrlMappings.groovy umändern. Beispiel (URL "/product" auf die Aktion "list()" vom Controller "Product" leiten):

"/product"(controller: "product", action: "list")

Man kann die URL-Mappings auch verschachteln:

group "/store", {
    group "/product", {
        "/$id"(controller:"product")
    }
}


(Explizites) REST-Mapping

"/books"(resources:'book')

ist gleich wie

get "/books"(controller:"book", action:"index")
get "/books/create"(controller:"book", action:"create")
post "/books"(controller:"book", action:"save")
get "/books/$id"(controller:"book", action:"show")
get "/books/$id/edit"(controller:"book", action:"edit")
put "/books/$id"(controller:"book", action:"update")
delete "/books/$id"(controller:"book", action:"delete")


// Results in /books/1/authors/2
<g:link controller="author" action="show" method="GET" params="[bookId:1]" id="2">The Author</g:link>

Redirects

"/viewBooks"(redirect: '/books/list')
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
"/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true])


Integrierte Variablen

"/product/$id"(controller: "product")

Im obrigen Fall wird Grails automatisch beim Besuchen von "/product/2" die Zahl 2 in das params-Scope einsetzen, damit es vom Controller einfach genutzt werden kann:

class ProductController {
     def index() { render params.id }
}

Natürlich kann man auch mehrere Variablen verwenden, wie bei diesem Beispiel eines Blogs:

"/$blog/$year/$month/$day/$id"(controller: "blog", action: "show")

Optionale Variablen

Indem man ein Fragezeichen am ende des Variabl-Namens anhängt schlägt das URL-Mapping auch zu, wenn der Nutzer diese nicht angegeben hat. Beispiel-Mapping:

"/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")

Das obrige Beispiel-Mapping schlägt bei allen folgenden URLs zu:

/graemerocher/2007/01/10/my_funky_blog_entry
/graemerocher/2007/01/10
/graemerocher/2007/01
/graemerocher/2007
/graemerocher

Optionale File-Extension

Wenn man die Datei-Erweiterung auch ganz einfach in einer params-Scope Variable haben möchte, kann man dies durch das anhängen von der Optionalen Variable (.$format)?:

// Mapping
"/$controller/$action?/$id?(.$format)?"()

// Controller
def index() {
    render "extension is ${response.format}"
}

Selbst "eingepflanzte" Variablen

Man kann auch selber mithilfe von URL-Mapping Variablen in das params-Scope reinschreiben.

"/holiday/win" {
     id = "Marrakech"
     year = 2007
}


Dynamisch aufgelöste Variablen

Die oben-genannten, selbst setzbaren statischen Variablen sind sehr nützlich.
Aber manchmal soll sich der Wert der Variable je nach Anfrage anders verhalten können. Dies kann man erzielen, indem man einen eigenen Block zur Variable hinzufügt.

"/holiday/win" {
     id = { params.id }
     isEligible = { session.user != null } // must be logged in
}

Im obrigen Fall wird der Code innerhalb der Blöcke erst dann evaluiert, wenn das URL-Matching zugeschlagen wurde, und kann dadurch "mit Logik versehen werden".

Constraints (Einschränkungen des Typens einer Variable)

Nehmen wir folgendes Beispiel:

static mappings = {
   "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
}

Dieses Mapping funktioniert für:

/graemerocher/2007/01/10/my_funky_blog_entry

Aber auch für Werte, die wir vllt. nicht vorgesehen haben:

/graemerocher/not_a_year/not_a_month/not_a_day/my_funky_blog_entry

Dies wäre sehr problematisch und man müsste eine (komplexe) eigene Logik reincoden, damit bestimmte Variablen nur einen bestimmten Wert haben dürfen.
Zum Glück bieten uns URL-Mappings einen einfachen Weg für diese Einschränkung/Validation:

"/$blog/$year?/$month?/$day?/$id?" {
     controller = "blog"
     action = "show"
     constraints {
          year(matches:/\\\d{4}/)
          month(matches:/\\\d{2}/)
          day(matches:/\\\d{2}/)
     }
}

Error-Codes

Grails lässt uns zudem jeglichen HTTP-Fehlercode auf eine eigene Aktion (Seite) mappen:

"403"(controller: "errors", action: "forbidden")
"404"(controller: "errors", action: "notFound")
"500"(controller: "errors", action: "serverError")

Man kann auch auftauchende Exceptions auf eine eigene Seite mappen:

"500"(controller: "errors", action: "illegalArgument", exception: IllegalArgumentException)