Zobrazují se příspěvky se štítkemgrails. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemgrails. Zobrazit všechny příspěvky

středa 16. ledna 2013

Dependency Injection v Grails

Jedna z vlastností Grails je automatická podpora dependency injection (DI) service tříd ve Spring beanech napsané v Groovy (to jsou mimo jiné i controller, service, taglib nebo domain v Grails aplikacích). Stačí napsat jednoduše název dané service třídy, např. pokud mám UserService:

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

O víkendu jsem tvořil CMS a chtěl jsem vyzkoušet nějakou službu, kam bych mohl ukládat obrázky. Mezi službami jsem zkoušel i Box.net. Cílem bylo umožnit upload obrázku do mého osobního box.net (mám 50 GB místa a neuložil jsem tam zatím nic).

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.

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()
...
    }
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é.

neděle 26. srpna 2012

JS soubory podle prostředí v Grails Resources

Při vývoji web aplikace je užitečné mít jiné Javascript (JS) soubory než při produkci. Příkladem jsou minifikované vs plné verze JS nebo JS soubory ze CDN vs uložené JS soubory. Grails pro práci s JS má plugin Resources (i když teď už se stal součástí Grailsu). V dokumentaci jsem ale nevyčetl, jak rozlišit různé prostředí v definici resources. Ve skutečnosti je to docela jednoduché:
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 = {
    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
}
That's that s**t!




středa 30. května 2012

Postřehy z integračního testování v Grails

