neděle 27. května 2012

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.

čtvrtek 10. května 2012

Moje první MySQL procedura

Včera jsem napsal svoji první MySQL proceduru. Měl za úkol vygenerovat náhodné číslo, které má 10 číslic a je jedinečné v celé databázi.

create procedure generaterc()
begin
declare _rc varchar(255);  --tady jsem měl chybu
declare _id int;
set _id = 1;
while _id < ((select count(*) from patient) + 1) do
    begin
    set _rc = cast(FLOOR(1000000000 + (RAND() * 8999999999)) AS char);  
    if not exists(select * from patient where patient.rc = _rc) then
        update patient set rc=_rc where id=_id;
        set _id=_id+1;          
    end if;
    end;
end while;  
end
Poznámka pod čarou:
Proměnné, které chceme pak dávat jako hodnotu do sloupců databáze, by měly mít stejný datový typ jako ten sloupec. Měl jsem v tabulce sloupec rc, který byl typu varchar(255). Proměnnou jsem však deklaroval jako declare _rc char; a to způsobilo nekompatibilní dat. typ. http://stackoverflow.com/questions/10524573/mysql-procedure-random-number-generating

Zjištění:
V databázi s 82 618 řádky funkce vůbec nedoběhne do konce, a to jsem ji nechal běžet přes 30 min. Nakonec jsem ty čísla náhodně negeneroval :)

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.