30 kwietnia 2009

Z rozdziału 15. o usługach sieciowych z DGG2

0 komentarzy
Przyszła pora na relację z rozdziału 15. Web Services z "The Definitive Guide to Grails, Second Edition".

Temat usług sieciowych zazwyczaj rozpoczyna się od protokołu SOAP (ang. Simple Object Access Protocol), który "Simple" był w założeniu i okazał się być całkowicie inny w praktyce. Na scenie pojawił się REST (ang. Representational State Transfer), który jest prostym (nie z nazwy, ale faktycznie) podejściem architektonicznym, który opiera się na wykorzystaniu HTTP do komunikacji między usługami sieciowymi (do tej pory mówiąc o Web Services na myśl przychodził SOAP, który z narodzinami REST schodzi w cień). Zaletą REST jest wykorzystanie sprawdzonego protokołu HTTP do przesyłania komunikatów w dowolnym formacie, np. JSON (ang. JavaScript Object Notation), POX (ang. Plain Old XML) czy wręcz SOAP. Istotną zaletą REST jest powiązanie poleceń HTTP na odpowiednie polecenia w aplikacji CRUD, tj. poleceniom PUT, GET, POST i DELETE odpowiadają, odpowiednio, akcje Create, Read, Update i Delete wykonywane na pewnym zasobie w aplikacji. Komunikacja za pomocą formatu JSON znacznie upraszcza tworzenie aplikacji AJAX, ponieważ JavaScript ma natywne wsparcie dla JSON (nie przypadkiem tak się nazywa).

Przekładając teorię REST na możliwości Grails - polecenia HTTP to akcje kontrolera, który operuje na zasobach - klasach dziedzinowych. Najbardziej trywialną obsługą REST w Grails jest oparcie się na parametrze request.method, np. (Listing 15-1):
 class AlbumController {
def index = {
switch(request.method) {
case "GET":
return show()
case "PUT":
return save()
...
}
}
}
Jest jednak prostszy sposób (Listing 15-2):
 static mappings = {
"/album/$id?"(controller:"album") {
action = [GET:'show', PUT:'save', POST:'update', DELETE:'delete']
}
}
Przy bardziej skompilowanym adresie URL, np. "/music/$artist/$album?/$song?", wymagamy, aby kontroler, za pomocą serii if-else, wykonał odpowiednią akcję. Możemy uprościć kontroler przenosząc decyzję "co i kiedy" na konfigurację Grails i bardziej specjalizowane kontrolery, np. (Listing 15-4):
 "/music/$artistName/$albumTitle?/$songTitle?" {
controller = {
if (params.albumTitle && params.songTitle) return 'song'
else if (params.albumTitle) return 'album'
else return 'artist'
}
action = [GET:'show', PUT:'save', POST:'update', DELETE:'delete']
}
Grails posiada mechanizm negocjacji (zwracanej) treści (ang. content negotiation) na podstawie nagłówków HTTP: ACCEPT lub CONTENT_TYPE, parametru format oraz rozszerzenia w adresie URL.

Definicja akceptowanych typów MIME jest w grails-app/conf/Config.groovy przez parametr grails.mime.types.

Korzystając z metody withFormat w kontrolerze możemy obsługiwać różne typy MIME, np. (Listing 15-6):
 import grails.converters.*
class ArtistController {
def show = {
def artist = ...
if (artist) {
withFormat {
html artist:artist, albums:artist?.albums
xml { render artist as XML }
}
} else {
response.sendError 404
}
}
}
Konstrukcja "as XML" korzysta z zaimportowanej klasy-konwertera grails.converters.XML, aby automatycznie wysłać odpowiedź w postaci XML.

Do odpowiedzi w Grails (a właściwie Groovy) korzystamy z XmlSlurper oraz dostępu za pomocą notacji OGNL, np. (Listing 15-8):
 url = new URL("...")
conn = url.openConnection()
conn.addRequestProperty("accept", "application/xml")

artist = new XmlSlurper().parse(conn.content)

println "Artist Name = ${artist.name}"
Jeśli przeglądarka wyśle nagłówek ACCEPT z wartością */*, wtedy Grails bierze pierwszą metodę w domknięciu withFormat. Możemy również wskazać Grails, aby brał inną poprzez grails.mime.types (domyślnie jest all: '*/*', co oznacza, że w withFormat szukana jest metoda all).

Najprostszym sposobem jest wybór typu żądania/odpowiedzi po rozszerzeniu w URL. Domyślnie, Grails obsługuje żądania z URL zakończonym na xml jako żądania XML i w odpowiedzi wysyła XML. Wyłączamy to zachowanie przez zmienną grails.mime.type.extensions=false w grails-app/conf/Config.groovy.

Ostatnim rozwiązaniem jest za pomocą parametru format. Łącząc to z mapowaniem URL możemy wskazywać rodzaj żądania, np. (Listing 15-14):
 "/music/$artist(controller:"artist") {
action = "show"
format = "xml"
}
Wskazanie strony GSP obsługującej dany typ żadania, to dodanie typu żądania do nazwy pliku przed rozszerzeniem gsp, np. show.wml.gsp. W przypadku braku, brana jest domyślna strona GSP, np. show.gsp.

Do wysłania odpowiedzi w postaci JSON korzystamy z konwertera grails.converters.JSON i wywołania "as JSON", np. (Listing 15-20):
 import grails.converters.*

...
withFormat {
json { render artist as JSON }
}
W książce autorzy prezentują użycie wtyczki Firefox Poster do śledzenia ruchu między przeglądarką a serwerem HTTP.

Domyślnie Grails obsługuje żądania XML i JSON, jak żądania HTML.

Standardy RSS i Atom służą do publikowania strumieni informacji w Sieci. Możliwość prezentowania danych w aplikacji GTunes autorzy opierają na wtyczce feeds. Pamiętam, jak w komentarzu do wpisu o Grails ktoś wspomniał o konstrukcji g.message(code:"gtunes.latest.feed.title") do pobrania komunikatu dla danego języka. Właśnie teraz pojawiło się wykorzystanie - w kodzie kontrolera do stworzenia strumienia. Z wykorzystaniem znacznika <feed:meta> konfigurujemy link do RSS lub Atom, np. (Listing 15-31):
 <html>
<head>
<feed:meta kind="rss"
version="2.0"
controller="store"
action="latest"
params="[format='rss']" />
...
</head>
...
</html>
Korzystanie z dobrodziejstw SOAP możliwe jest z pomocą wtyczek xfire, axis2 oraz metro. Autorzy prezentują użycie wtyczki xfire. Wystawienie kontrolera jako usługi sieciowej z SOAP to static expose = ['xfire'] (zamiast xfire, moglibyśmy użyć również rmi lub innej technologii).

Na koniec opisuje się pokrótce projekt Groovy-WS.

28 kwietnia 2009

Groovy przez pryzmat makr OpenOffice.org

15 komentarzy
Niesamowite rzeczy można wyczyniać w tym Groovy. Wciąż nie przestaje mnie zadziwiać. Tym razem natrafiłem na pewną cechę Groovy przez...OpenOffice.org.

Na tapetę poszedł temat pisania skryptów w OpenOffice.org (OOo). Korzystam z OOo Calc i wciąż przeklejam dane z Sieci, aby coś tam wyliczać, więc pomyślałem, że mógłbym to zautomatyzować. Przejrzałem Sieć w poszukiwaniu informacji nt. pisania skryptów w OOo (dokładniej OpenOffice.org Scripting Framework) i wyszło mi, że poza Javą, JavaScript i Python można również pisać w Groovy za pomocą rozszerzenia Groovy for OpenOffice. Właśnie! Groovy! Tego mi było trzeba. Po instalacji wtyczki G4OOo, która sprowadza się do wykonania kilku czynności z poziomu OOo, pozostało napisać skrypt. I tu zaczęła się walka z poznawaniem OOo API. Już dawno mnie tak nic nie zmęczyło. Normalnie koszmar. Przypomniały mi się czasy programowania w technologii CORBA. Na szczęście z pomocą przyszedł Groovy, który męki z OOo API przesłonił Groovy Categories. Jest tam wzmianka o Objective-C, którego nie miałem okazji wcześniej poznać (a widać powinienem, skoro są tam takie cuda), więc tym bardziej zaciekawiło mnie, cóż to są te kategorie. Na zachętę zacytuję tylko wycinek wprowadzenia do kategorii Groovy na wspomnianej stronie:

There are many situations where you might find that it would be useful if a class not under your control had additional methods that you define.

W świecie AOP nazywa się to bodajże Mixin. Bez względu jak by to nie nazwać łącząc to z OOo API zamiast pisać:
 def doc = XSCRIPTCONTEXT.document
def spreadsheet = UnoRuntime.queryInterface(XSpreadsheetDocument.class, doc)
można tak:
 class UnoCategory {
public static Object uno(Object unoObj, Class clazz) { UnoRuntime.queryInterface(clazz, unoObj) }
public static Object getAt(XPropertySet pset, String pname) { pset.getPropertyValue(pname) }
public static void putAt(XPropertySet pset, String pname, Object newValue) { pset.setPropertyValue(pname, newValue) }
public static Object getAt(XIndexAccess ndx, int x) { ndx.getByIndex(x) }
}

use (UnoCategory) {
def doc = XSCRIPTCONTEXT.document
def spreadsheet = doc.uno(XSpreadsheetDocument)
}
Na tym przykładzie możnaby pomyśleć, że pisania jest znacznie więcej, ale tak nie jest w rzeczywistości. Wystarczy kilkakrotnie wykonać metodę uno() zamiast jej równoważnika UnoRuntime.queryInterface(clazz, unoObj), aby natychmiast zauważyć różnicę. W moim niewielkim skrypcie korzystam z tej metody kilkakrotnie, a poza wypisaniem tekstu "Groovy rulez!" w komórce (5,1) nic nie robię - w końcu wciąż się tego uczę, więc czego się spodziewaliście?! :)
 import com.sun.star.uno.UnoRuntime
import com.sun.star.sheet.XSpreadsheetDocument
import com.sun.star.sheet.XSpreadsheet
import com.sun.star.lang.XMultiComponentFactory
import com.sun.star.awt.XDialogProvider
import com.sun.star.beans.XPropertySet
import com.sun.star.container.XIndexAccess

class UnoCategory {
public static Object uno(Object unoObj, Class clazz) { UnoRuntime.queryInterface(clazz, unoObj) }
public static Object getAt(XPropertySet pset, String pname) { pset.getPropertyValue(pname) }
public static void putAt(XPropertySet pset, String pname, Object newValue) { pset.setPropertyValue(pname, newValue) }
public static Object getAt(XIndexAccess ndx, int x) { ndx.getByIndex(x) }
}