Včera a dnes jsem začal s integračním testováním. Nevím, zda správně aplikuji filosofii integračních testů, ale hlavní je, že testuje to, co chci :) Napíšu sem pár postřehů, které jsem za 2 dny shromáždil a které mě překvapili:

  1. 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".
    /**
    * 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()
    }
    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.

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

  3. 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
    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
        }
    }
    Uvnitř closure, který předávám v metodě edit(), se zpracování nezastaví, i když tam hodím breakpoint.
  4. 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()
    controller.params.login = "marcpalmer"
    controller.params.password = "secret"
    controller.params.passwordConfirm = "secret"
    controller.signup()
    To však lze provést i pomocí Map parametru.
    def controller = new AuthenticationController()
    controller.request.parameters = [login: "marcpalmer", password: "secret", passwordConfirm: "secret"]
    controller.signup()

  5. Odlišný počet parametrů při použití controller.request.parameters a  controller.params. Mám-li jako parametr něco takového:
    [

    "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()

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

neděle 27. května 2012

Grails - jak jsou naprogramovány mapping v doménových třídách

Tento článek je o jedné zajímavosti z knihy Programming Grails (koupil jsem si Early Release). V Grails existuje jednoduchý způsob definování mapování pro doménové třídy pomocí closure static mapping = {}. Doteď jsem neměl ani páru, jak je to udělané, ani jsem se o to nezajímal. Teď už chápu o trochu více, i když ne úplně vše.

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() {
    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
    }
}
Výstup:

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 {
    Date day
    static mapping = {
        sort("day")
    }
}
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í:
this = class wellness.Attendance
owner = class wellness.Attendance
delegate = org.codehaus.groovy.grails.orm.hibernate.cfg.HibernateMappingBuilder@3321875 // ha, to je on
Když 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í.

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

Dlouho jsem hledal způsob, jak namapovat všechny controllery ve stejném package do stejného vzoru. Hodilo by se to např. v případě, že potřebuji změnit všechny controllery pod package admin do URL "/admin/${controller}/${action}". Jednak se lépe zabezpečují a jednak mají tyto URL "hezčí" formát. V oficiální dokumentaci Grails není popsaný takový způsob mapování. Na StackOverflow mi poradili, ať udělám admin sekci jako zvláštní plugin. To je ale podle mě dost složitý na malý projekt.

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ánek
        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
                }
            }
        }
}
Odteď 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}.

středa 16. května 2012

getTotalCount() metoda

Tak jsem se dnes naučil trik, jak získat celkový počet entit (aneb objektů, instancí, řádků) v databázi, které vyhovují kritériím. Jde o to, že pomocí metody createCriteria().list(max: params.max, offset: params.offset) získám omezený počet entit. Také však potřebuji získat počet, kolik těchto entit se skutečně nachází v databázi.
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

Včera jsem po přečtení článku narazil znovu na fci inject(). Tuto fci jsem předtím moc nechápal, tak jsem se rozhodl, že ho zkusím použít v aplikaci, kterou zrovna dělám.

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 {
        NEW("Nová"),
        SENT("Odeslaná"),
        CLOSED("Uzavřená")
        String czech
        Status(String czech) {
            this.czech = czech
        }
    }
Jeden způsob, jakým mohu získat předchozí řetězec, je tento:
var statusEnumsStr = "${protetika.Invoice.Status.values().collect { it.name() + ":" + it.czech}.join(";")}";
statusEnumsStr = ":Vše;"+ statusEnumsStr; 
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:
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í:

  1. naše kolekce [name: "NEW", czech:"Nová"; name: "SENT", czech: "Odeslaná"; name: "CLOSED", czech: "Uzavřená"]
  2. inicializační hodnota je ":Vše" a první prvek v kolekci je name: "NEW", czech:"Nová"
  3. V closure se provede ":Vše" + ";" + "NEW" + ":" + "Nová" a tato hodnota se vrací jako další inicializační hodnota
  4. 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

Dnes jsem hledal možnost, jak vyřadit duplikátní záznamy ve výsledcích vrácené z databáze pomocí GORM. Protože jsem už používal dotazy pomocí třídy Criteria v Hibernate, tak jsem nakonec našel tuto konstrukci:
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čera jsem měl za úkol vyřešit problém s kódováním češtiny v PDF dokumentu. Kolegové, kteří na tom dělali, si nevěděli rady (nebo se s tím nechtěli párat), tak to přesunuli na mě...
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 {
        src: url(file:c:/windows/fonts/arialuni.ttf);
        -fs-pdf-font-embed: embed;
        -fs-pdf-font-encoding: Identity-H;
}
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:

@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

Pro svou aplikaci jsem potřeboval, aby fungovala filtrace podle ID entity. Mám v aplikaci faktury, které jsou rozlišené podle ID typu long (což je číslo). Když uživatel chce najít např. fakturu, která obsahuje "1" - tj. faktura č. 1, ale i č. 10 nebo 11, tak pomocí těch předdefinovaných Criteria v Hibernate to nejde. Teda ono jde, ale pomocí kritéria sqlRestriction. Tzn. uživatel si musí pomocí SQL nadefinovat kritérium sám :)

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 Veselý obličej 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é Veselý obličej

sobota 24. března 2012

Našeptávač pomocí jQuery a Grails

Jak udělat podobnou funkcionalitu našeptávače, jako má Google při vyhledávání, že vám zobrazí ty podobné výrazy? Zrovna včera jsem něco takového udělal.
Šlo o to, že při zadávání část rodného čísla server automaticky vyhledá ty RČ, která obsahují zadávaná čísla a vloží je za uživatele do textboxu. Pro zhotovení jsem použil jQuery UI Autocomplete: http://jqueryui.com/demos/autocomplete/ a serverová část je naprogramovaná v Grails 2.0.1.

Serverová část:
Tato část zahrnuje přijímání parametru z requestu, vyhledávání dle RČ a generování JSON.

První je potřeba udělat vyhledávání pacienta (aplikace je pro zdravotnictví) podle RČ:

List patientList = Patient.createCriteria().list() {
ilike('RC', "%${params.RC}%")
}

Protože je RC uložena jako text, lze vyhledávat pomocí LIKE v SQL.

Tento výsledek pak připravíme pro generování JSON:

def resultList = [] // výsledný list
patientList.each {patient ->
def patientMap = [:] // add to map. jQuery autocomplete expects the JSON object to be with id/label/value.
patientMap.put("id", patient.id)
patientMap.put("label", patient.fullName) // to, co se zobrazí v našeptávači
patientMap.put("value", patient.RC) // to, co se vyplní do vyhledávaného pole
patientMap.put("rc", patient.RC)
patientMap.put("firstName", patient.firstName)
patientMap.put("lastName", patient.lastName)
patientMap.put("insuranceCompany.acronym", patient?.insuranceCompany?.acronym)
resultList.add(patientMap) // add to the arraylist
}
render patientList as JSON // generování JSON v Grails

Jak vidíme, lze zobrazit uživateli něco jiného, než co se vloží pak po vyhledání - zde uživateli zobrazím při zadání části RČ možného pacienta se stejným RČ, ale vložím zase RČ do pole pro RČ.

Klientská část:
Prvně je nutné získat RČ z textboxu - pomocí jQuery:

var rc = $('#patient\\.RC').val(); // za předpokladu, že id textboxu je "patient.RC"

Pak je potřeba vytvořit autocomplete plugin:

$("#patient\\.RC").autocomplete({
delay:500,
minLength:3,
source:contextPath + "/patient/autocompletePatient?rc=" + rc,
select:function (event, ui) { // event handler when user selects a company from the list.
$("#patient\\.id").val(ui.item.id);
$("#patient\\.RC").val(ui.item.rc);
$("#patient\\.firstName").val(ui.item.firstName);
$("#patient\\.lastName").val(ui.item.lastName);
$("#patient\\.insuranceCompany\\.acronym").val(ui.item["insuranceCompany.acronym"]);
}
});

Parametr delay je nastaven na 500 ms, díky tomu server nebude přetěžován dotazy - posílá se až za 500 ms od té doby, kdy klient přestal psát. Minimální délka omezuje vyhledávání až do té chvíle, kdy jsou zadány min. 3 znaky - nevrací se tolik výsledků. Source určuje, kde lze získat zdroj pro zobrazování našeptávač - zde je zdroj vzdálený. Select je event listener, který říká, co se má provést, pokud uživatel klikne na jeden z našeptávaných výrazů - chci, aby se nejen doplnil zbytek RČ, ale také jméno, příjmení a zkratku pojišťovny u daného pacienta.
Pak je nutné, aby se source aktualizoval pokaždé, kdy uživatel něco napíše - lze použít keyup event v jQuery. Celý kód vypadá takto:

$("#patient\\.RC").keyup(function () {
var rc = $('#patient\\.RC').val();
$("#patient\\.RC").autocomplete({
delay:500,
minLength:3,
source:contextPath + "/patient/autocompletePatient?rc=" + rc,
select:function (event, ui) { // event handler when user selects a company from the list.
$("#patient\\.id").val(ui.item.id);
$("#patient\\.RC").val(ui.item.rc);
$("#patient\\.firstName").val(ui.item.firstName);
$("#patient\\.lastName").val(ui.item.lastName); $("#patient\\.insuranceCompany\\.acronym").val(ui.item["insuranceCompany.acronym"]);
}
});
});

Zdroje inspirace:

neděle 18. března 2012

Některé constraint v Grails

Dnes jsem při definování doménové třídy pro novou aplikaci narazil na problém, kdy jsem nevěděl, jaký je rozdíl mezi constrainty size a range. Tušil jsem, že jeden nejspíš bude pro String a druhý pro čísla. Po chvíli prozkoumání zdrojáku v Grails jsem narazil na toto:

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) {...
....
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.

sobota 18. února 2012

Cloud Foundry a problém se Spring Security v Grails

Při vývoji jedné aplikace využívám framework Grails a pro autentizaci plugin Spring Security. Při deploymentu na Cloud Foundry (CF) jsem se však setkal s nepříjemnou výjimkou java.lang.IllegalStateException a s takřka nic neříkajícím výpisem: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Dlouho jsem netušil jak na to. Nakonec jsem napsal do fóra na CF a odpověď přišla brzy: je to způsobené použitím anotací. Chyba se dala spravit tím, že místo anotací mám použít zbývající dva způsoby: InterceptUrlMap nebo Requestmap. Nakonec jsem zvolil způsob InterceptUrlMap, který spočíval v tom, že zabezpečené URL se namapují v Config.groovy.

Update: tak hurá už to frčí. Dnes (21.2.2012) mi psal jeden z vývojářů CF, že problém byl v pluginu do Grails a pro opravu stačí přidat compile ":webxml:1.4.1" do části plugins v souboru BuildConfig.groovy

středa 15. února 2012

Grails aplikace na Jelastic.com

Deployment na tento server je vcelku jednoduchý. Existuje totiž oficiální návod přímo od Jelastic. Co v návodu chybí, je konfigurace databáze. Na to však existuje jiný návod, který lze aplikovat pro Grails aplikace. Po rozběhnutí a první navštívení aplikace vypadá vše ok, ale pak ouvej. Když jsem si chtěl vytvořit záznam s českými znaky, do MySQL databáze se místo nich uložily "?".
Moje první podezření padlo na databázi a nastavení Collation. Měl jsem nastavený utf-8-cs, takže to bylo v pořádku. I jednotlivé tabulky a řádky byly stejně nastavené. Takže tím to nebylo.
Po chvíli googlování jsem měl další podezření. Mohlo by to nastat při zasílání požadavku ze stránek, kdy české znaky se kódují jako entity. Vypsal jsem si tedy v controlleru tyto hodnoty, a zjistil jsem, že to také nebylo ono.
Nakonec jsem našel tuto diskusi: http://stackoverflow.com/questions/3064644/character-encoding-problem-spring (big up této diskusi). Stačilo doplnit na konci jdbc url parametr ?useUnicode=yes&characterEncoding=UTF-8