Blog určený spíše pro osobní použití. Zaznamenávám sem problémy, se kterými se potýkám v práci a které mi trvaly trošku déle, než jsem je vyřešil. Tak abych příště dělal rychleji :) Nyní také o Austrálii.
středa 16. ledna 2013
Dependency Injection v Grails
def userService
pak vše funguje automaticky (viz manuál Grails). Nikdy jsem ale nevěděl (ani jsem se o to nijak nezajímal), jak to vlastně funguje. Narazil jsem na to až v knize Programming Grails (MEAP verze, v době psaní článku je zdarma a legálně dostupné tu).
Podmínkou je, aby proměnná měla scope třídy a taky aby byla public. Pro public proměnné Grails automaticky převede na privátní proměnnou s getter a setter metody.
private Object userService
void setUserService (Object userService) {
this.userService = userService
}
Object getUserService () {
return userService
}
Protože Grails běží na Spring frameworku, má definován též ApplicationContext soubor, kde atribut autowire je nastaven na typ "byName". Což znamená, že pokud ve třídě bude k dispozici setter, který má jméno jako bean, tak se automaticky provede DI. To vede k dalšímu důsledku, kdy proměnná definovaná v rámci metody (lokální proměnná) nemůže mít DI, protože nesplňuje podmínky pro autowiring.
pondělí 22. října 2012
Upload souboru do Box.net v Grails
V dokumentaci box.net bylo něco takového:
curl https://www.box.com/api/2.0/files/content \ -H "Authorization: BoxAuth api_key=API_KEY&auth_token=AUTH_TOKEN" \ -F filename=@FILE_NAME \ -F folder_id=FOLDER_ID
Je to pomocí nástroje cURL. Jednoduše vysvětleno, pro upload je potřeba provést POST na adresu https://api.box.com/2.0/files/content spolu s hlavičkou Authorization, kde uvedete nějaké ty tokeny, a dva formulářové elementy filename a folder_id + uploadovaný soubor.
Pro připojení jsem použil knihovnu Apache HttpComponents. Zkoušel jsem i nějaký plugin do Grails, ale u nich jsem měl problém vytvořit formulářový element spolu s tím souborem a poslat to celé.
private String sendHttps(String httpUrl, byte[] data, String filename) {
HttpClient httpclient = new DefaultHttpClient()
httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1)
HttpPost httppost = new HttpPost(httpUrl)
Map headers = ["Authorization": "BoxAuth api_key=${apiKey}&auth_token=${authToken}"] // hlavička s autorizačními údaji
headers.each {k, v ->
httppost.addHeader(k, v)
}
MultipartEntity mpEntity = new MultipartEntity() // pro POST formuláře s přílohou
ContentBody cbFile = new ByteArrayBody(data, filename)
mpEntity.addPart("filename", cbFile) // tato část mi dělala největší potíže
mpEntity.addPart("folder_id", new StringBody("445743883")) // ID složky na box.net
httppost.setEntity(mpEntity)
HttpResponse response = httpclient.execute(httppost)
HttpEntity resEntity = response.getEntity()
httpclient.getConnectionManager().shutdown()
...
}
neděle 26. srpna 2012
JS soubory podle prostředí v Grails Resources
environments {
development { // pro vývoj beru uložený plugin
modules = { // closure
jquery {
resource url: '/js/libs/jquery.js', disposition: 'head'
}
application { // duplikace
resource url: 'js/application.js'
}
}
}
production { // pro produkci beru ze CDN
modules = {
jquery {
resource url: 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js',
disposition: 'head'
}
application { // duplikace
resource url: 'js/application.js'
}
}
}
}
To už je skoro super. Pak jsem ale zjistil, že takhle definované má v sobě duplicity, když jsou soubory stejné pro oba prosředí (viz část application), a snadněji se dělají chyby (musí se přidávat ten samý řádek tolikrát, kolik je prostředí). Je potřeba ještě další trik - kompozice 2 closures v 1 closure:
commonModules = {That's that s**t!
application {
resource url: 'js/application.js'
}
}
environments {
development { // pro vývoj beru uložený plugin
modules = { // closure
jquery {
resource url: '/js/libs/jquery.js', disposition: 'head'
}
}
} << commonModules
production { // pro produkci beru ze CDN
modules = {
jquery {
resource url: 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js',
disposition: 'head'
}
}
} << commonModules
}
středa 30. května 2012
Postřehy z integračního testování v Grails
- pro vytvoření mockup controlleru použijte normálně Controller controller = new Controller(). Problémem je však v tom, že pokud jednou vytvořený controller přiřadíte parametry či zavoláte akci, zůstanou parametry i odpovědi z akce v controlleru. A to i přesto, že vytvoříte novou instanci znova pomocí new Controller(). Tento problém jsem eliminoval tak, že jsem controller vlastnoručně vyčistil od "bordelu".
/**
Problém by šel líp vyřešit pomocí setUp() a tearDown(), ale protože testuji celý proces, tak jsem tyto metody nepoužil.
* vyčistí controller pro další použití
* musí se použít pokaždé, když chceme
* vytvořit nový request
* @param controller
*/
private void clear(def controller) {
controller.response?.reset()
controller.params?.clear()
controller.modelAndView?.clear()
}
- pokud chcete otestovat vrácený model z controlleru, lze použít konstrukci controller.modelAndView.model.nazevModelu. Avšak konstrukce
[voucherInstance: voucherInstance, diagnosis: diagnosis, insuranceCompanyList: insuranceCompanyList]
nevrací žádný model (nevím, co přesně to vrací...). Stejně je to s konstrukcírender(template: "detail", model: [voucherInstance: voucherInstance], status: 200) .
Zde to vrací vygenerovaný HTML snippet. Model vrací tato konstrukce:render view: "edit", model: [voucherInstance: voucherInstance, diagnosis: diagnosis, insuranceCompanyList: insuranceCompanyList]
- používám IntelliJ jako IDE pro vývoj v Grails. Při testování jsem narazil na problém při debugování v controlleru, který používá metodu s closure.
// akce v controlleru
Uvnitř closure, který předávám v metodě edit(), se zpracování nezastaví, i když tam hodím breakpoint.
def edit() {
checkVoucherById(params.id) {voucherInstance ->
// tady nemůžu vůbec stopnout zpracování
render view: "edit", model: [voucherInstance: voucherInstance]
}
}
// tohle je metoda, která bere jako parametr closure
private checkVoucherById(id, closure) {
Voucher voucherInstance = null
try {
id = id as long
voucherInstance = voucherService.get(id)
closure(voucherInstance)
} catch (Throwable t) {
// nějaká jiná akce
}
} - při testování controlleru lze přidat parametry do requestu podobně jako je to při skutečném provozu. V dokumentaci je popsán tento způsob:
def controller = new AuthenticationController()
To však lze provést i pomocí Map parametru.
controller.params.login = "marcpalmer"
controller.params.password = "secret"
controller.params.passwordConfirm = "secret"
controller.signup()def controller = new AuthenticationController()
controller.request.parameters = [login: "marcpalmer", password: "secret", passwordConfirm: "secret"]
controller.signup()
-
Odlišný počet parametrů při použití controller.request.parameters a
controller.params. Mám-li jako parametr něco takového:
[
a použiji controller.request.parameters jako pro přiřazení parametrů do controlleru, v controlleru pak mám o 1 parametr víc. Tím je mapa vytvořená z visitList[0], což je stejné chování jako controller za běžného provozu. Použiji-li však pro přiřazení parametrů konstrukci controller.params jako v manuálu, tak mapa chybí.
"visitListSize": 1.toString(), "visitList[0].date_day": 1.toString(), "visitList[0].date_month": 1.toString(),
"visitList[0].date": Date.parse("dd-MM-yyyy", "01-01-2012").toString(), "visitList[0].date_year": 2012.toString()
]
neděle 27. května 2012
Grails - jak jsou naprogramovány mapping v doménových třídách
Closure (česky uzávěry) umožňují uvnitř nich volat metody. Příklad:
class SomeClass {
void callClosure() {
def dog = {
woof() // volání metody void woof() z closure
}
dog() // zavolá closure
}
void woof() {
println "Woof!"
}
}
Každý closure má obsažené 3 různé atributy: Owner, Delegate a this. this odkazuje na instanci třídy, kde je closure definován. Owner obsahuje odkaz na instanci, kde je obsažen closure. Ve většině případů je tedy this stejný jako owner. Výjimkou je třeba tento případ (z knihy Programming Groovy):
examiningClosure() {Výstup:
println "In First Closure:"
println "class is " + getClass().name
println "this is " + this + ", super:" + this.getClass().superclass.name // this = owner
println "owner is " + owner + ", super:" + owner.getClass().superclass.name
println "delegate is " + delegate +
", super:" + delegate.getClass().superclass.name
examiningClosure() { // closure uvnitř closure
println "In Closure within the First Closure:"
println "class is " + getClass().name
println "this is " + this + ", super:" + this.getClass().superclass.name // this != owner
println "owner is " + owner + ", super:" + owner.getClass().superclass.name
println "delegate is " + delegate +
", super:" + delegate.getClass().superclass.name
}
}
In First Closure:
class is ThisOwnerDelegate$_run_closure1
this is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
owner is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
delegate is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
In Closure within the First Closure:
class is ThisOwnerDelegate$_run_closure1_closure2
this is ThisOwnerDelegate@55e6cb2a, super:groovy.lang.Script
owner is ThisOwnerDelegate$_run_closure1@15c330aa, super:groovy.lang.Closure
delegate is ThisOwnerDelegate$_run_closure1@15c330aa, super:groovy.lang.Closure
Atribut Delegate v closure je ten, který právě umožňuje dělat kouzla jako v static mapping = {}. Closure totiž umožňuje nastavit atribut Delegate na jiný objekt pomocí setDelegate(Object delegate) a pak volat jejich metody. V praxi to znamená toto:
Doménová třída s defaultním řazením entit podle dne:
class Attendance {V doménové třídě však žádná metoda sort defaultně neexistuje. Avšak třída je platná a funkční. Pokud použijete debugger a zastavíte zpracování v closure mappings, uvidíte následující:
Date day
static mapping = {
sort("day")
}
}
this = class wellness.AttendanceKdyž si otevřete třídu HibernateMappingBuilder, nacházejí se tam metody, které se používají v mapping, jako je výše zmíněný sort, cache, table, version apod. To je celé tajemství.
owner = class wellness.Attendance
delegate = org.codehaus.groovy.grails.orm.hibernate.cfg.HibernateMappingBuilder@3321875 // ha, to je on
P.S. Další otázkou je, kde je nastaven setDelegate() v doménových třídách? To jsem se ještě nedočetl (ale myslím, že je to udělané pomocí MOP). O tom napíšu, až budu vědět na stopro nebo si to někde přečtu.
Grails - URL mapování pro celý package
Nakonec jsem objevil článek, který popisuje triky s URL mapováním v Grailsu. Trošku jsem to změnil a řešení je na světě:
for (controller in AppCtx.grailsApplication.controllerClasses) { // AppCtx - viz článekOdteď všechny controllery, které jsou v balíku "admin", budou mít URL namapovány jako "/admin/${controller}/${action}", a zbytek klasicky jako "/${controller}/${action}.
def cName = controller.logicalPropertyName
def packageName = controller.packageName
if (packageName.contains(".admin") || packageName.contains(".springsecurity")) {
"/admin/${cName}/$action?/$id?"(controller: cName) {
constraints {
}
}
} else {
"/${cName}/$action?/$id?"(controller: cName) {
constraints {
// apply constraints here
}
}
}
}
středa 16. května 2012
getTotalCount() metoda
A k tomu slouží právě metoda getTotalCount(). Metoda createCriteria().list(max: params.max, offset: params.offset) totiž vrací instanci třídy PagedResultList, který toto umožňuje. Jsem si toho ani nevšim a už jsem se chtěl zeptat na StackOverflow, ale byl jsem včas upozorněn a v dokumentaci je to hned na začátku popisu metody createCritera(). RTFM.
úterý 15. května 2012
Metoda inject() v Groovy
Příklad:
Chci v jqGrid použít dropwdown menu pro filtrování položek. Abych udělal toto dropdown menu, potřebuji dostat řetězec vypadající asi takto:
:Vše;NEW:Nová;SENT:Odeslaná;CLOSED:Uzavřenákde mám vždy dvojici hodnotu option value a text, který se zobrazuje. Obě tyto hodnoty můžu získat z enumerační třídy.
enum Status {Jeden způsob, jakým mohu získat předchozí řetězec, je tento:
NEW("Nová"),
SENT("Odeslaná"),
CLOSED("Uzavřená")
String czech
Status(String czech) {
this.czech = czech
}
}
var statusEnumsStr = "${protetika.Invoice.Status.values().collect { it.name() + ":" + it.czech}.join(";")}";Metoda values() vrací Collection<Status> se všemi enum hodnoty, který pak přeměním na řetězec složený z jména hodnoty (metoda name()) a českého názvu hodnoty, a "posbírám" tyto řetězce (metoda collect()) do nové kolekce. Hodnoty z této kolekce pak spojím do jednoho řetězce pomocí ";". Teď už mám řetězec:
statusEnumsStr = ":Vše;"+ statusEnumsStr;
NEW:Nová;SENT:Odeslaná;CLOSED:UzavřenáZbytek je jasné.
Teď to samé udělám pomocí metody inject():
var statusEnumsStr = "${protetika.Invoice.Status.values().inject(":Vše") {initVal, colVal -> initVal + ";" + colVal.name() + ":" + colVal.czech }}";Metoda inject bere 2 parametry: inicializační parametr a closure. Funguje tak, že pro první prvek v kolekci provede to, co je v closure, a výsledek se pak stane inicializační hodnotou pro další prvek. Lepší znázornění:
- naše kolekce [name: "NEW", czech:"Nová"; name: "SENT", czech: "Odeslaná"; name: "CLOSED", czech: "Uzavřená"]
- inicializační hodnota je ":Vše" a první prvek v kolekci je name: "NEW", czech:"Nová"
- V closure se provede ":Vše" + ";" + "NEW" + ":" + "Nová" a tato hodnota se vrací jako další inicializační hodnota
- Pro druhý prvek kolekce name: "SENT", czech: "Odeslaná" je pak k dispozici inic. hodnota :Vše;NEW:Nová;
Kroky se pak opakují.
sobota 12. května 2012
Vyřazení duplikátních výsledků z dtb
NejakaEntita.createCriteria().list() {
resultTransformer Criteria.DISTINCT_ROOT_ENTITY // this is it
}
Good to know. Význam té konstrukce jsem zatím nezkoumal, na to snad bude čas někdy jindy. Hlavní je, že funguje.
středa 9. května 2012
PDF renderování a čeština
V projektu používáme plugin Grails Rendering pro generování PDF souboru. Používání bylo jednoduché, jenže zase české znaky se nezobrazovaly. Bylo potřeba přidat správný font.
@font-face {Takhle se vše zobrazovalo správně. Problém byl ale v tom, že odkaz na font byl funkční jen pro Windows. OK, tak prostě se ten soubor s fontem zkopíroval do web-app/font složky (složka s HTML a CSS a obrázky, která se nekompiluje). Komplikací poté ale bylo, že nešlo na ten soubor odkazovat:
src: url(file:c:/windows/fonts/arialuni.ttf);
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
@font-face {
src: url(file:${resource(dir:'font', file: 'ARIALUNI.TTF')});
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
A hned NullPointerException. Naštěstí jsem pak našel na StackOverlow, jak to někdo také řešil. A také jsem přehlídl důležitou větu v manuálu (RTFM): All links to resources (e.g. images, css) must be accessible by the application . This is due to the linked resources being accessed by application and not a browser. Takže řešením bylo nějak takhle (na soubor se muselo odkazovat jako na soubor z PC, ne přes URL):
@font-face {
src: url(file:${czechFontPath});
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
úterý 1. května 2012
DBF soubor s českými znaky
České čárky a háčky,to je občas velké zlo v kódování. A ještě větší zlo je,když toto kódování nevíte a ani neumíte zjistit. V současném projektu je 1 z úkolů migrace databáze. Jenže databáze starého systému,který je starý a k tomu ještě desktopový, že jsem tu databázi viděl poprvé (slyšel podruhé). Jednalo se o databázi foxpro a hromada souborů s koncovkou .dbf.
Myslel jsem, že vše proběhne bez problému. Jako první jsem stáhl z netu nějaké programy pro zobrazování,ale bohužel žádná neuměla české znaky přečíst (např. č). Pak jsem koukal i přímo po java knihovnách. Nejlepší byl tento: http://www.hxtt.com/orderdbf.html, akorát stál prachy,takže nic pro studenty. Trial verze umožňovala zobrazit jen 1000 prvních řádků... Ale plusem byl ten,že jsem konečně zjistil kódování dbf souboru (Cp1250).
Po delším hledání jsem narazil na program od českých autorů: http://www.pspad.com/cz/dbfview.htm. Ten funguje skvěle,doporučuji. Migrace samotné provedeme konverzí na csv a pak načtení csv do databáze.
pondělí 9. dubna 2012
SQL vyhledávání LIKE pro čísla
V případě, že mám číslo, ale chci ho vyhledat pomocí LIKE jako String, tak musím prvně změnit jeho typ. Vypadá to nějak takhle:
sqlRestriction "cast( id AS char ) like '%" + id.toString() + "%'"kde id je ID typu long převedené na String.
Ještě jsem pak četl, že je potřeba dát pozor na SQL Injection, ale nezkoumal jsem to více, protože v mém případě se jedná o interní systém pro pár lidí. Každopádně tento problém by měl být odstraněn ve verzi 2.1 pomocí parametrů.
Zdroj:
http://grails.1312388.n4.nabble.com/LIKE-or-ILIKE-on-Integer-td2997745.html
sobota 31. března 2012
Bootstrap.groovy a velké množství počátečních dat
Dnes se mi podařilo vyřešit problém, který jsem ani nedoufal, že se dá vyřešit. Problém spočíval v tom, že v bootstrap souboru v Grailsu zkouším vytvářet velké množství testovacích dat - celý soubor má cca 1000 řádků. Jenže při spuštění Grails hlásil divnou chybu:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000000262b036, pid=4152, tid=8016
#
# JRE version: 6.0_29-b11
# Java VM: Java HotSpot(TM) 64-Bit Server VM (20.4-b02 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# j BootStrap$_closure1.doCall(Ljava/lang/Object;)Ljava/lang/Object;+87
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
--------------- T H R E A D ---------------
Current thread (0x000000000d985800): JavaThread "pool-7-thread-1" [_thread_in_Java, id=8016, stack(0x000000000bcf0000,0x000000000bdf0000)]
siginfo: ExceptionCode=0xc0000005, reading address 0x0000000138c797cf
Registers:
RAX=0x000000000bdedd78, RBX=0x000000000bdedd78, RCX=0x00000000d9d1e488, RDX=0x00000000d9d0abff
RSP=0x000000000bdedd88, RBP=0x000000000bdeddb8, RSI=0x000000000d985800, RDI=0x000000000ee83ed0
R8 =0x0000000000000004, R9 =0x00000000d4b4e890, R10=0x000000006dfc4f80, R11=0x000000000ee83f38
R12=0x0000000000000000, R13=0x00000000d9d1d8ce, R14=0x000000000bdee5b0, R15=0x000000000d985800
RIP=0x000000000262b036, EFLAGS=0x0000000000010287
Když jsem ten dlouhej soubor okomentoval zhruba tak 3/4, tak vše jelo. Prvně jsem si myslel, že je to chyba v nedostatku paměti JVM. Zkoušel jsem tedy zvýšit paměť ze 128MB na 512 MB. Chyba však stále byla. Druhé, co mě pak napadlo, byla ne nejnovější verze JVM. Měl jsem 1.6-něco-29 a nejnovější bylo asi 1.6.-něco-31. Updatoval jsem, ale chyba tu furt byla. Už jsem to docela vzdal a smířil jsem se s tím, že prostě budu mít míň testovacích dat, stejně who cares.
Včera jsem však kouknul zpátky na StackOverflow, kam jsem položil otázku na tento problém. Nějakej týpek napsal, ať zkusím rozdělit soubor do více transakcí. Za chvíli jsem našel konstrukci, která to provádí:
Patient.withTransaction {
// vytváření a ukládání doménových tříd…
}
Najednou chyba už nebyla Jen pak byl problém s tím, že se ukládaly jen 50 entit. Pak jsem zjistil, že se tedy musí dávat .save(flush:true) na konec té transakce, aby se uložila. Také je nutné, aby na sebe odkazující entity byly v jedné transakci. A to je celé
sobota 24. března 2012
Našeptávač pomocí jQuery a Grails
neděle 18. března 2012
Některé constraint v Grails
Tedy Size je pro String a Range pro čísla. Také jsem pak narazil na tuto část v dokumentaci: http://grails.org/doc/latest/guide/single.html#gormConstraints.třída RangeConstraint metoda processValidate():....if (from instanceof Number && propertyValue instanceof Number) {....třída SizeConstraint metoda processValidate():....if (propertyValue.getClass().isArray()) {...else if (propertyValue instanceof Collection) {...else if (propertyValue instanceof String) {.......