use (UnoCategory) {
// kontekst jest dostępny wszystkim skryptom
def doc = XSCRIPTCONTEXT.document

def spreadsheet = doc.uno(XSpreadsheetDocument)
def sheets = spreadsheet.sheets
def sheetGospodarka = sheets.getByName("Gospodarka").uno(XSpreadsheet)
sheetGospodarka.getCellByPosition(5,1).setFormula("Groovy rulez!")

// skrypt powinien zwrócić 0 jako poprawne wykonanie
0
}
Nie agitując dalej, warto się przyjrzeć kategoriom w Groovy - zdaje się, że jest to pomost do świata zaawansowanego AOP, a zaczęło się tak niewinnie - napisanie skryptu w OOo. Interesujące (a książka Programming Groovy z The Pragmatic Programmers wciąż czeka na mnie cierpliwie).

Swój skrypt-makro umieściłem w głównym menu, więc pozostaje go tylko dokończyć o pobieranie danych z Sieci i finito! Więcej informacji o strukturze arkuszy w OOo Calc znajdziemy w Working with Spreadsheet Documents, a przykładowe skrypty są w dawno nieodświeżanym serwisie OO-Snippets.

Jakby tego było mało, trafiła mi się jeszcze jedna ciekawostka Groovy. Podczas próby uruchomienia wątków za pomocą konstrukcji
 Thread t = new Thread() {
public void run() {
// zrób coś
}
}
t.start()
jedyne, co otrzymywałem, to komunikat błędu (nie ma sensu go tu przywoływać, taki był dziwaczny). Przypomniałem sobie, że w kontekście integracji Wicket i Grails wspominano coś o braku wsparcia dla klas wewnętrznych (w tym przypadku klas anonimowych), więc spróbowałem tak:
 class MyRunnable implements Runnable {
public void run() {
// zrób coś
}
}
Nie miałem pojęcia, czy tak można, czy nie, ale tym razem komunikat błędu był najwyższych lotów:

Class definition not expected here. Possible attempt to use inner class. Inner classes not supported, perhaps try using a closure instead.

To mnie skierowało, aby oprogramować to w jeszcze inny sposób:
 Thread t = new Thread({
// zrób coś
})
t.start()
I to zadziałało! I jak tu nie lubić Groovy'ego?! Mimo potencjalnego braku szybkości działania, w przypadku pisania skryptów OOo jest w zupełności wystarczający. Nie potrzeba przecież wyrafinowanego wydajnościowo środowiska - Groovy nadaje się w tym przypadku idealnie. Wystarczy, aby pisało się szybko i bez karbów silnego typowania.

Dla odświeżenia umysłu warto otworzyć OOo Calc i wpisać =starcalcteam() w dowolną komórkę. Interesujące, co?! Więcej na Easter Egg: starcalcteam().

27 kwietnia 2009

Z rozdziału 14. o bezpieczeństwie w Grails z DGG2

0 komentarzy
W rozdziale 14. Security w "The Definitive Guide to Grails, Second Edition" autorzy przedstawiają dostępne w Grails mechanizmy wspierania tworzenia aplikacji webowych bezpiecznie(j). Znajdziemy w nim przedstawienie sposobów obsługi uwierzytelniania (ang. authentication), czyli stwierdzenia z kim rozmawiamy oraz autoryzacji (ang. authorization), czyli przypisania właściwych praw w aplikacji.

Jak to ujęli autorzy książki Graeme i Jeff:

"Of course, there is no point in reinventing the wheel, so we'll cover how you can use one of the security frameworks already available".

Istnieje wiele ataków na aplikacje webowe, ale najbardziej znanymi są SQL Injection oraz cross-site scripting (XSS). Pierwszy polega na przekazaniu parametrów żądania, w taki sposób, aby nieumiejętne tworzenie zapytań bazodanowych w aplikacji zwróciło więcej danych niż planowano, lub co gorsza, zmodyfikowało je w nieautoryzowany sposób. Drugi, XSS, to takie przekazanie parametrów żądania w postaci skryptu JavaScript, aby umieszczenie ich na stronie (przez wyświetlenie wprowadzonych przez użytkownika danych) albo w dziennikach zdarzeń (sam JavaScript w logach nie jest niczym strasznym, ale wyświetlenie go na stronie, to potencjalnie wykonanie go) spowodowało nieplanowane działanie aplikacji (najczęściej wykonanie kodu, który wymagałby specjalnych uprawnień do jego wykonania).

Rozwiązaniem przed SQL/HQL Injection to wykorzystanie mechanizmu przekazywania parametrów do zapytań HQL (podobnie jak w JPA) zamiast składania zapytania przez konkatenację tekstu, np. (Listing 14-2):
 Album.findAll("from Album as a where a.title = ?", [params.title])
Album.findAll("from Album as a where a.title = :title", [title:params.title])
Album.withCriteria {
eq('title', params.title)
}
Album.findAllByTitle(params.title)
Wstrzeliwanie skryptu Groovy (ang. Groovy injection) to kolejny rodzaj ataku na aplikację udostępniającą możliwość wykonywania skryptów Groovy bez ograniczenia dostępnych funkcji przez mechanizm bezpieczeństwa w Javie, np. (Listing 14-3):
 def execute = {
new GroovyShell().execute(params.script)
}
W szczególności możnaby przekazać "System.exit(1)" do wykonania.

Ciekawostką w kontekście bezpieczeństwa i Grails jest wskazanie "an attacker" jako "she" (!) - patrz str. 409:

The technique (XSS) involves injecting JavaScript written by the attacker into the page. An attacker able to control the JavaScript on your site is an incredibly dangerous scenario. She could do all manner of things, from stealing a user's cookie to changing a login form so that it sends requests to another server that captures usernames and passwords.

Tym samym uzmysłowiłem sobie, ile to razy prezentowałem aplikację, która była podatna na ataki XSS - chciażby wyświetlanie danych użytkownika. Jeśli zamiast poprawnego imienia wpiszemy skrypt typu:
 <script type="text/javascript>alert('hello')</script>
To niegroźne z pozoru Witaj <span id="imie">${session?.user?.firstName}</span>! spowoduje wykonanie skryptu. Grails umożliwa ochronę przed tego typu atakiem za pomocą:
 grails.views.default.codec="html"
w grails-app/conf/Config.groovy, który zamieni wszystkie specjalne znaki w dowolnym wyrażeniu ${...} w GSP na ich odpowiedniki jako encje HTML (ang. HTML entities), np. dla < będzie to &lt;. Dotyczy to wszystkich stron GSP, bez względu na tworzony rodzaj strony - HTML, PDF, JSON, itp. Zawężenie do pojedynczej strony GSP, to:
 <%@ defaultCodec="html" %>
Możemy również zamienić jedynie symbole dla pojedynczej zmiennej z metodą encodeAsHTML(), tj. (Listing 14-4):
 ${session?.user?.firstName?.encodeAsHTML()}
Możemy również skorzystać ze znacznika <g:textField>, który wykona za nas całą robotę i zadba o nielegalne symbole (a zastanawiałem się ostatnio, do czego miałbym wykorzystać ten znacznik, skoro mogę bezpośrednio wypisać tekst - teraz już jest jasne).

Podobna sytuacja będzie z adresami URL i XSS, tj. korzystajmy z <g:link> do tworzenia adresów URL, zamiast (Listing 14-5):
 <a href="/gtunes/albums?title=${params.title}">Show Album</a>
Jeśli jednak musimy, korzystajmy z encodeAsURL(), tj. (Listing 14-6):
 <a href="/gtunes/albums?title=${params.title?.encodeAsURL()}">Show Album</a>
Uwaga na DoS (ang. Denial of Service), który widzieliśmy już przy wykonaniu skryptu System.exit(1). Możemy paść jego ofiarą również w sytuacji wykonania zbyt obciążającego system zapytania, np. (Listing 14-7):
 def list = {
if (!params.max) params.max = 10
[albumList: Album.list(params)]
}
Wystarczy wykonać powyższe domknięcie z parametrem "max" ustawionym na 1000000 i system...nie pozbiera się przez jakiś czas. A wystarczy (Listing 14-8):
 def list = {
params.max = Math.min(params.max?.toInteger() ?: 0, 100)
[albumList: Album.list(params)]
}
Niby niewiele potrzeba, a późniejsze utrzymywanie systemu będzie znacznie przyjemniejsze. Zostaliście ostrzeżeni! :)

Uważajmy również na automatyczne przypisywanie wartości z żądania do atrybutów klas dziedzinowych. Wystarczy odpowiednio spreparować żądanie, aby (Listing 14-9):
 def update = {
def user = User.get(params.id)
user.properties = params
if(user.save()) {
redirect(action:"profile", id:user.id)
}
...
}
ustawiło egzemplarz użytkownika z danymi, które nie powinny zostać zmienione! Zamiast tego, skorzystajmy z zawężonego przypisywania danych (Listing 14-10):
 def update = {
def user = User.get(params.id)
user.properties['firstName', 'lastName', 'phoneNumber', 'password'] = params
if(user.save()) {
redirect(action:"profile", id:user.id)
}
...
}
Ponownie niewiele potrzeba, aby system był bezpieczniejszy (chociaż świadomość istnienia tego typu "kwiatków" nie należy do powszechnej wiedzy...niestety).

Metody encodeAsHTML() oraz encodeAsURL() są dostarczane przez kodeki w Grails. Zgodnie z konwencją Grails, klasa kodeka kończy się na "Codec" i posiada dwie metody statyczne static encode(target) oraz static decode(target). Zapisujemy klasę w katalogu grails-app/utils i od tej pory wykonanie metod encodeAs[nazwa-kodeka]() oraz decode[nazwa-kodeka]() sprowadzi się do wykonania, odpowiednio, metody encode i decode. W książce znajdziemy przykład kodeka do kodowania i dekodowania danych algorytmem Blowfish, np.:
 def encrypted = "This is some secret info".encodeAsBlowfish()
def unencrypted = encrypted.decodeBlowfish()
Następnie wymienia się dostępne "gotowce" do wdrożenia mechanizmów uwierzytelnienia i autoryzacji - Spring Security (dawne Acegi), wtyczki Authentication oraz JSecurity. Ich implementacja opiera się na mechaniźmie filtrów w Grails (nie mylić z filtrami z Java Servlets, aczkolwiek można je skonfigurować podobnie). Filtry przypominają aspekty z AOP, gdzie tworzymy klasę, której metody wykonywane są przed (before) i po (after) akcjach kontrolerów. Tworzymy filtr przez utworzenie klasy w katalogu grails-app/conf, której nazwa kończy się na "Filters". W ramach klasy filtra definiujemy statyczną zmienną właściwość filters z definicjami, gdzie i jaki filtr "przyłożyć" kontrolerom w konwencji:
 class [NazwaKlasyFiltrów]Filters {
static filters = {
[nazwa-filtra1](controller:["*"|"nazwa-kontrolera"], action:["*"|"nazwa-akcji"]) {
before = {
...
}
after = { model ->
...
}
}
[nazwa-filtra2]...
}
}
Możemy użyć symbolu '*' (gwiazdka), aby wskazać wszystkie kontrolery i/lub akcje. De facto, możemy użyć dowolnego wyrażenia regularnego do ich określenia, np. secure(controller:"(admin|secure)", action="*"). Zamiast parametrów controller i action możemy uzyć parametru uri, określającego adres URL, którego dotyczy filtr.

