Groovy

Instalacja

  1. Ściągnij Groovy 1.0 ze strony http://groovy.codehaus.org/Download (lub lokalnie)
  2. Ustaw zmienną środowiskową GROOVY_HOME na katalog, gdzie znajduje się rozpakowana dystrybucja
  3. Dodaj GROOVY_HOME/bin do zmiennej PATH
  4. Ustaw zmienną JAVA_HOME tak, by wskazywała na JDK
  5. Uruchom groovysh (konsola) lub groovyConsole (Swing)

Wtyczka do Eclipse'a

http://groovy.codehaus.org/Eclipse+Plugin

Cechy języka

Closures - programowanie funkcyjne

Jest to anonimowy blok kodu wykonywalnego. Może brać argumenty i zwraca wartość jak funkcja. Może korzystać ze zmiennych widocznych w otaczającym kontekście. Można napisać:

{ square -> square * square }

Składnia: { [closureArguments->] statements } closureArguments to opcjonalna lista argumentów oddzielonych przecinkami. Jeśli zostanie pominięta, będziemy mieli closure jednoargumentową (z dostępem do parametru poprzez nazwę it, np. { println it }).

W Javie należałoby zapisać ten kod w otaczającej klasie i metodzie.

Blok można przypisać na zmienną:

  1. def c = { it * it } // domyślny parametr it - zawsze występuje  
  2. c(2// zwróci 4  
  3. c.call(2// to samo co wyżej  

Przypisaną na zmienną closure można potem przekazać jako parametr:

  1. public class GVector extends java.util.Vector {  
  2.   public void apply( c ){  
  3.      for (i in 0..<size()){  
  4.         this[i] = c(this[i])  
  5.      }  
  6.   }  
  7. }  
  1. def gVect = new GVector()  
  2. gVect.add(2)  
  3. gVect.add(3)  
  4. gVect.add(4)  
  5.   
  6. gVect.apply{ value -> println(value) }  

Trochę bardziej złożony przykład:

  1. class Employee {  
  2.     def salary  
  3. }  
  4. def highPaid(emps) {  
  5.     def threshold = 150  
  6.     return emps.findAll{ e -> e.salary > threshold }  
  7. }  
  8.   
  9. def emps = [180140160].collect{ val -> new Employee(salary:val) }  
  10.   
  11. println emps.size()           // prints 3  
  12. println highPaid(emps).size() // prints 2  

Zmienne związane z closure są dla niej dostępne nawet, gdy zostanie zwrócona na zewnątrz otaczającego kontekstu.

Przykład:

  1. public class A {  
  2.     private int member = 20;  
  3.   
  4.     private String method()  
  5.     {  
  6.       return "hello";  
  7.     }  
  8.   
  9.     def publicMethod (String name_)  
  10.     {  
  11.       def localVar = member + 5;  
  12.       def localVar2 = "Parameter: ${name_}";  
  13.       return {  
  14.         println "${member} ${name_} ${localVar} ${localVar2} ${method()}"  
  15.       }  
  16.     }  
  17. }  
  18.   
  19. A sample = new A();  
  20. def closureVar = sample.publicMethod("Xavier");  
  21. closureVar();  
  22.   
  23. The above code will print out:  
  24.   
  25. 20 Xavier 25 Parameter: Xavier hello  

Closures pochodzą od klasy groovy.lang.Closure. Są tzw. First Class Objects.

Z closure można związać na stałe parametry za pomocą tzw. currying'u, np.:

  1. def c = { arg1, arg2-> println "${arg1} ${arg2}" }  
  2. def d = c.curry("foo")  
  3. d("bar")  

Jak zdefiniować metodę, która bierze closure?

  1. class SomeCollection {  
  2.     public void each (Closure c)  
  3. }  

Można teraz wywołać each() z definicją closure poza nawiasami okrągłymi:

  1. SomeCollection stuff = new SomeCollection();  
  2. stuff.each() { println it }  

Bardziej tradycyjna składnia jest też dostępna. W Groovy'm można unikać nawiasów w wielu sytuacjach, zatem poniższe kombinację są też dopuszczalne:

  1. SomeCollection stuff = new SomeCollection();  
  2.   stuff.each { println it }     // Look ma, no parens  
  3.   stuff.each ({ println it })   // Strictly traditional  

Zakresy

  1. // lets use an exclusive range  
  2. range = 5..<8  
  3. assert range.size() == 3  
  4. assert range.get(2) == 7  
  5. assert range[2] == 7  
  6. assert range instanceof java.util.List  
  7. assert range.contains(5)  
  8. assert ! range.contains(8)  
  1. (1..10).each { i ->  
  2.   println "Hello ${i}"  
  3. }  

Zakresy mogą być także użyte w połączeniu ze switch ... case.

  1. switch (years) {  
  2.    case 1..10: interestRate = 0.076break;  
  3.   case 11..25: interestRate = 0.052break;  
  4.       default: interestRate = 0.037;  
  5. }  

Operator *.

assert [1, 3, 5] == ['a', 'few', 'words']*.size()

Indeksowanie

  1. def text = "nice cheese gromit!"  
  2. def x = text[-1// ujemne indeksy - indeksowanie od końca  
  3. assert x == "!"  
  4.   
  5. def name = text[-7..-2]  
  6. assert name == "gromit"  
  7.       
  8. def text = "nice cheese gromit!"  
  9. def name = text[3..1// tak też można - odwracanie indeksów  
  10. assert name == "eci"  

Dynamiczne obiekty (Expandos)

Jest to obiekt, który nie musi mieć z góry zdefiniowanych właściwości i metod.

  1. def player = new Expando()  
  2. player.name = "Dierk"  
  3. player.greeting = { "Hello, my name is $name" }  
  4.   
  5. println player.greeting()  

Wyrażenia regularne

Groovy wspomaga wyrażenia regularne za pomocą wyrażenia ~wzorzec, które kompiluje się do obiektu Javy Pattern z podanego łańcucha znakowego. Groovy posiada też operator =~, które tworzy obiekt Matcher oraz ==~, który dopasowuje wyrażenie regularne.

  1. import java.util.regex.Matcher  
  2. import java.util.regex.Pattern  
  3.   
  4. assert "cheesecheese" =~ "cheese"  
  5. assert "cheesecheese" =~ /cheese/  
  6. assert "cheese" == /cheese/   /*they are both string syntaxes*/  
  7.   
  8. // lets create a regex Pattern  
  9. def pattern = ~/foo/  
  10. assert pattern instanceof Pattern  
  11. assert pattern.matcher("foo").matches()  
  12.   
  13. // lets create a Matcher  
  14. def matcher = "cheesecheese" =~ /cheese/  
  15. assert matcher instanceof Matcher  
  16. answer = matcher.replaceAll("edam")  
  17.   
  18. // lets do some replacement  
  19. def cheese = ("cheesecheese" =~ /cheese/).replaceFirst("nice")  
  20. assert cheese == "nicecheese"  

Iteracja po String'ach, mapach, listach, ...

  1.     // iterowanie po mapie  
  2. def map = ['abc':1'def':2'xyz':3]  
  3. x = 0  
  4. for ( e in map ) {  
  5.     x += e.value  
  6. }  
  7. assert x == 6  
  8.   
  9. // iterowanie po wartościach mapy  
  10. x = 0  
  11. for ( v in map.values() ) {  
  12.     x += v  
  13. }  
  14. assert x == 6  
  15.   
  16. // iterowanie po znakach w łańcuchu  
  17. def text = "abc"  
  18. def list = []  
  19. for (c in text) {  
  20.     list.add(c)  
  21. }  
  22. assert list == ["a""b""c"]  

Switch'owanie po wszystkim

  1. def x = 1.23  
  2. def result = ""  
  3. switch ( x ) {  
  4.     case "foo":  
  5.         result = "found foo"  //UWAGA: bez break'a!  
  6.     case "bar":  
  7.         result += "bar"  
  8.     case [456, 'inList']:  
  9.         result = "list"  
  10.         break  
  11.     case 12..30:  
  12.         result = "range"  
  13.         break  
  14.     case Integer:  
  15.         result = "integer"  
  16.         break  
  17.     case Number:  
  18.         result = "number"  
  19.         break  
  20.     default:  
  21.         result = "default"  
  22. }  
  23. assert result == "number"  

Operator ?.

Gdy przechodzimy po skomplikowanym grafie obiektów i nie chcemy, by rzucany był NullPointerException, możemy użyć operatora ?. zamiast ..

  1. def foo = null  
  2. def bar = foo?.something?.myMethod()  
  3. assert bar == null  

Grails

Cel

Celem Grails jest zapewnienie podobnego do Rails środowiska, które wygląda bardziej znajomo dla programistów Javy, dzięki czemu przejście do tego środowiska ma być łagodniejsze.

Grails bazuje na technologiach takich jak Spring (MVC), Hibernate (mapowanie obiektów i relacji), SiteMesh (interfejs użytkownika).

Inne elementy, na których opiera się Grails, to Dynamic Tag Libraries, Grails Object Relational Mapping, Groovy Server Pages oraz Scaffolding.

Instalacja

  1. Ściągnij Grails ze strony http://grails.codehaus.org/Download (lub lokalnie)
  2. Utwórz zmienną środowiskową GRAILS_HOME i ustaw tak, by wskazywała katalog z rozpakowanym archiwum
  3. Dodaj do zmiennej PATH katalog bin z katalogu GRAILS_HOME
  4. W linii poleceń wpisz grails

Tworzenie projektu

Aby utworzyć projekt należy wpisać w konsoli grails create-app. Grails zapyta o nazwę projektu (w przykładzie będzie to my-project) i utworzy strukturę projektu.

%PROJECT_HOME%
    + grails-app
       + conf                 ---> configuration artifacts like data sources
       + controllers          ---> controller artifacts
       + domain               ---> domain classes
       + i18n                 ---> message bundles for i18n
       + services             ---> services
       + taglib               ---> tag libraries
       + util                 ---> special utility classes (e.g., codecs, etc.)
       + views                ---> views
           + layouts              ---> layouts
   + hibernate              ---> optional hibernate config
   + lib
   + spring                 ---> optional spring config
   + src
       + groovy               ---> optional; location for Groovy source files 
                                   (of types other than those in grails-app/*)
       + java                 ---> optional; location for Java source files
   + war
       + WEB-INF

Grails i Eclipse

Grails automatycznie tworzy Eclipse'owe projekty przy tworzeniu aplikacji.

Bug! Nie należy używać katalogu głównego ani ścieżek ze spacjami.

W Eclipse przejdź do Windows -> Preferences... -> Java -> Build path -> Classpath Variables -> New i dodaj zmienną GRAILS_HOME.

Konfiguracja projektu

Źródła danych

W katalogu grails-app/conf utworzone zostały pliki konfiguracyjne dla źródeł danych. Domyślnie ustawione są na bazę danych HSQLDB działającą w pamięci operacyjnej (zobacz: grails-app/conf/DevelopmentDataSource.groovy).

Jeśli chcemy użyć innej bazy danych, wystarczy zmienić odpowiednie wartości w plikach konfiguracyjnych oraz dodać sterownik bazy danych do katalogu lib projektu.

Domain Classes - encje

Domain class jest to klasa utrwalana w bazie danych. Domyślnie wszystkie jej atrybuty są zapamiętywane.

Aby utworzyć domain class, w katalogu głównym projektu wykonaj polecenie grails create-domain-class. Zostaniesz poproszony o podanie nazwy klasy (wpisz np. Book). Klasa zostanie utworzona w katalogu grails-app/domain.

Można dodać teraz dane testowe. Żeby to zrobić, należy zmienić plik grails-app/conf/ApplicationBootStrap.groovy:

  1. class ApplicationBootStrap {  
  2.   
  3.     def init = { servletContext ->  
  4.         // Create some test data  
  5.         new Book(author:"Stephen King",title:"The Shining").save()  
  6.         new Book(author:"James Patterson",title:"Along Came a Spider").save()  
  7.     }  
  8.     def destroy = {  
  9.     }  
  10. }  

Kontrolery

Kontrolery obsługują zapytania i mapują URL'e.

Aby wygenerować kontroler oraz widok (strony GSP) należy wykonać grails generate-all i wpisać nazwę encji (Book z przykładu). Kontroler zostanie wygenerowany do pliku grails-app/controllers/BookController.groovy. Otwórz ten plik i zmień go w następujący sposób, by wykorzystać dynamiczny scaffolding:

  1. class BookController {  
  2.      def scaffold = Book  
  3. }  

Uruchamianie projektu

Zostanie uruchomiony silnik Jetty na porcie 8080.

Lista książek jest dostępna pod adresem http://localhost:8080/my-project/book/list.

Grails Object Relational Mapping (GORM)

Grails wykorzystuje Hibernate 3, ale nie trzeba nic konfigurować.

Domain Class

Jak widzieliśmy w przykładzie jest to zwykła klasa. W czasie wykonania dostaje jednak dwa dodatkowe atrybuty: id oraz version.

Domyślnie wszystkie atrybuty są obowiązkowe oraz są utrwalane w bazie danych. Aby zmienić to zachowanie, wystarczy stworzyć listy wyjątków w klasie:

  1. class Book {  
  2.     static optionals = [ "releaseDate" ]  
  3.     static transients = [ "digitalCopy" ]  
  4.   
  5.     Author author  
  6.     String title  
  7.     String author  
  8.     Date releaseDate  
  9.     File digitalCopy  
  10. }  

Można także ustawić wartości domyślne oraz zmienić nazwę mapowanej tabeli:

  1. class Book {  
  2.     static withTable = "book_table" // tabela będzie mapowana do book_table  
  3.     //...  
  4.     String title = ''  
  5.     String author = '[author unknown]' // domyślna wartość  
  6.     //etc..  
  7. }  

Modelowanie związków pomiędzy encjami

Walidacja danych

Ograniczenia na dane nakłada się za pomocą closure constraints, np.:

  1. class User {  
  2.    Long id  
  3.    Long version  
  4.   
  5.    String login  
  6.    String password  
  7.    String email  
  8.    Date age  
  9.   
  10.    static constraints = {  
  11.           login(length:5..15,blank:false,unique:true)  
  12.           password(length:5..15,blank:false)  
  13.           email(email:true,blank:false)  
  14.           age(min:new Date(),nullable:false)  
  15.    }  
  16. }  

Walidujemy dane za pomocą metody validate(). Domyślnie waliduje także save().

Wprowadzone ograniczenia mają wpływ na generowany widok, np. zamiast pól tekstowych generowane są listy, lub zakresy wartości

  1. def user =  new User()  
  2.        // populate properties  
  3.   
  4.        if(user.validate()) {  
  5.           // do something with user  
  6.        }  
  7.        else {  
  8.            user.errors.allErrors.each {  
  9.                println it  
  10.            }  
  11.        }  

Więcej o kontrolerach

Kontrolery można tworzyć automatycznie za pomocą polecenia grails create-controller. Taki sam efekt będzie miało utworzenie kontrolera ręcznie. Mapowanie URL'i jest automatyczne.

  1. class BookController { // mapuje na <...>/book  
  2.     def list = {    // mapuje na <...>/book/list  
  3.         // do controller logic  
  4.         // create model  
  5.         return model  
  6.     };  
  7.       
  8.     def index = {   // domyślna akcja  
  9.         redirect(action:list)  
  10.     }  
  11. }  

Podczas wykonania kontroler dostaje metody i parametry, pozwalające na dostęp do sesji, czy też parametrów żądania a także na logowanie błędów, itp. Pełny spis metod i parametrów znajduje się tu.

  1. class BookController {  
  2.     def find = {  
  3.         def findBy = params["findBy"// parametry żądania  
  4.         def appContext = servletContext["appContext"]  
  5.         def loggedUser = session["logged_user"// parametry sesji  
  6.   
  7.         // do stuff  
  8.         // return model  
  9.         return model  
  10.     };  
  11. }  

Kontekst "Flash" jest to pomysł wzięty z Rails, polegający na wprowadzeniu tymczasowego składu dla atrybutów, które mają być dostępne tylko przy następnym żądaniu. Przykład użycia:

  1. def delete = {  
  2.     def b = Book.get( params['id'] )  
  3.     if(!b) {  
  4.         flash['message'] = "User not found for id ${params['id']}"  
  5.         redirect(action:list)  
  6.     }  
  7.     ... // remaining code  
  8. }  

Kontroler powinien zwrócić pewien model, którego używa widok przy renderowaniu strony. Model jest mapą:

  1. def show = {  
  2.     def b = Book.get( params['id'] )  
  3.     return [ book : b ]  
  4. }  

Jeśli nie będzie zwrócony żaden model, użyte zostaną atrybuty kontrolera.

Widoki

Widok realizowany jest za pomocą stron GSP lub JSP. Zasadnicza różnica pomiędzy nimi jest taka, że w stronach GSP skryptlety pisze się w Groovy'm oraz dostępne są dodatkowe tagi. Tutaj znajduje się lista dostępnych tagów.

Za podłączenie widoków do kontrolerów odpowiada mechanizm konwencji. Na przykład dla kontrolera:

  1. class BookController {  
  2.       def list = {  
  3.          ["books" : Book.list() ]  
  4.       }  
  5. }  

odpowiedni widok znajduje się w katalogu grails-app/views/book i ma nazwę list.gsp.

  1. <html>  
  2. <head>  
  3.     <title>Book list</title>  
  4. </head>  
  5. <body>  
  6. <h1>Book list</h1>  
  7. <table>  
  8.     <tr>  
  9.         <th>Title</th>  
  10.          <th>Author</th>  
  11.     </tr>  
  12.   
  13.     <g:each in="${books}">  
  14.         <tr>  
  15.              <td>${it.title}</td>  
  16.              <td>${it.author}</td>  
  17.         </tr>  
  18.     </g:each>  
  19. </table>  
  20. </body>  
  21. </html>  

Layouts

Układ strony można stosować w Grails poprzez SiteMesh. Polega to na dodaniu tagu meta z nazwą layout:

  1. <html>  
  2.     <head>  
  3.         <meta name="layout" content="main"></meta>  
  4.     </head>  
  5.     <body>This is my content!</body>  
  6. </html>  

Layout nazwany main.gsp należy umieścić w katalogu grails-app/views/layouts.

  1. <html>  
  2.       <head>  
  3.           <title><g:layoutTitle default="An example decorator" /></title>  
  4.           <g:layoutHead />  
  5.       </head>  
  6.       <body onload="${pageProperty(name:'body.onload')}">  
  7.             <div class="menu"><!--Jakieś wspólne menu--></menu>  
  8.                  <div class="body">  
  9.                       <g:layoutBody />  
  10.                  </div>  
  11.             </div>  
  12.       </body>  
  13. </html>