Jeśli domknięcie w before lub after zwróci false oznacza to, że nie będzie wykonana przesłonięta akcja kontrolera.

Domknięcie after wykonywane jest po akcji kontrolera, ale przed wyświetleniem strony GSP. Jeśli jednak chcielibyśmy wykonać filtr po stronie GSP korzystamy z afterView, np. (Listing 14-14):
 after = {
request.currentTime = System.currentTimeMillis()
}
afterView = {
log.debug "View took ${System.currentTimeMillis() - request.currentTime}ms"
}
Zakończenie rozdziału to przedstawienie instalacji i konfiguracji wtyczki JSecurity oraz jej wdrożenie do aplikacji GTunes do stworzenia sekcji "My Music" z możliwością odsłuchania utworów (za pomocą QuickTime opakowanego w tworzony znacznik GSP - media:player).

25 kwietnia 2009

46. spotkanie Warszawa JUG - "Extending Java and developing DSLs with JetBrains MPS" z Konstantin Solomatov

3 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na 46. spotkanie, które odbędzie się 27.04.2009 (poniedziałek) o godzinie 18:00 w sali 5440 Wydziału MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Extending Java and developing DSLs with JetBrains MPS open source language workbench
Prelegent: Konstantin Solomatov (lead developer of the JetBrains MPS project)

Developers in different fields have always wanted to add constructs from their field to a general purpose language in which they program. Doing so directly requires a lot of effort, and often combining such extensions is impossible. JetBrains MPS, an open source language development environment, solves this problem. It enables many language extensions: collections language, dates language, regular expressions language, and closures language. JetBrains MPS not only allows you to extend a language, but also provides IDE services: editor, completion, error highlighting, etc. In addition to extending languages, JetBrains MPS allows you to create domain-specific languages tailored to specific fields. We will create a simple language extension during this talk.

Recent interview: Episode 126: Jetbrains MPS with Konstantin Solomatov

Planowany czas prezentacji to 1,5 godziny, po której planuje się 15-30-minutową dyskusję.

Wstęp bezpłatny!

Zapraszam w imieniu Konstantina i grupy Warszawa JUG!

23 kwietnia 2009

Z rozdziału 13. o wtyczkach w Grails z DGG2

2 komentarzy
Jak to ujęli Graeme i Jeff - autorzy książki "The Definitive Guide to Grails, Second Edition" - we wstępie do rozdziału 13. Wtyczki:

Now it's time to turn the tables and become a plugin author.

Grails jest w zasadzie niczym innym niż środowiskiem uruchomieniowym wtyczek. Grails wie, jak załadować i uruchomić je, a one odwdzięczają mu się nowymi funkcjonalnościami - może to być dodanie nowej metody do istniejących klas w aplikacji lub pełna funkcjonalność biznesowa w stylu blog. Domyślnie Grails jest już dystrybuowany z zestawem wtyczek, np. GORM czy Grails MVC, i bez większego wysiłku możemy stworzyć własne. Do pracy z wtyczkami, np. odszukiwanie i instalacja, Grails udostępnia dedykowany zbiór poleceń.

Dzięki wtyczkom tworzymy moduły funkcjonalne, które możemy ponownie użyć w kolejnych projektach grailsowych. Tworzymy wtyczki, a w zasadzie tworzymy nowe moduły funkcjonalne i włączamy/wyłączamy je w kolejnych projektach.

Wtyczka jest pełnoprawną aplikacją Grails. Jeśli stworzymy aplikację Grails z pewną funkcjonalnością, której będziemy potrzebować w innym projekcie, możemy (i powinniśmy!) stworzyć na jej bazie wtyczkę - dodać deskryptor wtyczki (skrypt Groovy, oczywiście), spakować w plik zip i opublikować, np. na serwerze HTTP.

Odszukiwanie wtyczek to polecenie grails list-plugins, które skontaktuje się z centralnym repozytorium wtyczek i pobierze aktualną ich listę (potrzebne jest połączenie z siecią). Po liście dostępnych wtyczek z centralnego repozytorium wyświetlania jest lista już tych zainstalowanych w projekcie. Na liście znajdziemy pierwszą kolumnę z nazwą wtyczki, kolejną z wersją i ostatnią z opisem wtyczki. Dokładniejsza informacja o wtyczce to polecenie grails plugin-info.

Instalacja wtyczki to polecenie grails install-plugin [nazwa-wtyczki] [wersja-wtyczki].

Domyślnie wtyczka instalowana jest w pojedynczym projekcie. Instalacja globalna, współdzielona między wszystkie projekty, jest możliwa poleceniem grails install-plugin z opcją -global, np. (Listing 13-6):
 grails install-plugin -global code-coverage
Odinstalowanie wtyczki to grails uninstall-plugin [nazwa-wtyczki].

Wtyczki grailsowe są dystrybuowane jako pliki zip, więc możemy zainstalować je po wcześniejszym pobraniu z dowolnego, innego repozytorium lub projektu wykonując polecenie "grails install-plugin" z podaniem ścieżki do pliku zip wtyczki, np. (Listing 13-9):
 grails install-plugin ~/grails-audit-logging-0.3.zip
Możemy również zainstalować wtyczkę, która jest opublikowana na serwerze HTTP podając adres URL do pliku zip.

Tworzenie wtyczki to polecenie grails create-plugin [nazwa-wtyczki]. W katalogu głównym (projektu) wtyczki znajdziemy deskryptor wtyczki, który jest skryptem Groovy z nazwą zakończoną "GrailsPlugin". W ramach klasy-deskryptora wtyczki definiujemy jej metadane - autora, wersję, opis i in. Wszystkie metadane są opcjonalne.

Za pomocą właściowości dependsOn, która jest mapą, określamy wtyczki zależne, np.:
 def dependsOn = [hibernate:"1.1"]
Powyższe definiuje zależność wtyczki od (tak na prawdę GORM, który i tak zawsze jest). Możliwe jest określenie zakresów wersji zależności, np. "1.0 > 1.1" (dowolna wersja między 1.0 i 1.1 włącznie) czy "* > 1.1" (dowolna wersja aż do 1.1 włącznie). Przy instalacji wtyczki z określonym dependsOn, wszystkie zależności zostaną pobrane i zainstalowane automatycznie (dzięki przechodniemu rozwiązywaniu zależności, jak to ma miejsce w Ivy czy Maven).

W deskryptorze dostępne są zmienne odpowiadające zdarzeniom związanym z wtyczkami, którym przypisuje się domknięcie, dzięki któremu wtyczka ma możliwość uczestniczenia (przechwycenia zajścia zdarzenia) w jej cyklu rozwojowym.
  • doWithWebDesriptor - na wejściu domknięcia przekazywany jest XML dla web.xml w postaci GPathResult (dzięki XmlSlurper z Groovy)
  • doWithSpring - możliwość udziału w etapie konfiguracji springowego ApplicationContext; brak parametrów wejściowych
  • doWithDynamicMethods - wykonane po stworzeniu ApplicationContext, gdzie wtyczki mogą dodawać nowe metody do klas grailsowych; przekazywany ApplicationContext na wejściu
  • doWithApplicationContext - stworzony ApplicationContext przekazany na wejściu; możliwość jego modyfikacji
Domyślnie, podczas tworzenia wtyczki, wszystkie wymienione domknięcia są puste.

W ramach zdarzeń (definiowanych jako domknięcia) możemy korzystać z dostępnych zmiennych inicjowanych przez Grails - application (org.codehaus.groovy.grails.commons.GrailsApplication z informacjami o załadowanych klasach i dostępnych artefaktach), manager (org.codehaus.groovy.grails.plugins.GrailsPluginManager do pracy z wtyczkami) oraz plugin (org.codehaus.grails.plugins.GrailsPlugin, który reprezentuje aktualnie konfigurowaną wtyczkę).

Wtyczka jest aplikacją grailsową, więc jej rola może się sprowadzić do dostarczenia pojedynczego kontrolera, biblioteki znaczników czy usługi, albo ich całego zbioru, który dostarcza większej funkcjonalności biznesowej. Korzystamy z normalnych poleceń, jak przy tworzeniu aplikacji grailsowej - create-controller, create-taglib czy create-service. Możemy nawet uruchomić wtyczkę jak zwykłą aplikację grailsową z grails run-app!

Grails umożliwa zdefiniowanie własnych typów artefaktów, podobnie do już istniejących kontrolerów, klas dziedzinowych, usług czy znaczników, np. wtyczka quartz dostarcza własnego typu artefaktowego Job. Wystarczy zaimplementować org.codehaus.groovy.grails.commons.ArtefactHandler lub rozszerzyć org.codehaus.groovy.grails.commons.ArtefactHandlerAdapter, która odpytana odpowiada na pytanie "Czy dany obiekt jest obiektem pewnego artefaktu?" i pozwala na ich właściwe utworzenie. Określenie klasy nowego rodzaju artefaktu to zdefiniowanie właściwości artefacts w deskryptorze wtyczki, np.
 def artefacts = [new JobArtefactHandler()]
Domyślnie stosowane jest podejście - jeśli jesteś w katalogu grails-app/[nazwa] i nazwa kończy się "Nazwa", wtedy artefakt jest typu Nazwa, tzw. duck typing w Groovy (idziesz jak kaczka, kwaczesz jak kaczka, więc jesteś...kaczka). Możemy również sprawdzić, czy posiada odpowiednią metodę i na tej podstawie podjąć decyzję, czy dana klasa jest danym artefaktem czy nie.

Domknięcie doWithSpring pozwala nam na zdefiniowanie nowych ziaren springowych za pomocą grailsowego BeanBuilder i jego DSL dla Spring Framework, np. (Lisitng 13-20):
 class SimpleCacheGrailsPlugin {
def doWithSpring = {
globalCache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
timeToLive = 300
}
}
}
Nazwa ziarna to nazwa metody w ramach doWithSpring, natomiast pierwszy parametr wejściowy to typ ziarna, a kolejny, domknięcie, to sposób na przekazanie parametrów ziarna.

Grails to aplikacja springowa, więc w ramach Grails istnieje pojęcie springowego ApplicationContext. Wszystkie ziarna springowe (również te, z których zbudowany jest Grails) są w nim dostępne. Domyślnie, każde ziarno springowe jest "jedynakiem" (singletonem), a więc co najwyżej jeden egzemplarz ziarna będzie dostępny w systemie. Mechanizm wstrzeliwania zależności Spring Framework dostępny jest w ramach kontrolerów, bibliotek znaczników oraz usług. Wystarczy zadeklarować atrybut o nazwie odpowiadającej nazwie ziarna springowego, aby odpowiedni egzemplarz został przekazany (wstrzelony), np. (Listing 13-21):
 import net.sf.ehcache.Ehcache

class CacheService {
static transactional = false

Ehcache globalCache
}
Groovy, podobnie jak inne języki dynamiczne, np. Smalltalk, Ruby czy Lisp, udostępnia Meta Object Protocol (MOP). Dla każdej klasy java.lang.Class Groovy tworzy klasę-bliźniaka typu MetaClass. Zachowanie metod, konstruktora, właściwości wyznaczane jest właśnie przez MetaClass. Z jego pomocą możemy dynamicznie dodawać nowe metody, właściwości, konstruktory czy metody statyczne do dowolnej klasy w trakcie jej działania, np. (Listing 13-27):
 class Dog {}
Dog.metaClass.bark = { "woof!" }
assert "woof!" == new Dog().bark()
Mając MOPa można..."posprzątać" niezły kawałek z listy zadań ;-) Wystarczy we wtyczce grailsowej skorzystać z domknięcia doWithDynamicMethods, aby dodać pożądaną metodę do wszystkich klas danego typu, np. dla kontrolerów byłoby to (Listing 13-28):
 class SimpleCacheGrailsPlugin {
def doWithDynamicMethods = { applicationContext ->
def cacheService = applicationContext.getBean("cacheService")
application
.controllerClasses
*.metaClass
*.cacheOrReturn = { Serializable cacheKey, Closure callable ->
cacheService.cacheOrReturn(cacheKey, callable)
}
W przykładzie wykorzystane są możliwości Grails, które de facto są odzwierciedleniem możliwości Groovy i Spring Framework w postaci operatora spread (gwiazdka - wykonaj metodę na każdym elemencie z listy), przekazanie (wstrzelenie) zależności cacheService, czy wykorzystanie Closure do dynamicznego przekazania domknięcia, aby możliwe było zapisanie jego wyniku na określony czas w pamięci podręcznej (ang. cache).

Wtyczki mogą reagować na wystąpienie zdarzeń w Grails, tj. onChange, onConfigChange oraz onShutdown. W onChange wtyczka monitoruje zbiór zasobów określonych przez właściwość watchedResources w klasie wtyczki, np.
 def watchedResources = "file:./grails-app/i18n/*.properties"
Właściwość watchedResources działa na bazie org.springframework.core.io.support.PathMatchingResourcePatternResolver oraz pakietu Spring Core IO.

Domknięcie związane z onChange na wejściu dostanie mapę składającą się z source (źródło zdarzenia, które jest typu org.springframework.core.io.Resource dla zasobów innych niż klasy lub java.lang.Class, jeśli obserwuje się klasy Groovy), application (egzemplarz GrailsApplication), manager (egzemplarz GrailsPluginManager) oraz ctx (egzemplarz springowego ApplicationContext), np. (Listing 13-29):
 class QuartzGrailsPlugin {
def watchedResources = "file:./grails-app/jobs/**/*Job.groovy"

def onChange = { event ->
Class changedJob = event.source
GrailsClass newJobClass = application.addArtefact(changedJob)
def newBeans = beans {
"${newJobClass.propertyName}"(JobDetailsBean) {
name = newJobClass.name
jobClass = newJobClass.getClazz()
}
}
new Beans.registerBeans(applicationContext)
}
}
Warto zapoznać się dokładniej z powyższym kodem i zauważyć Grails DSL dla Spring Framework, gdzie zaprezentowano wykonanie metody beans, która przyjmuje domknięcie będącym rozszerzenie konfiguracji Springa w trakcie działania aplikacji. Ponownie, zapożyczone z Groovy, dynamiczne wykonanie metody przez jej nazwę w zmiennej tekstowej. Warto również zwrócić uwagę na wykonanie metody application.addArtefact, które rejestruje nową klasę jako artefakt grailsowy i od tej pory Grails będzie wiedział, jaka jest konwencja nazewnicza zadań quartz'owych. Pretty neat, huh?

Zdarzenie onConfigChange pojawia się przy zmianie głównego pliku konfiguracji aplikacji grailsowej, tj. zmianie grails-app/conf/Config.groovy. Oczywiście, na wejściu onConfigChange dostaniemy się do egzemplarza ConfigObject, który jest również dostępny później jako GrailsApplication.config.

Zdarzenie onShutdown pojawia się, przy wykonaniu metody GrailsPluginManager.shutdown(), np. przy odinstalowaniu aplikacji z kontenera.

Domknięcie doWithWebDescriptor wykonywane jest, po przetworzeniu web.xml przez Grails, a przed faktycznym uruchomieniem aplikacji. Ciekawostką tego rozwiązania jest fakt, że zmiany w web.xml, który jest plikiem XML, dokonujemy za pomocą grailsowego XmlSlurper, który udostępnia Grails DSL do pracy z plikami XML. W zasadzie wykorzystane są jedynie funkcjonalności języka Groovy, co w połączeniu z odpowiednimi nazwami metod sprawia wrażenie czegoś niezwykle odświeżającego (chyba zaczynam się z tym 'odświeżającym' powtarzać?), np. (Listing 13-30):
 def doWithWebDescriptor = {webXml ->
// pobieramy listę wszystkich filtrów przez wykonanie metody getFilter()
def filters = webXml.filter
// przechodzimy za ostatni z filtrów
def lastFilter = filters[filters.size()-1]
// ...i dodajemy własną deklarację filtra z operatorem +
lastFilter + {
// wykonujemy metodę filter, która akceptuje domknięcie będący podelementami filter w XML
filter {
// wykonanie metody filter-name to dodanie elementu filter-name do XMLa
// z wartością urlMapping
'filter-name'('urlMapping')
// podobnie tutaj, ale wartość wyliczana jest dynamicznie
'filter-class'(UrlMappingsFilter.getName())
}
}
...
}
I niech ktoś powie, że to nie jest odświeżające?!

Podobna funkcjonalność, tyle, że statyczna, możliwa jest do uzyskania poleceniem grails install-templates, której podajemy zmodyfikowany szablon web.xml.

Stworzenie paczki dystrybucyjnej wtyczki to polecenie grails package-plugin z poziomu katalogu głównego projektu wtyczki.

Opublikowanie wtyczki w centralnym repozytorium Grails wiąże się z uzyskaniem praw (opisane na stronach Creating, Distributing & Installing) i wydanie polecenia grails release-plugin. Uzyskanie praw do centralnego repo to uzyskanie loginu i hasła do repozytorium SVN, który jest podstawą technologiczną repozytorium wtyczek w Grails. Jeśli chcielibyśmy opublikować wtyczkę we własnym repozytorium wystarczy stworzyć własne repozytorium SVN i wskazać na nie w grails-app/conf/BuildConfig.groovy dla pojedynczej aplikacji lub ogólnie, dla wszystkich, w USER_HOME/.grails/settings.groovy, np. (Listing 13-32):
 grails.plugin.repos.discovery.myRepository="http://foo.bar.com"
grails.plugin.repos.distribution.myRepository="https://foo.bar.com"
Lista adresów w zmiennej discovery wykorzystywana jest przez polecenia list-plugins, install-plugin i plugin-info. Zmienna distribution jest wykorzystywana przez polecenie release-plugin. Wskazanie na zdefiniowany serwer to wykonanie grails release-plugin z opcją repository, której wartością jest nazwa repozytorium, np.
 grails release-plugin -repository=myRepository
Przez cały rozdział autorzy tworzą kilka wtyczek, za pomocą których prezentują ich siłę "krojenia" aplikacji na moduły. Do tego należy dodać sztuczki w Groovy i co jakiś czasy możnaby zejść na serce (ze zdumienia, że wiele z tych usprawnień nie ma co szukać w "czystej" Javie i alternatywnych szkieletach webowych), np. (Listing 13-37):
 albumArtService.cacheService = [cacheOrReturn:{key, callable -> callable() }]
które zaślepia niedostępność mechanizmu wstrzeliwania zależności w klasach-testach jednostkowych. Za pomocą "duck typing" w Groovy "przesłaniamy" wywołanie metody cacheOrReturn() na zmiennej cacheService w obiekcie albumArtService. W Groovy możemy zdefiniować mapę, której klucz wskazuje na przesłanianą metodę, a wartość jej implementację. Cudo!

Dodanie dodatkowej metody do wszystkich kontrolerów w domknięciu doWithDynamicMethods, to (Listing 13-38):
 class AlbumArtGrailsPlugin {
def doWithDynamicMethods = { ctx ->
def albumArtService = ctx.getBean("albumArtService")

application.controllerClasses
*.metaClass
*.getAlbumArt = { String artist, String album ->
return albumArtService.getAlbumArt(artist, album)
}
}
Jeślibyśmy zamiast application.controllerClasses użyli application.domainClasses rozszerzanie dotyczyłoby klas dziedzinowych.

Tworzenie widoków GSP jako części składowej wtyczek wymaga, aby użycie <g:render> wskazywało na wtyczkę, z której zostało wywołane za pomocą atrybutu plugin, gdyż w przeciwnym przypadku Grails próbowałby odszukać szablonu w ramach samej struktury katalogowej aplikacji, np. (Listing 13-44):
 <g:render plugin="blog"
template="post"
var="post"
collection="${postList?.reverse()}" />
Dostęp do wartości zmiennej konfiguracyjnej zdefiniowanej w grails-app/conf/Config.groovy w stronie GSP to ${grailsApplication.config.[nazwa-zmiennej]}, tj.
 <h1>${grailsApplication.config.blog.title ?: 'No Title'}</h1>
Po instalacji wtyczki twórca aplikacji może zdefiniować zmienną "blog.title" w Config.groovy, nadpisując tym samym domyślną wartość. Innym rozwiązaniem mogłoby być skorzystanie ze znacznika <g:message> i użycie plików grails-app/i18n/messages*.properties.

Jak to ujęli autorzy: "Plugins are definitely worth a look, even if you don't intend to become an expert on Grails internals". Wiedzą, co piszą! Zgadzam się z tym w 100%.

UWAGA: Właśnie się dowiedziałem od mojego syna - OBCY istnieją! Po co w takim razie byłyby tabliczki "OBCYM wstęp wzbroniony!"?! ;-)

21 kwietnia 2009

Który szkielet aplikacyjny polecasz?

25 komentarzy
Dostałem dzisiaj wiadomość z grupy J Architects na LinkedIn o głosowaniu "Which one of the following is the best application framework". Jak można się domyśleć, moim wybranym na chwilę obecną jest Grails, więc nie miałem żadnych wątpliwości, na co postawić. Na razie Grails przoduje z 33% odpowiedzi, przed JBoss Seam i Wicket.

Poza Grails w szranki i konkury stanęli: JBoss Seam, Rails, Wicket i Tapestry. Sądzę, że wielu z głosujących nie ma bladego pojęcia, co można, a czego nie w pozostałych poza tym wskazanym (ja również należę do tej grupy), a i rozkład procentowy mógłby być inny w zależności od kryteriów (a może kryterii?!). Nie mniej jednak, dla mnie to był łatwy wybór, bo jestem na bieżąco z Grails i faktycznie odświeżył moje spojrzenie na tworzenie aplikacji webowych. Coś mnie tknęło (przez baaaardzo krótki moment), aby zagłosować na Wicketa, ale tak szybko, jak przyszło, szybko sobie poszło. Bardzo mi się podobał, ale podobnie jak z Grails, mam na jego temat bardzo powierzchowną wiedzę na bazie kilku domowych przykładów typu "Hello World" i tylko jedną...przeczytaną książkę. W przypadku Grails chociaż liczba książek jest większa (już dwie, a czekają kolejne!) i Groovy nie pozostawia złudzeń, co lubić. Pozostaje jeszcze JBoss Seam, który podobał mi się początkowo, bo łączył EJB3 i JSF, którymi się "upajałem" swego czasu, ale teraz?! Chyba nie robi na mnie wrażenia. W zasadzie jest powieleniem tego, co mam w Grails. Czasy pewnego zainteresowania JBoss Seam mam za sobą i teraz uważam, że EJB3+JSF są dobre jedynie dlatego, że są...specyfikacjami. Są jednak ciekawsze (lepsze?) rozwiązania, jak chociażby Groovy i wszechobecne DSLe w Grails, a nawet Wicket. JBoss Seam zawsze wydawał mi się jakiś przekombinowany i stosunkowo trudny w poznaniu (no pun intended! :)).

W głosowaniu znajdziemy również Rails i Tapestry, i jakkolwiek książka o Tapestry czeka na mnie, i potencjalnie będę miał na nią chęć, to o Rails nie mogę już tego powiedzieć. Zresztą Tapestry to dla wielu Wicket, a i nie inaczej jest z Grails i Rails. Jedynym powodem, dla którego rozważam zapoznanie się z Rails jest sam język Ruby (dopiero teraz zauważyłem, jak niedaleko jest między Grails a Rails - nawet nazwy języków są zbliżone - Ruby i Groovy. Ciekawe komu odbije się to czkawką?). Coraz częściej dochodzą mnie głosy, że nie ma co poświęcać czasu Railsom, skoro są Grailsy i że to dla ludzi ze świata PHP (chociaż Chlebik może sugerować inne myślenie migrując na Grailsy). Argument poznania kolejnego języka - Ruby - jest mimo wszystko zachęcający. W tym właśnie klimacie spoglądam na Scalę. Pisze się o nim (niej?) to tu, to tam i nawet pojawił się w komentarzu do głosowania jako...Lift (szkielet webowy w Scali). Chyba tylko nawał oczekujących mnie książek nie pozwala mi zająć się nim. Niech czeka cierpliwie (jak wielu klientów, którzy są skłonni zapłacić dobrze za dobrze wykonany projekt, a ostatecznie dostają jedynie dobre składowe z całością przypominającą Goliata - ufam, że moi tego nie doświadczają :)). Kiedy wczoraj przeglądałem prezentację integracji MS Excel i GlassFisha, a właściwie środowiska .Net i Java EE za pomocą WSIT z uwzględnieniem WS-Security (patrz prezentacja Aruna Gupty Excel using WSIT - Metro and .NET interoperability sample), dopiero otworzyłem oczy, jakie to cuda można wyczyniać znając różne rozwiązania, dobrane do problemu. To bodaj najstarszy problem informatyczny, aby dobierać właściwe narzędzia do problemu. Podoba mi się stwierdzenie "Kiedy masz pod ręką tylko młotek, wszystko dookoła wygląda jak gwóźdź". Ciekawe kto jest autorem tego powiedzenia, które tak trafnie oddaje stosunkowo powszechne podejście do rozwiązywania problemów. Przy tylu dostępnych szkieletach aplikacyjnych, aż dziw bierze, że udaje nam się wdrożyć tę maksymę w życie (a może właśnie, dlatego, że jest ich tak wiele, tak wielu się udaje?!). Przy okazji przeszukiwania Sieci w poszukiwaniu odpowiedzi, kto jest autorem trafiłem na bloga Nie tylko młotek - zapowiada się interesująco, bo na początek idzie Haskell i...Ruby!). O problemie młotka pisał ostatnio również i Sławek Sobótka na swoim blogu Holistyczna inżynieria oprogramowania - chociażby we wpisie DAO. Wiosenne porządki zostawiają odciski na rękach (po nieumiejętnym trzymaniu młotka) i dają się we znaki, co?!

Zainteresowanych głosowaniem zapraszam do mojego - "Który szkielet aplikacyjny polecasz?". Znajdziesz go na moim blogu w panelu po prawej. Przy 800 regularnych czytelnikach powinno się choć trochę wysondować polskie spojrzenie na temat. Do głosowania wchodzą wszystkie wymienione i kilka innych szkieletów aplikacyjnych, które przyszły mi do głowy (umieściłem nawet smalltalkowy Seaside, którym byłbym zainteresowany, gdyby ktoś tylko zechciał mi to wyłożyć - może temat na bloga?). Dodałem również kategorię "Inne", aby tam zgromadzić wiedzę na temat wykorzystania alternatywnych rozwiązań, być może wciąż niszowych, ale przyszłościowych. Pozwólcie mi poznać Wasze gusta, aby samemu zasmakować najlepszego i...oferować klientom jako własne za niewielką opłatą! ;-)

20 kwietnia 2009

Bezpieczne programowanie na CONFidence 2009 & OWASP EU

0 komentarzy
Skontaktował się ze mną organizator CONFidence 2009 i tak oto rzecze:

Słuchaj Jacku mam prośbę o umieszczenie u Ciebie na blogu informacji o konferencji CONFidence tym bardziej, że:
- zrobiliśmy ścieżkę wyłącznie o aplikacjach web i bazach danych
- przed CONFidence odbędzie się konfernecja OWASP EU (http://www.owasp.org)
Proszę bo zadziwia mnie, że security wciąż w Polsce kojarzy się tylko z administratorami, kiedy cały świat już przejrzał na oczy i uważa to za bardziej złożony problem. Co ciekawe na konfernecje OWASP jest całe 10 osób z Polski, a pozostałe 100 z zagranicy. Na naszego CONFidence jest 300 osób ale może 10 programistów


Na co ja odpowiedziałem, że w zasadzie ja również przynależę do tego szanownego grona ignorantów bezpiecznego programowania i nawet nie wiem, dlaczego powinienem się tym zajmować. Zajmuję się obecnie tematem WS-Security, ale to ponownie bezpieczeństwo na poziomie komunikatów SOAP i jest gotowe (jestem klientem nie dostawcą), a dodając do tego możliwość bezpiecznej transmisji z SSL/TLS, to o co kruszyć kopię?! Pomyślałem jednak, że wesprę choć ogłoszeniem na moim blogu próbę zmiany postrzegania bezpieczeństwa jako czegoś co jest integralnie związane z programowaniem. I oto jest ogłoszenie CONFidence 2009:

W maju w Krakowie odbędzie się 5 edycja konferencji CONFidence. W tym roku udało się nam przekonać do zorganizowania konferencji OWASP EU, która dopełni tydzień bezpieczeńśtwa w Polsce.

CONFidence jest podzielony na niezależne ścieżki dla programistów i administratorów systemów. To więcej niż konferencja, to bardziej spotkanie z osobami, dla których bezpieczne i estetyczne kodowanie jest ważne. W trakcie konferencji odbędzie się masa imprez towarzyszących – turniej robotów, nauka locpickingu, konkurs Capture the Flag. Jeśli jesteś prawdziwym progamistą, interesuje Cię co się dokładnie dzieje wewnątrz systemu, umiesz spojrzeć na aplikację głębiej niż szeregowy koder, nie zgadzasz się ze stwierdzeniami z bloga CONFidence musisz wziąć udział w majowej konferencji.
Wystąpią: Bruce Schneier (doradca Kongresu USA ds. bezpieczeństwa), Tavis Ormandy (security inżynier Google), Joanna Rutkowska, Jacob Appelbaum, Marc Schoenefeld (specjaslita bezpieczeństwa Javy) i wielu innych.

O przydatności konferencji niech świadczą materiały z poprzednich lat dostępne na stronie http://confidence.org.pl.

Z rozdziału 12. o integracji Grails z DGG2

0 komentarzy
W rozdziale 12. "Integrating Grails" dowiadujemy się o włączaniu Grails do istniejących środowisk - systemu budowania, środowisk IDE, konfigurację raportów jakościowych naszego kodu i środowiska serwerowego. Wszystko to, co sprawia, że tworzenie aplikacji Grails jest łatwiejsze, przyjemniejsze, ale również wysokiej jakości.

Siłą Grails jest podejście "konwencja ponad konfigurację", więc przy zachowaniu odpowiednich reguł nie potrzeba nic konfigurować. Mimo wszystko, Grails nie wzbrania nas przez konfiguracją, jeśli mamy taką potrzebę. Jak to ujęli autorzy - konwencja ponad konfigurację, ale nie zamiast niej (ang. "Crucially, however, it's convention over configuration, not instead of it"). Do mnie przemawia.

Konfiguracja ogólna dla całej aplikacji Grails znajduje się w grails-app/conf/Config.groovy. Jest to skrypt Groovy przypominający plik Java typu properties - seria klucz-wartość. Ustawiamy zmienne korzystając z operatora dereferencji (kropka), np.:
 grails.views.gsp.encoding="UTF-8"
Dostęp do tej zmiennej w poziomu aplikacji jest możliwy przy pomocy właściwości config egzemplarza grailsApplication, który jest dostępny w kontrolerach i widokach, np.:
 assert grailsApplication.config.grails.views.gsp.encoding == "UTF-8"
Możemy grupować konfigurację korzystając z bloków, np. (Listing 12-1):
 grails.mime {
file.extensions = true
types = [html: 'text/html']
}
Przy takiej konfiguracji zostaną utworzone dwa wpisy konfiguracyjne w config - grails.mime.file.extensions oraz grails.mime.types.

Istnieje możliwość konfiguracji per środowisko, np.:
 // set per-environment serverURL stem for creating absolute links
environments {
production {
grails.serverURL = "http://www.changeme.com"
}
development {
grails.serverURL = "http://www.rozwojowy.com"
}
}
System komunikatów oparty jest o log4j. Grails upraszcza jego konfigurację z własnym, dedykowanym DSL. Wystarczy zadeklarować zmienną log4j i przypisać jej domknięcie, które stanie się konfiguracją log4j, np.:
 // log4j configuration
log4j = {
// Example of changing the log pattern for the default console
// appender:
//
//appenders {
// console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
//}

error 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages', // GSP
'org.codehaus.groovy.grails.web.sitemesh', // layouts
'org.codehaus.groovy.grails."web.mapping.filter', // URL mapping
'org.codehaus.groovy.grails."web.mapping', // URL mapping
'org.codehaus.groovy.grails.commons', // core / classloading
'org.codehaus.groovy.grails.plugins', // plugins
'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
'org.springframework',
'org.hibernate'

warn 'org.mortbay.log'
}
Jest to domyślna konfiguracja log4j z grails-app/conf/Config.groovy. W ramach domknięcia wywołujemy metody off, fatal, error, warn, info, debug, trace, all (nazwy odpowiadają poziomom komunikatów) z parametrami będącymi listą nazw pakietów, które mają mieć wskazany poziom.

W każdym kontrolerze, usłudze i bibliotece znaczników dostępna jest właściwość log. Domyślna konfiguracja to poziom error. Nasze artefakty grailsowe możemy ustawiać na inne przez wskazanie nazwy klasy, poprzedzonej grails.app, np. (Listing 12-4):
 log4j = {
debug 'grails.app.controller.UserController',
'grails.app.service.AlbumArtService'
}
Domyślnie komunikaty odnotowywane są na standardowym wyjściu. Grails DSL dla log4j pozwala na stworzenie własnego wyjścia (ang. appender) z blokiem appenders, np. (Listing 12-5):
 log4j = {
appenders {
rollingFile name:"myLog",
file:"/var/log/gtunes.log",
maxFileSize:"1MB",
layout: pattern(conversionPattern: '%c{2} %m%n')
}
...
}
Dodatkowe "wyjścia" to jdbc (org.apache.log4j.jdbc.JDBCAppender), null (org.apache.log4j.varia.NullAppender), console (org.apache.log4j.ConsoleAppender), file (org.apache.log4j.FileAppender) i rollingFile (org.apache.log4j.RollingFileAppender).

Z tak zdefiniowanym "wyjściem" możemy związać go z właściwymi pakietami, np. (Listing 12-6):
 log4j = {
...
trace myLog:'org.hibernate'
debug mylog:['org.codehaus.groovy.grails.web.mapping.filter',
'org.codehaus.groovy.grails.web.mapping']
}
Nazwa "wyjścia" odpowiada wartości parametru name z appenders.

W przypadku wystąpienia wyjątku w Grails, stos wywołań Javy czyszczony jest z wewnętrznych wywołań Groovy i Grails. Jednakże, pełny stos wywołań zapisywany jest do pliku stacktrace.log w katalogu głównym projektu. Nadpisanie tej konfiguracji to wskazanie kanału "StackTrace" na odpowiednie "wyjście" i poziom, np. (Listing 12-7):
 log4j = {
appenders {
rollingFile name:"stacktraceLog",
file:"/var/log/unfiltered-stacktraces.log",
maxFileSize:"1MB",
layout: pattern(conversionPattern: '%c{2} %m%n')
}
error stacktraceLog: "StackTrace"
}
Całkowite wyłączenie wypisywania stosu wywołań to ustawienie zmiennej logicznej grails.full.stacktrace, np.
 grails -Dgrails.full.stacktrace=false run-app
Podczas rozwoju aplikacji, plik Config.groovy kompilowany jest do klasy i włączany do archiwum WAR. Istnieje możliwość wyniesienia konfiguracji poza archiwum przez ustawienie właściwości grails.config.location w Config.groovy, która zawiera listę skryptów Groovy, które po połączeniu razem ustanowią konfigurację aplikacji, np. (Listing 12-8):
 grails.config.locations = ["file:${userHome}/gtunes-logging.groovy"]
Jakkolwiek pliki (skrypty) konfiguracyjne Config.groovy i DataSource.groovy są oddzielnymi plikami, Grails i tak ostatecznie łączy je w pojedynczy obiekt konfiguracyjny podczas uruchomienia aplikacji. W ten sposób możemy również wynieść jakąkolwiek konfigurację poza archiwum, np. konfigurację dostępu do bazy danych (Listing 12-9):
 grails.config.locations = ["file:${userHome}/.settings/gtunes-logging.groovy",
"file:${userHome}/.settings/gtunes-datasource.groovy"]
Jeśli interesuje nas konfiguracja za pomocą plików properties (zamiast skryptów Groovy) wystarczy podać w grails.config.locations plik o rozszerzeniu .properties.

Grailsowy system budowania oparty jest na Gant, który z kolei opakowuje Apache Ant. Nie powinno być zaskoczeniem, że Gant korzysta z Groovy DSL do tworzenia skryptów budowania, np. (wycinek z Listing 12-10):
 targetDir = "build"
target(clean:"Cleans any compiled sources") {
delete(dir:targetDir)
}
target(compile:"The compilation task") {
depends(clean)
mkdir(dir:"$targetDir/classes")
javac(srcdir:"src/java"
destdir:"$targetDir/classes")
}
...
setDefaultTarget(clean)
Odpowiednikiem antowego <target> jest metoda target, zależności między zadaniami buduje się za pomocą depends i każde zadanie w Ant ma swój odpowiednik w Gant DSL.

Wykonanie Gant poza Grails to uruchomienie polecenia gant. Głównym plikiem (skryptem) Gant jest build.gant.

Grails opakowuje Gant we własne polecenie grails, który jest zoptymalizowany do pracy z układem katalogów w Grails. Wykonując jakiekolwiek polecenie grails, Grails poszukuje skryptu do wykonania w kolejności PROJECT_HOME/scripts, GRAILS_HOME/scripts, PLUGINS_HOME/*/scripts i USER_HOME/.grails/scripts. Wykonywany jest domyślne zadanie skryptu. Wykonanie grails create-app poszukuje CreateApp.groovy we wskazanych katalogach (pierwszy wygrywa) - już powinno być jasne, jaka jest konwencja (łatwiej było mi przedstawić na przykładzie niż opisać).

Tworzenie własnych poleceń jest możliwe za pomocą grails create-script [nazwa-polecenia]. W książce pojawiają się dokładnie opisane przykłady tworzenia własnego polecenia (chociażby tworzenie polecenia do wdrażania projektu na Apache Tomcat), jednakże sama analiza dostępnych skryptów w GRAILS_HOME/scripts będzie niemniej wartościową lekcją (zresztą, analiza już stworzonych i działających aplikacji zawsze jest wartościowa - bez względu na poziom zaawansowania programisty, aczkolwiek, czym bardziej zaawansowany tym lepiej).

Podczas uruchomienia skryptów Groovy, Grails zapisuje ich klasy w katalogu USER_HOME/.grails/[wersja-grails]/projects/[nazwa-projektu]/classes. Za pomocą zmiennej grails.work.dir możemy zmienić położenie tego katalogu, np.
 grails -Dgrails.work.dir=/tmp run-app
Warto pamiętać, że polecenie grails nie wspiera łączenia zadań, jak gant czy ant, tj. możliwe jest wykonanie jedynie pojedynczego polecenia w danej chwili.

Ciekawostką, która powinna uprościć nam wdrożenie grailsowego systemu budowania do już istniejącej infrastruktury opartej na Apache Ant (tym samym i Apache Maven, i Apache Ivy, który de facto jest wykorzystywany i tak w Grails) jest możliwość wykorzystania tworzonego automatycznie build.xml z katalogu głównego projektu. On z kolei wywołuje polecenia Grails (wymagana jest instalacja Grails). Wystarczy wykonać polecenie ant test w katalogu głównym projektu, aby przekonać się o tym, np.
 $ ant test
Buildfile: build.xml

download-ivy:

-download-ivy:

init-ivy:

-resolve:
No ivy:settings found for the default reference 'ivy.instance'. A default instance will be used
[ivy:retrieve] :: Ivy 2.0.0 - 20090108225011 :: http://ant.apache.org/ivy/ ::
:: loading settings :: file = c:\projs\sandbox\grailsmysql\ivysettings.xml
[ivy:retrieve] :: resolving dependencies :: org.example#grailsmysql;working@work
...
---------------------------------------------------------------------
| | modules || artifacts |
| conf | number| search|dwnlded|evicted|| number|dwnlded|
---------------------------------------------------------------------
| build | 47 | 0 | 0 | 3 || 44 | 0 |
| compile | 35 | 0 | 0 | 4 || 31 | 0 |
| test | 37 | 0 | 0 | 5 || 32 | 0 |
| runtime | 40 | 0 | 0 | 5 || 35 | 0 |
---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: org.example#grailsmysql
[ivy:retrieve] confs: [build, compile, test, runtime]
[ivy:retrieve] 0 artifacts copied, 142 already retrieved (0kB/79ms)

-init-grails:

test:
[grailsTask] Running pre-compiled script
[grailsTask] Environment set to test
[grailsTask] [groovyc] Compiling 1 source file to
C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[grailsTask]
[grailsTask] [mkdir] Created dir: C:\projs\sandbox\grailsmysql\test\reports\html
[grailsTask]
[grailsTask] [mkdir] Created dir: C:\projs\sandbox\grailsmysql\test\reports\plain
[grailsTask]
[grailsTask]
[grailsTask] Starting unit tests ...
[grailsTask] Running tests of type 'unit'
[grailsTask] -------------------------------------------------------
[grailsTask] Running 2 unit tests...
[grailsTask] Running test pl.jaceklaskowski.grails.KsiazkaControllerTests...PASSED
[grailsTask] Running test pl.jaceklaskowski.grails.KsiazkaTests...PASSED
[grailsTask] Tests Completed in 875ms ...
[grailsTask] -------------------------------------------------------
[grailsTask] Tests passed: 2
[grailsTask] Tests failed: 0
[grailsTask] -------------------------------------------------------
[grailsTask]
[grailsTask] Starting integration tests ...
[grailsTask] [groovyc] Compiling 1 source file to
C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[grailsTask]
[grailsTask] [groovyc] Compiling 1 source file to
C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[grailsTask]
[grailsTask] Running tests of type 'integration'
[grailsTask] No tests found in test/integration to execute ...
[grailsTask] [junitreport] Processing
C:\projs\sandbox\grailsmysql\test\reports\TESTS-TestSuites.xml to c:\temp\null276154924
[grailsTask]
[grailsTask] [junitreport] Loading stylesheet
jar:file:/c:/projs/sandbox/grailsmysql/lib/build/ant-junit-1.7.1.jar!/org/apache/tools/ant/taskdefs/optional/junit/xsl/junit-frames.xsl
[grailsTask]
[grailsTask] [junitreport] Transform time: 844ms
[grailsTask]
[grailsTask] [junitreport] Deleting: c:\temp\null276154924
[grailsTask]
[grailsTask]
[grailsTask] Tests PASSED - view reports in C:\projs\sandbox\grailsmysql\test\reports.

BUILD SUCCESSFUL
Total time: 22 seconds
Innym sposobem uruchomienia grails z poziomu skryptu w Ant jest zaimportowanie pliku GRAILS_HOME/src/grails/grails-macros.xml, w którym definiuje się zadanie <grails>, np.:
 <property environment="env" />
<import file="${env.GRAILS_HOME}/src/grails/grails-macros.xml" />
...
<grails command="test-app" />
Zadanie grails poszukuje Grails, aczkolwiek instalacja nie jest konieczna, gdyż wystarczy skorzystać z <extend-classpath>, aby wskazać na pliki jar Grails.

Domyślnie Grails nie udostępnia raportów pokrycia testami (ang. code coverage reports). Istnieje dedykowana do tego zadania wtyczka code-coverage. Po jej instalacji (grails install-plugin code-coverage) wystarczy wykonać polecenie test-app, aby uruchomić testy (jednostkowe i integracyjne), po których zostaną utworzone raporty (w książce wspomina się o poleceniu test-app-cobertura, jednakże sprawdziłem, że obecna wersja wtyczki działa na mechaniźmie zdarzeń Gant i nie dodaje własnego polecenia - integracja powinna być na tyle przeźroczysta, jak to tylko możliwe, aby nie było konieczności uczenia się nowych poleceń).
 $ grails install-plugin code-coverage
Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\grailsmysql
Running script c:\apps\grails\scripts\InstallPlugin.groovy
Environment set to development
Reading remote plugin list ...
Reading remote plugin list ...
Plugin list out-of-date, retrieving..
[delete] Deleting: C:\Documents and Settings\jlaskowski\.grails\1.1\plugins-list-default.xml
[get] Getting: http://plugins.grails.org/.plugin-meta/plugins-list.xml
[get] To: C:\Documents and Settings\jlaskowski\.grails\1.1\plugins-list-default.xml
......................
[get] last modified = Sun Apr 19 21:57:32 CEST 2009
[get] Getting: http://plugins.grails.org/grails-code-coverage/tags/RELEASE_1_1_5/grails-code-coverage-1.1.5.zip
[get] To: C:\Documents and Settings\jlaskowski\.grails\1.1\plugins\grails-code-coverage-1.1.5.zip
.....................
[get] last modified = Sat Mar 07 14:47:44 CET 2009
[copy] Copying 1 file to C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\plugins
Installing plug-in code-coverage-1.1.5
[mkdir] Created dir: C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\plugins\code-coverage-1.1.5
[unzip] Expanding:
C:\Documents and Settings\jlaskowski\.grails\1.1\plugins\grails-code-coverage-1.1.5.zip into
C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\plugins\code-coverage-1.1.5
Executing code-coverage-1.1.5 plugin post-install script ...
Plugin code-coverage-1.1.5 installed
Found events script in plugin code-coverage
Teraz wystarczy uruchomić polecenie grails test-app
 $ grails test-app
Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\grailsmysql
Running script c:\apps\grails\scripts\TestApp.groovy
Environment set to test
[groovyc] Compiling 1 source file to C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[mkdir] Created dir: C:\projs\sandbox\grailsmysql\test\reports\html
[mkdir] Created dir: C:\projs\sandbox\grailsmysql\test\reports\plain
Instrumenting classes for coverage ...
[cobertura-instrument] Cobertura 1.9 - GNU GPL License (NO WARRANTY) - See COPYRIGHT file
[cobertura-instrument] Instrumenting 2 files
[cobertura-instrument] Cobertura: Saved information on 2 classes.
[cobertura-instrument] Instrument time: 125ms

Starting unit tests ...
Running tests of type 'unit'
-------------------------------------------------------
Running 2 unit tests...
Running test pl.jaceklaskowski.grails.KsiazkaControllerTests...PASSED
Running test pl.jaceklaskowski.grails.KsiazkaTests...PASSED
Tests Completed in 782ms ...
-------------------------------------------------------
Tests passed: 2
Tests failed: 0
-------------------------------------------------------

Starting integration tests ...
[copy] Copying 1 file to C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\test-classes\integration
[mkdir] Created dir: C:\projs\sandbox\grailsmysql\web-app\plugins\code-coverage-1.1.5
[copy] Copying 28 files to C:\projs\sandbox\grailsmysql\web-app\plugins\code-coverage-1.1.5
[groovyc] Compiling 1 source file to C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[copy] Copying 1 file to C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[groovyc] Compiling 1 source file to C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
Running tests of type 'integration'
No tests found in test/integration to execute ...
[junitreport] Processing C:\projs\sandbox\grailsmysql\test\reports\TESTS-TestSuites.xml to c:\temp\null727040804
[junitreport] Loading stylesheet
jar:file:/c:/apps/grails/lib/ant-junit-1.7.0.jar!/org/apache/tools/ant/taskdefs/optional/junit/xsl/junit-frames.xsl
[junitreport] Transform time: 2672ms
[junitreport] Deleting: c:\temp\null727040804

Tests PASSED - view reports in C:\projs\sandbox\grailsmysql\test\reports.
Cobertura: Loaded information on 2 classes.

--------------------------------------------
***********WARNING*************
Unable to flush code coverage data.
This usually happens when tests don't actually test anything;
e.g. none of the instrumented classes were exercised by tests!
--------------------------------------------

[mkdir] Created dir: C:\projs\sandbox\grailsmysql\test\reports\cobertura
[cobertura-report] Cobertura 1.9 - GNU GPL License (NO WARRANTY) - See COPYRIGHT file
[cobertura-report] Cobertura: Loaded information on 2 classes.
[cobertura-report] Report time: 187ms
Done with post processing reports in 15ms
[delete] Deleting: C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\resources\web.xml
[delete] Deleting directory C:\projs\sandbox\grailsmysql\web-app\plugins
[delete] Deleting directory C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\classes
[delete] Deleting directory C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\resources
[delete] Deleting directory C:\Documents and Settings\jlaskowski\.grails\1.1\projects\grailsmysql\test-classes
Cobertura Code Coverage Complete (view reports in: C:\projs\sandbox\grailsmysql\test\reports/cobertura)
i podziwiać stworzony raport:

Kolejnym tematem w książce jest przedstawienie podejścia ciągłej integracji (ang. CI - continuous integration) na przykładzie integracji Grails i Hudson (o CI pisał ostatnio Łukasz Lenart w Ciągła Integracja (CI) - o co chodzi?), po którym kilka stron omówienia narzędzi IDE, rozpoczynając od IntelliJ IDEA ("By far the most complete IDE for Grails available at the moment is JetBrains' Intellij IDEA with the JetGroovy plugin installed"), NetBeans ("Of the open source IDEs available, NetBeans provides the most advanced support for Groovy and Grails development"), Eclipse ("It is still under active development") i TextMate ("If you happen to be lucky to work on a Mac (cue flame wars!), then you can take advantage of the excellent support for Groovy and Grails in the TextMate text editor"). Autorzy przedstawiają integrację aplikacji Grails z serwerem pocztowym, wykonywanie zadań z wtyczką Quartz, aby na zakończenie omówić temat wdrażania aplikacji Grails na serwer i inicjowanie bazy danych.

Najprostszą opcją wdrożenia aplikacji Grails na serwerze aplikacyjnym jest zainstalowanie Grails na serwerze produkcyjnym i uruchomienie aplikacji poleceniem grails run-war, które uruchamia serwer Jetty na porcie 8080. Stawiamy Apache HTTP Server z wtyczką mod_proxy i mamy działające rozwiązanie. Z opcją server.port możemy uruchomić serwer na wskazanym porcie, np.
 grails -Dserver.port=80 run-war
Alternatywą jest stworzenie pliku WAR (polecenie grails war) i samodzielne wdrożenie na serwer. Zaleca się ustawienie opcji -server w JVM serwera, zwiększenie PermGen (-XX:MaxPermSize=256m) oraz samej pamięci dla aplikacji (-Xmx512m). Strojenie aplikacji i konfiguracji serwera zdecydowanie zalecane.

Początkowo, stworzony plik WAR poleceniem grails war ma w nazwie domyślny numer wersji - 0.1. Zmiana wersji aplikacji to grails set-version [numer-wersji]. Istnieje możliwość poznania numeru wersji przy uruchomionej aplikacji przez
 println grailsApplication.metadata."app.version"
albo korzystając ze znacznika <g:meta> w GSP, np. (Listing 12-50):
 Version: <g:meta name="app.version" />
Build with Grails <g:meta name="app.grails.version" />
Wskazanie na własny web.xml to zmiana grails.config.base.webXml w grails-app/conf/Config.groovy, np.:
 grails.config.base.webXml="file:${userHome}/.settings/my-web.xml"
Zmiana miejsca, gdzie zapisany jest wynikowy war to grails.war.destFile, zmiana jego zawartości to grails.war.copyToWebApp (zmienna, która przyjmuje domknięcie z konstrukcjami gantowymi) oraz grails.war.resources (z domknięciem, którego parametrem wejściowym jest stagingDir, gdzie piszemy ala Gant).

Rozdział kończy się tematem wypełniania bazy danych z pomocą skryptu grails-app/conf/BootStrap.groovy z dwoma zmiennymi init oraz destroy, uruchamiane, odpowiednio, przy uruchomieniu serwera, a tym samym i aplikacji i jej zatrzymaniu (niegwarantowane, np. przy padzie serwera). Wystarczy wykonać kilka GORMowych konstrukcji z save() do zapisania zmian i baza przygotowana do pracy. Tworzenie danych w bazie można warunkować środowiskiem za pomocą zmiennej grails.util.GrailsUtil.environment, np. (Listing 12-54):
 def init = {
switch(grails.util.GrailsUtil.environment) {
case "development":
def album = new Album(title:"Because of the Times")
.addToSongs(title:"Knocked Up")
...
def Artist(name:"Kings of Leon")
.addToAlbums(album)
.save(flush:true)
break
case "production":
// tutaj inne "wstawki" produkcyjne
break
}
}
Zamiast nazw można użyć stałych z grails.util.Environment. Okazuje się nawet, że opisany w książce sposób oznaczony jest jako...niezalecany (ang. deprecated) - grails.util.GrailsUtil.getEnvironment()! Zdumiewające.

19 kwietnia 2009

Rozdział 11. o usługach z DGG2

2 komentarzy
Rozdział 11. "Services" w książce "The Definitive Guide to Grails, Second Edition" omawia temat klas usługowych (usług) w Grails. Na bazie klas usługowych tworzy się warstwę usługową systemu z określonym API. Usługą nazwiemy po prostu pewną czynność biznesową, dla której w ogóle tworzymy system. Każdy system ma taką warstwę, mniej lub bardziej związaną z warstwą widoku czy kontrolerów (patrząc przez pryzmat wzorca MVC (Model-View-Controller)).

Grails opiera swoje działanie na pracy Spring Framework i naturalnie Spring stał się idealnym kandydatem dla zbudowania warstwy usługowej.

Klasy usługowe, podobnie jak inne klasy (poza testami jednostkowymi i integracyjnymi), nie rozszerzają jakiejkolwiek klasy bazowej i znajdują się w katalogu grails-app/services.

Polecenie do tworzenia klas usługowych: grails create-service [nazwa-klasy-usługowej].

Usługi są singletonami (przydałby się jakiś chwytliwy polski odpowiednik), co oznacza, że mamy do dyspozycji wyłącznie pojedynczy egzemplarz i stan, jeśli istnieje, współdzielony jest ze wszystkimi wywołaniami klienckimi.

W Spring Framework mamy do dyspozycji automatyczne wiązanie zależności (ang. autowiring) po nazwie lub typie. Podobnie działa tworzenie egzemplarzy usług w Grails. Wystarczy zadeklarować atrybut o nazwie klasy usługowej (rozpoczynając od małej litery), aby skorzystać z automatycznego wiązania po nazwie. Podczas uruchomienia atrybut zostanie zainicjowany przez Grails odpowiednim egzemplarzem usługi, np. (Listing 11-3):
 class StoreController {
def storeService
}
W tym przypadku korzystamy z dynamicznego wyznaczenia typu, ale istnieje możliwość jawnego podania typu klasy usługi.

Mechanizm automatycznego wiązania zależności dostępny jest w kontrolerach, usługach i znacznikach GSP.

Nie zalecane jest samodzielne tworzenie egzemplarzy klas usługowych, gdyż Grails, poza zmniejszeniem ilości pracy, jaką musimy włożyć do oprogramowania systemu, obsługuje również inne aspekty systemu, jak transakcje.

Klasy usługowe są klasami transakcyjnymi, tj. wszystkie publiczne metody wykonywane są pod auspicjami transakcji (podobnie jak publiczne metody biznesowe ziaren EJB). Kontrolujemy opakowanie transakcją metod publicznych usługi statyczną właściwością transactional, która akceptuje wartość logiczną true/false (domyślnie true).

Wyłączenie automatycznego opakowania transakcji dla publicznych metod to static transactional = false. Przy takiej konfiguracji, korzystamy ze statycznej metody withTransaction() dostępnej w każdej klasie dziedzinowej. Parametrem wejściowym jest domknięcie, które wyznacza zasięg transakcji, np. (Listing 11-7):
 package com.g2one.gtunes

class GtunesService {
static transactional = false

void someServiceMethod() {
Album.withTransaction {
// ...sama klasa usługowa jest nietransakcyjna,
// ale tutaj jesteśmy objęci transakcją
}
}
}
Pojawienie się wyjątku w ramach domknięcia powoduje wycofanie transakcji.

Samodzielne kontrolowanie wyniku transakcji w metodzie withTransaction() jest możliwe przez przekazywany parametr tx (typu org.springframework.transaction.TransactionStatus), np. (Listing 11-8):
 Album.withTransaction { tx ->
// ...seria operacji bazodanowych, transakcyjnych,
// po których decydujemy się wycofać transakcję
tx.setRollbackOnly()
}
Dostęp do usług nie jest synchronizowany, co w połączeniu z jej pojedynczym egzemplarzem, wymaga od nas samodzielnego dbania o jej współbieżność. Jeśli potrzebujemy przechowywać stan w usłudze możemy zdefiniować jej zasięg działania inny niż domyślny singleton. Mamy do dyspozycji:
  • prototype - nowy egzemplarz usługi jest tworzony każdorazowo podczas wstrzeliwania (rozwiązywania) zależności
  • request - nowy egzemplarz usługi per żądanie
  • flash - egzemplarz usługi ten sam dla obecnego i następnego żądania
  • flow - zasięg przepływu
  • conversation - zasięg przepływu i jego podprzepływów
  • session - zasięg sesji użytkownika
  • singleton - domyślny zasięg usług, gdzie mamy jedynie pojedynczą instancję w systemie
Użycie zasięgów flash, conversation oraz flow wymaga, aby klasa usługowa implementowała java.io.Serializable.

Deklaracja zasięgu istnienia usługi realizowana jest przez statyczną właściwość scope o wartości jak wyżej, np. (Listing 11-9):
 class LoanCalculationService {
static transactional = true
static scope = 'request'
}
Testowanie klas usługowych w ramach testów jednostkowych wymaga samodzielnego tworzenia egzemplarzy, natomiast testy integracyjne uruchamiają całą infrastrukturę Grails wraz ze Spring IoC. Do testowania jednostkowego możemy podeprzeć się Expando.

Udostępnianie usługi światu poza Grails możliwe jest przez dostępne wtyczki (rozszerzenia), np. jmx. Po instalacji wtyczki jmx poleceniem grails install-plugin jmx wystarczy zadeklarować statyczną zmienną expose z listą składającą się z pożądanego mechanizmu udostępniania, w tym przypadku byłoby to 'jmx', np. (Listing 11-11):
 class GtunesService {
static transactional = true
static expose = ['jmx']
}
Przy takiej konfiguracji, każda publiczna metoda jest dostępna via JMX i wystarczy uruchomić Grails z opcją com.sun.management.jmxremote, np. poprzez zmienną JAVA_OPTS, aby podłączyć się jconsole.

Istnieją wtyczki udostępniające usługi za pomocą protokołów XML-RPC, RMI, Hessian, Burlap oraz Spring HttpInvoker czy SOAP (wtyczki xfire i axis2). Uaktywnienie sposobu udostępnienia usług za pomocą wybranej technologii, to odpowiednie zadeklarowanie statycznej zmiennej expose. W ten sposób moglibyśmy stworzyć aplikację Grails całkowicie opartą na warstwie usługowej i dziedzinowej bez warstwy kontrolerów (kłania się SOA, czyli mocny argument za wdrożeniem Grails).

18 kwietnia 2009

Dokończenie rozdziału 10. o Grails ORM (GORM) z DGG2

0 komentarzy
Dokończenie relacji rozdziału 10. "GORM" z książki "The Definitive Guide to Grails, Second Edition".

Jednym z elementów Hibernate, który nie trafił do specyfikacji JPA (Java Persistence API) były zapytania kryteriowe (ang. criteria queries). Jest to sposób na tworzenie zapytań dynamicznie, który w Grails stał się jeszcze bardziej trywialny z pomocą wsparcia "budowniczych" (ang. builders) z Groovy. Mechanizm "budowniczych" polega na wykonaniu hierarchii metod i domknięć w celu stworzenia hierarchicznych struktur drzewiastych, jak np. dokumenty XMLowe czy hierarchia komponentów GUI.

GORM rozszerza każdą klasę dziedzinową o statyczną metodę createCriteria() do utworzenia egzemplarza zapytania kryteriowego. Następnie, korzystamy z 4 metod akceptujących domknięcie do zbudowania bardziej szczegółowego zapytania:
  • get() do odszukania pojedynczego egzemplarza klasy dziedzinowej
  • list() dla listy klas dziedzinowych spełniających warunek
  • scroll(), który zwraca ScrollableResults
  • count(), którego wynikiem jest liczba egzemplarzy
np. (Listing 10-11):
 def c = Album.createCriteria()
def results = c.list {
eq('genre', 'Alternative')
between('dateCreated', new Date()-30, new Date())
}
Metody w ramach domknięcia są zamieniane na odpowiednie wywołania metod na egzemplarzu klasy org.hibernate.criterion.Restrictions z Hibernate.

Domknięcie jest blokiem kodu, który może być przypisywany do zmiennej oraz może odwoływać się do zmiennych z otoczenia. W ten sposób można stworzyć wzorzec zapytania (domknięcie), przypisać do zmiennej i wykonać z nim jedną z 4 wymienionych metod, np. (Listing 10-12):
 def today = new Date()
def queryMap = [genre:'Alternative', dateCreated: [today-30, today]]
def query = {
queryMap.each { key, value ->
if (value instanceof List) {
between(key, *value)
} else {
like(key, value)
}
}
}
def criteria = Album.createCriteria()
println(criteria.count(query))
Interesującym elementem w tym przykładzie jest użycie operatora "spread" (gwiazdka przy value), który dzieli listę na dwa argumenty wejściowe (tablicę dwuargumentową) dla between, która wymaga 3 argumentów (więc niewprost zakłada się, że value jako lista będzie dwuelementowe). Reszta powinna być jasna, aczkolwiek dla mnie była niezwykle odświeżająca.

Wyszukiwanie egzemplarzy klas dziedzinowych po stanie związanych z nimi klas dziedzinowych, to wykonanie metod, których nazwy odpowiadają nazwom pól łączących klasy dziedzinowe w związki, np. (Listing 10-13):
 def criteria = Album.withCriteria {
songs {
ilike('title', '%Shake%')
}
}
Jakkolwiek użyliśmy nowej metody statycznej withCriteria() to dotyczy to również createCriteria().

GORM udostępnia metodę projections(), której parametrem wejściowym jest domknięcie, do tworzenia specjalizowanych zapytań z SQLowymi count, distrinct czy sum. Wewnątrz domknięcia wykonujemy metody org.hibernate.criterion.Projections z Hibernate, np. countDistinct().

GORM posiada mechanizm "zapytań przez przykład" (ang. query by example), gdzie metodzie find() czy findAll() przekazujemy egzemplarz poszukiwanej klasy z odpowiednim stanem. W połączeniu z tworzonym przez Groovy domyślnym konstruktorem akceptującym serię atrybut/wartość daje to interesujące konstrukcje, np. (Listing 10-15):
 def album = Album.find( new Album(title:'Odelay') )
Zapytanie jest budowane na podstawie wartości przekazywanych w konstruktorze.

Do pracy z zapytaniami HQL w Grails mamy metody find(), findAll() oraz executeQuery(). Podajemy im parametr tekstowy będący zapytaniem HQL. Zapytania z "?" wypełniane są wartościami z listy będącą ich drugim parametrem wejściowym (podobnie z parametrami nazwanymi), np. (Listing 10-18):
 def album = Album.find(
'from Album as a where a.title = :theTitle',
[theTitle:'Odelay'])
Stronicowanie w GORM to wykonanie zapytania HQL z listą z parametrami max i offset, np.
 Album.findAllByGenre("Alternative", [max:10, offset:20])
Konfiguracja GORM jest w pliku grails-app/conf/DataSource.groovy w sekcji dataSource. Większość parametrów konfiguracyjnych Hibernate jest dostępna w GORM, np. wyświetlanie wysyłanych zapytań to
 dataSource {
logSql = true
}
albo ustawienie dialektu
 dataSource {
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}
Pełne nazwy parametrów konfiguracyjnych Hibernate określamy w sekcji hibernate we wspomnianym grails-app/conf/DataSource.groovy, np.
 hibernate {
hibernate.connection.isolation=4
}
Zapisanie zmian w bazie danych jest domyślnie buforowane do późniejszego, bardziej odpowiedniego momentu niż każdorazowe zapisanie w bazie bezpośrednio po zmianie. Jeśli nie interesuje nas takie zachowanie GORM, możemy wykonać metody save() oraz delete() z parametrem flush ustawionym na true, np. (Listing 10-24):
 def album = new Album(...)
album.save(flush:true)
Oczywiście wypchnięcie zmian pojedynczego egzemplarza klasy dziedzinowej powoduje wypchnięcie zmian w całej sesji Hibernate (wykonanie metody Session.flush()).

Dostęp do aktualnie otwartej sesji Hibernate jest możliwy dzięki mechanizmowi wstrzeliwania zależności ze zmienną sessionFactory, np. (Listing 10-26):
 def sessionFactory

def index = {
def session = sessionFactory.currentSession()
}
Istnieje również inny sposób ze statyczną metodą withSession() dostępną w każdej klasie dziedzinowej, której domknięcie przyjmuje sesję Hibernate jako parametr wejściowy, np. (Listing 10-27):
 def index = {
Album.withSession { session ->
...
}
}
W rozdziale pojawia się wiele innych informacji nt. integracji GORM z Hibernate, jednakże są one związane z samym działaniem Hibernate i ich rozpoznanie zostawiam zainteresowanym jako dodatkową zachętę do lektury książki DGG2. Przed nami relacja z rozdziału 11. "Services" o klasach usługowych.