2014. július 30., szerda

Groovy funkcionális eszközök

A Groovy sorozatot folytatva (1, 2) ebben a bejegyzésben a nyelv funkcionális aspektusát szeretném bemutatni a teljesség igénye nélkül. Két fontos tulajdonság képezi az alapját a funkcionális programozásnak Groovyban, az egyik, hogy van lehetőség anonim funkciók (Closure) írására, a másik pedig, hogy az utolsó kifejezés értéke automatikusan visszatérési érték lesz, ha nincs explicit visszatérési érték meghatározva (üres Closure visszatérési értéke null). Ez a két tulajdonság elengedhetetlen ahhoz, hogy funkcionális szemléletben tudjunk programozni, de pusztán e két dolog használata még nem eredményezi automatikusan a szemlélet-váltást. Vegyük sorra milyen egyéb eszközökkel támogatja a Groovy a munkánkat.
  • Először nézzük a Closure összefűzést:
    def m = { "${it}-" }
    def p = { "${it}+" }
    def pm = m << p
    
    println pm('') == m(p('')) // true
    
    Természetesen a másik irányba is működik a dolog:
    def mp = m >> p
    println p(m('')) == mp('') // true
    
  • A Closure.curry() metódus becsomagolja a Closure példányt, és az eredeti Closure paraméterei helyére lehet fix értékeket beállítani. A példa magáért beszél:
    def plus = { int f, int s, int t ->
        println "$f $s $t"
        return f + s + t
    }
    def fix = plus.curry(0, 0) // további opciók: ncurry() és rcurry()
    println fix(5) // 0 0 5
    
  • Felmerülhet az igény, hogy már meglévő metódusokból csináljuk Closuret. Nem a legelegánsabb megoldás, de mindenféleképpen hasznos ha meglévő eszközeinket szeretnénk "modernizálni":
    class o {
        void f() {
            println 'called'
        }
    }
    
    def c = o.&f // vagy new o().&f
    
    println c.class // class org.codehaus.groovy.runtime.MethodClosure
    
  • A funkcionális programozásra igen jellemző a rekurzív végrehajtás, és ezen programozási nyelvek részét képezik a különböző optimalizációs eszközök. Természetesen a Groovyban is van lehetőségünk finomhangolni rekurzióinkat. Az első ilyen eszköz, amit bemutatok a Closure.memoize(), ami nemes egyszerűséggel annyit tesz, hogy a visszaadott csomagoló Closure gyorsítótárazza a végrehajtás eredményeit. Különös tekintettel kell lennünk használatakor a memória-szivárgásokra, mert alapértelmezetten nincs méret határ szabva a gyorsítótárnak:
    def a = { print "called" }.memoize() // vagy memoizeBetween(int protectedCacheSize, int maxCacheSize)
    a();a() // called
    
    def a = { i -> print "called $i " }.memoize() // vagy memoizeAtLeast(int protectedCacheSize) és memoizeAtMost(int maxCacheSize)
    a(1);a(2);a(2) // called 1 called 2
    
    Meglévő metódusainkat pedig a @Memorized annotációval tudjuk hasonló működésre bírni, mely két opcionális paramétert vár a maxCacheSize és a protectedCacheSize.
  • A rekurzív hívásoknak van egy igen kártékony mellékhatása a JVMben. Minden egyes hívás rákerül a stackre, ami könnyen StackOverflowErrorhoz vezet. Ezt elkerülendő a Closure.trampoline() segítségével referenciát szerezhetünk egy olyan TrampolineClosurera, mely szekvenciálisan hívja az eredeti Closuret egészen addig, míg az TrampolineClosuret ad vissza. Ezzel a technikával mentesíti a stacket, lássuk ezt a gyakorlatban:
    def s
    s = { l, c = 0 ->
        l.size() == 0 ? c : s(l.tail(), ++c)
    }.trampoline()
    
    println s(1..10) // 10
    
    A Closure.trampoline() metódus mintájára az @TailRecursive annotációt használhatjuk, a dokumentációban szereplő megkötésekkel.
  • A funkcionális nyelvek általában az un. lazy evaluation szemléletet követik, ami röviden annyit jelent, hogy csak akkor értékel ki a rendszer valamit, ha arra feltétlenül szükség van. A Groovy is biztosít megoldásokat a paradigma követéséhez.
    def l = [].withDefault { 45 }
    println l[3] // 45
    println l // [null, null, null, 45]
    
    Vagy a @Lazy annotációval tetszőleges adattagot varázsolhatunk lusta kiértékelésűre. Egy opcionális paraméterével akár puha referenciában is tárolhatjuk az értéket, természetesen az alapértelmezett működés szerint erős referenciát alkalmaz:
    class Person {
        @Lazy(soft = true) pets = ['Cat', 'Dog', 'Bird']
    }
    
    def p = new Person()
    println p.dump() // <Person@7b073071 $pets=null>
    p.pets
    println p.dump() // <Person@18e30556 $pets=java.lang.ref.SoftReference@3f0e6ac>
    
    Annyit mindenféleképpen meg kell még jegyeznem, hogy ha a mező statikus, és nem puha referenciában történik a tárolás, akkor a Initialization on demand holder mintát követi a fordító.
A beépített funkciók után térjünk át a haladó technikákra. Bár a Groovynak szoros értelemben nem része a GPars keretrendszer, mégis érdemes kicsit közelebbről megismerkedni vele. A dokumentációból idézve:

"GPars is a multi-paradigm concurrency framework, offering several mutually cooperating high-level concurrency abstractions, such as Dataflow operators, Promises, CSP, Actors, Asynchronous Functions, Agents and Parallel Collections."

  • Meglévő Closurejainkat könnyedén aszinkron hívásokká alakíthatjuk a GParsExecutorsPool segítségével, ahogy a példa is mutatja.
  • Collectionök párhuzamos feldolgozására a GParsPoolt tudjuk segítségül hívni. A GParsPool osztály ParallelArray alapon végzi a műveleteket, míg párja a GParsExecutorsPool hagyományos thread poolokat használja.
  • A GPars része egy a Java Fork/Join könyvtárára épülő magasabb absztrakciós réteg. Ez a köztes réteg -mely a mindennapi fejlesztést hivatott megkönnyíteni- használata során nem kell szálakkal, poolokkal, és szinkronizációval bajlódnunk. Részletek a dokumentációban találhatók.
  • A Dataflow egy alternatív párhuzamos feldolgozási szemlélet. Szépsége az egyszerűségében rejlik, apró párhuzamos feladatokra bonthatjuk az alkalmazásunkat, és amikor az egyik darabka még egy ki nem értékelt adathoz szeretne hozzáférni, akkor blokkolt állapotba kerül amíg egy másik ki nem értékeli azt. Működéséből adódóan nincs verseny helyzet, nincs Dead és Live Lock sem többek között. Megkötés, hogy csak egyszer adhatunk értéket a DataflowVariable életciklusa során.
  • Az Agentek szál-biztos, nem blokkoló párhuzamosítást tesznek lehetővé, és ehhez annyit kell tennünk, hogy az osztályunkat a groovyx.gpars.agent.Agentből származtatjuk. Fontos különbség a Dataflow modellhez képest, hogy az Agent tartalma tetszőlegesen változtatható.
  • Természetesen elmaradhatatlan kellék a méltán népszerű Actor modell. Leegyszerűsítve az Actorok üzeneteket fogadnak, továbbítanak, és válaszolnak azokra. Minden üzenet bekerül egy sorba, majd onnan a feldolgozásra. A megoldás előnye, hogy implicit szál-biztos, nem blokkolt, és nincs szükség szinkronizációra sem a soros feldolgozás miatt. Lényeges tulajdonsága az Actor rendszernek, hogy nem hagyományos szál-kezelést használ, hanem saját maga menedzseli a feladatokat. Létezik állapot-tartó, és állapot-mentes Actor egyaránt.
Ahogy a Barátkozás a Groovyval bejegyzésben is leírtam, a Groovy nem kezdő programozók kezébe való eszköz. Szép és jó ez a sok beépített okosság, de az alapok ismerete nélkül csak még jobban összezavarnak mindent, megnehezítik a hiba feltárást és az elhárítást is. Remélem sikerült kedvet csinálnom a téma mélyebb megismeréséhez, ráadásként pedig egy igazi "ínyencséget" hagytam:
def deliver(String n) {
 [from: { String f ->
     [to: { String t ->
         [via: { String v ->
             println "Delivering $n from $f to $t via $v."
         }]
     }]
 
 }]
}
deliver "Béla" from "Mezőberény" to "Kisfái" via "Traktor" // Delivering Béla from Mezőberény to Kisfái via Traktor.

2014. május 7., szerda

import groovy.transform.Immutable

A Groovy szemlélet ellentétben a Java-val a nyiltságról szól. Minden alapértelmezetten publikus, nem kell bajlódnunk getter/setter írásával (persze a háttérben történnek dolgok, most a szemléletről beszélek), a metaclass jóvóltából módosítani lehet osztályok működését, és még sorolhatnám. A most bemutatásra kerülő lehetőség mégis sokszor tud hasznos lenni.
@groovy.transform.Immutable
class A { Integer a }
new A()
A kódot futtatva az alábbi eredményt kapjuk:
org.codehouse.groovy.runtime.metaclass.MethodSelectionException: Could not find which method <init>() to invoke from list:
public A#<init>(java.util.HashMap)
public A#<init>(java.lang.Integer)
Tehát a fordító nem tudta eldönteni, hogy melyik konstruktorát használja az osztálynak, ami azt jelenti, hogy az ilyen intefésszel ellátott osztályok tagjainak kötelező kezdő értéket adnunk.
@groovy.transform.Immutable
class A { Integer a }
new A(0).a = 1
Mikor megpróbáljuk módosítani az adattagot, egy kivétellel hálálja meg a JVM.
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: a for class: A
A történet eddig nem nagy szám, mert az alábbi kódsorok ugyanezt az eredményt adják (kicsit több gépeléssel):
class A {
final Integer a
A(a) { this.a = a }
}
new A(0).a = 1
De mi a helyzet ezzel:
@groovy.transform.Immutable
class A { List a }
A a = new A([])
a.a << 1
Ennél az esetnél mutatkozik meg, hogy az annotáció mivel több egy mezei final módosítónál. A listánk bizony egy Collection.UnmodifiableRandomAccessList lett. Tovább kísérletezve mi történik, ha egy saját osztályt teszünk adattagként A-ba.
@groovy.transform.Immutable
class A { B b }
class B { Integer b }
A a = new A(new B())
java.lang.RuntimeException: @Immutable processor doesn't know how to handle field 'b' of type 'B' while constructing class A.
@Immutable classes only support properties with effectively immutable types including:
- Strings, primitive types, wrapper types, BigInteger and BigDecimal, enums
- other @Immutable classes and known immutables (java.awt.Color, java.net.URI)
- Cloneable classes, collections, maps and arrays, and other classes with special handling (java.util.Date)
Other restrictions apply, please see the groovydoc for @Immutable for further details
Amennyiben ellátunk egy osztályt ezzel az annotációval, a Groovy fordító garantálja nekünk, az osztály módosíthatatlan lesz, hiszen kötelezően minden adattagjának szintén módosíthatatlannak kell lennie. Bővebb információ a GroovyDoc-ban, amit fontos kiemelni, hogy @Target({ElementType.TYPE}), tehát osztályokra, interfészekre, és enumokra lehet tenni az annotációt, bár utóbbi 2 esetben fordítási hibát fogunk kapni.
Házi feladat a a groovy.transform csomag átböngészése, sok hasznos eszköz lapul még benne.

2014. április 23., szerda

groovy.lang.Tuple kicsit közelebbről

Belefutottam egy aranyos Groovy osztályba, a Tuple-be. Ha jobban megvizsgáljuk az öröklési láncot láthatjuk, hogy a Tuple lényegében egy AbstractList implementáció, és arra való, hogy összetartozó értékeket tároljunk benne. Példányosításkor meghatározzuk a tartalmát, amit a későbbiekben nem lehet módosítani. Kisértetiesen hasonlít a Collections.unmodifiableList()-hez, de a következő példa jól szemlélteti a Tuple eleganciáját:
Collections.UnmodifiableRandomAccessList foo() { Collections.unmodifiableList([]) }
Ezt ne próbáljátok meg lefordítani, nem fog sikerülni. A probléma az, hogy a UnmodifiableRandomAccessList a Collections belső osztája, ha pedig List-re cseréljük a visszatérési érték típusát, akkor a hívó fél részére nem lesz egyértelmű, hogy a lista nem módosítható, vagy ne adj isten egy verzióváltás során lesz UnmodifiableRandomAccessList, és a működő kód megy tönkre (sose láttunk még csak hasonlót sem). Itt jön az elegancia:
Tuple foo() { new Tuple() }
Ez az egyszerű példa megmutatja mi mindenre alkalmas többek között a Tuple, ha a munkatársainkat szeretnénk kicsit megviccelni (dynamic type rulz)
def x = (a,b,c) = { x -> new Tuple(x*10,"${x*20}") } << 5
println "$a,$b,$c,$x"

2014. január 18., szombat

Ping-conf Day 2

A második nap a Ping-conferencián hozta az első nap színvonalát. Yevgeniy Brikman izgalmas demóval egybekötött előadása mindenkit lázba hozott korán reggel. Prezentációját azzal kezdte, hogy elmesélte, hogy másfél éve használják a Play Frameworköt LinkedInnél több mint 60 projekthez nagy sikerrel. Megnevezett két problémás területet tapasztalataik alapján:
  • Nehezen kezelhető komplexitás (szerintem ez nem feltétlen csak Playben jött volna elő az ő esetükben)
  • Borzalmas teljesítmény
Az első ponttal kapcsolatban megmutatta, hogy a LinkedIn oldalán rengeteg apró kis tartalmi rész van, amik ráadásul eltérnek a különböző felhasználó típusonként (újonc, prémium, etc), ezért nem lehetséges egyetlen kontrollerbe tenni a logikát (hecc kedvéért kipróbálták, és a fordító elszállt a scala fájl méretére hivatkozva). A megoldás, amit használnak, hogy minden egyes kis doboz önállóan is él, tehát van saját kontrollere, nézete, meg minden ami kell. Nagy előnye a módszernek, hogy nagyon könnyen tesztelhető, továbbá nagyon könnyen skálázható vízszintesen, ugyanis az egyes modulok szétoszthatóak több szerver között. A publikus oldalak kontrollereiben pedig egyszerűen legenerálják az egyes részeket, és összefűzik a kiszolgálandó HTMLbe. A technika hátulütője, hogy a statikus erőforrásokat a kontrollerben kell kezelni (szerk,. megj. illetve nem lehet ezen erőforrások tömörítését, egybefűzését valamilyen pluginnel leegyszerűsíteni).
Ezt a fragmentációs technikát választva az alábbiak igényelnek külön odafigyelést:
  • Cookiek esetén szükségük van egy metódusra, ami a darabkák headerjeiből összefűzte a Cookie bejegyzéseket
  • Statikus erőforrások beillesztésére szintén a headerbe kell tenni  X.CSS ill. X-JS bejegyzéseket, amit aztán összefűznek, deduplikálják, és a HTML headbe tesznek (erről később, hogy miért pont oda)
  • Hibakezeléshez a legfelső réteg kiolvassa a darabkák státsz-kódját
Miután végigmentünk a komplexitás témakörén, áttértünk a teljesítmény problémákra. Első lépésben átalakította az alkalmazást, hogy a hagyományos String alapú HTML generálás helyett streamelje a kimenetet. A Scala nyelvben vannak natív eszközök, mindössze egy custom render templatet kellett bekötni a Playbe (az alapértelmezett StringBuildert használ), és az Enumerator, Future párossal megoldotta, hogy a szerver az előállított HTMLt azonnal küldte is a kliensnek. Akit érdekel részletesebben a dolog itt tud utánaolvasni. Ez azért növeli a teljesítményt, mert a HTML headben elhelyezett erőforrások letöltésével nem kell a kliensnek megvárnia, amíg minden kis darabka összeáll. Következő trükk, amivel tovább fokozta a teljesítmény és a felhasználói élményt eléggé leleményes. A darabkák kimenetét a HTML bodyn kívülre, egy script type="text/html-stream" elembe tette, majd JavaScripttel a helyükre illesztette, amikor végzett velük a szerver. A módszer előnye, hogy nincs szükség darabonként egy AJAX kérésre, így tehermentesíthető a szerver, mégis a felhasználó azonnal kap választ a kérésére. Amire érdemes odafigyelni, hogy a hirtelen a helyükre kerülő elemek miatt ugrálhat az oldal, megzavarva ezzel az éppen kattintani vágyó felhasználót, valamint a headerek már a kérés elején kiküldésre kerülnek, ezért azokat a kérés további részében nem lehet változtatni. Nehezebb az oldalt tesztelni, és SEO problémák is felmerülhetnek. A demóalkalmazás egyébként elérhető githubon.

A következő prezentációt James Proper tartotta, aki szintén a teljesítmény-optimalizációt választotta témájául. Az első dolog, amit szép teljesítmény-tesztekkel bemutatott, hogy az aszinkron nem egyenlő a gyorsasággal, sőt! Egy egyszerű példán keresztül bebizonyította, hogy az aszinkron kérés mennyivel tud lassabb lenni a sok context switch miatt, amik elveszik a drága processzoridőt. Folytatásként több módszert is bemutatott, amivel növelhető a teljesítmény, és elkerülhető a rengeteg context switch.
  • Az első javaslata az volt, hogy használjuk a Scala beépített ExecutionContextjét, ami a Java Fork/Join osztályaira épül. Bővebben
  • Második lehetőségként azt ajánlotta, hogy ne használjuk az első pontban említett contextet, hanem váltsunk ImmediateExecutionContextre
Általános jó tanácsként említette, hogy sose használjunk blokkolt erőforrásokat ExecutionContextek használatakor, ha mégis szükségünk van rá, akkor inkább konfiguráljunk menetirányítókat típusonként (na jó ezt senki sem érteni :) "Configure dispatcher per type"). Mivel az Akka tud ilyet, és a Playben van beépített Akka, adja magát, hogy azt érdemes használni.
Kiemelte azt is, hogy nagyon nagyon nagyon nagy weboldalaknál lehet teljesítmény-növekedést elérni, ha a routingot több különálló fájlba tesszük, és egy custom router megírásával csak a megfelelő fájlt dolgozzuk fel, amikor a kérés beérkezik. A gyorsulás oka, hogy a  Play regexpeket illeszt az URI-re, és ha több száz illesztést kell csinálnia, mire a route fájl végén megtalálja a keresett bejegyzést, az felesleges processzor-terhelést jelent. A futtatott benchmarkoknál pár százalékos teljesítmény-növekedés volt mérhető.


A teljesítmény optimalizációs témaköröket elhagyva a következő előadást ismét  Julien Tournay és Pascal Voitot tartotta, és témája a Scalaz-Stream volt. A prezentáció lényege pár mondatban összegfoglalva annyi, hogy miként lehet streamek fogadására lecserélni a jelenleg is működő Iteratee megoldást Playben erre, az egyébként még fejlesztés alatt álló, megoldásra. A prezentációban egyetemi előadásokra emlékeztető részletességgel  mesélték el többek között, hogy miért is jó ez az újfajta megközelítés, hogy milyen problémákba ütközhetünk a HTTP, és a WebSocket eltérősége miatt, stb.


Ebédszünet előtt még Adam Evans és Asher Glynn tartott egy (számomra nem annyira érdekes) előadást arról, hogy milyen tapasztalataik voltak, amikor a BBC gyerekeknek szóló PHP-s megoldását lecserélték Play frameworkre. Architekturálisan volt egy PHP frontendjük, ami a BBC Java-s backendjéhez fordult adatokért. Ami miatt a Playre esett a választásuk:
  • Mert Scala, ezt nem is fejtették ki bővebben
  • Full stack web-framework
  • Reaktív, könnyű benne non blocking szolgáltatásokat készíteni és hívni
  • Typed safe template rendszer eléggé awesome
  • Sok vállalat használja, és zizeg az egész
Miután kiválasztották a keretrendszert csináltak egy pilot projektet, ami nagyon jól sikerült, a PHP fejlesztőknek könnyű volt átszokni Scalára, és mindenki boldog.


A sponsor pitch után következő előadás igazán érdekes volt. Grant Klopper a The Guardian hírportál frontendjét kiszolgáló alkalmazásról beszélt, milyen problémákkal kell szembenézniük, milyen megoldásaik vannak, stb. Elmesélte, hogy naponta három Budapestnyi ember látogatja meg a weboldalukat, átlagosan mindegyik megnéz három oldalt. 900 kérést kell kiszolgálniuk minden másodpercben. A rendszer teljesen nyílt forrású, és elérhető a githubon, sőt nem csak elérhető, hanem konkrétan onnan buildelik a live rendszert. A teljes frontend letölthető egyetlen futtatható jar fájlként, és elindítható a java -jar frontend-artifact.jar paranccsal. A rengeteg kérés miatt mindent cachelnek, és a rendszer garantálja, hogy nem töltődik be sohasem ugyanaz a tartalom kétszer, a második kérés mindenféleképpen cache hit lesz. Beszélt továbbá még a deployment folyamatukról is, ami abból áll, hogy elindítanak három új verziót, regisztrálják őket a load balancerbe, majd lekapcsolják a régi hármat. Előadás után külön odamentem hozzá, és rákérdeztem miként kezelik azt a szituációt, amikor az összes node fut, de valami kódváltozás miatt a cachelt elem is megváltozik, és a régi verzióknak még a régit kell kiszolgálniuk, az újaknak pedig már az újakat. Dolgozom elosztott szinkron cachel, és ez nálunk bizony okoz problémát és fejfájást, éppen ezért mi kikapcsoljuk a cachet egészen addig, amíg minden nodeon az új verziójú szoftver nem fut. A válasz egész egyszerűen annyi volt, hogy sehogy. Azt mondta, hogy olyan rövid ideig fordulhat elő ez az eset, és a magas kérésszám miatt a statisztikában meg sem jeleni az a pár hibás kérés.


Utolsó előtti előadást Tobias Neef tartotta a kontrollerek absztraktálásának lehetőségeiről a Playben, hogy a lehető legjobban elkerülhessük a kódismétlést. Pár pontban összeszedve a lényeg:

A konferencia Johan Andrén előadásával zárult, aki a különböző aszinkron lehetőségekről beszélt Scala és Java platformokon. Először a problémára világított rá Johan, miszerint az általunk írt kód a kiszolgálási idő nagyon kicsi részében van végrehajtási fázisban, és az idő nagy részét különböző erőforrásokra való várakozással tölti. Tegyük fel, hogy ha van 20 adatbázis, és 200 kiszolgáló szálunk, akkor a 200 + 1-edik adatbázist nem használó szál nem lesz kiszolgálva. A problémára háromféle megoldást mutatott be.
Az első, használjuk bátran a Future és Promise osztályokat (részletes információt itt találsz). Hátrányának azt nevezte meg, hogy amikor több rétegen keresztül dobáljuk a Future osztályokat, a rétegek között állandó jelleggel konvertálni kell azokat. Például egyik szerviz visszatér a userek listájával, de a hívónak JSON listát kell visszaadni, amit az őt hívónak HTML kóddá kell alakítani, stb. stb. Javaslata szerint az alábbi esetekben érdemes ezt a megoldás választanunk:
  • Amikor más szolgáltatásokkal kommunikálunk
  • Párhuzamos végrehajtásra, amikor a szálak teljesen elkülöníthetők egymástól
  • Egyszerű háttérszolgáltatások implementálásakor
Utóbbi esetre nem tenném a nyakam, Tomcat esetén mivel a kiszolgáló szál referenciát őriz a Future objektumra szépen bevárja annak végeredményét, szóval én nem javaslom ezt a kombinációt.
Következő lehetőségként az Akka Actorsokat vetette fel, segítségével esemény vezérelt programozást tudunk megvalósítani. Az eljárás lényege, hogy van egy bemenő sor, amibe be tudják a kiszolgáló-szálak küldeni a kéréseket, és a rendszer egy szálon szépen sorban végrehajtja azokat. A technika nem sebességéről híres, blokkolja a többi futó szálat, de cserébe jól skálázható. Mikor használjuk:
  • Amikor állapotokra van szükségünk
  • Adat streamelés
  • Esemény vezérelt programozásra
  • Háttérfolyamatok végzésére
Utolsó lehetősségként a Iteratee-k használatát mutatta be. Sajnos ennek nincs Java-s megfelelője, a Chunks API tud valami hasonlót, de képességei messze elmaradnak a Scala natív megoldásától. A dolog lényege, hogy pici szeletekben adható át a feldolgozásnak az adat (Enumerators), és a rendszer lényegében reagál az adatra, és várja a következő darabkát.
  • Enumerator[A] -> Iteratee[B, R]
  • Enumerator[A] -> Enumeratee[A, B] -> Iteratee[C, R]
Lényeges külömbség a hagyományos feldolgozással szemben, hogy az első hibás darabka esetén már lehet kezelni a hibát, nem kell a teljes adatfolyamot megvárni.
Mikor használjuk:
  • Streamel adatok esetén

A konferencia végére igencsak megfogyatkozott a létszám, láthatóan mindenki kellőképpen elfáradt. Meg kell hagyni, hogy elég tartalmasak voltak az előadások, szóval le a kalappal a szervezők előtt, nem bízták a véletlenre a mentális zombiságunkat. Gratula ezúton is.

2014. január 16., csütörtök

Ping-conf Day 1

Volt szerencsém a napomat a Ping-conferencián tölteni. A 2 napos rendezvény fő témája a Play Framework, mely a legelterjedtebb, hanem az egyetlen Scala nyelvre épülő webes keretrendszer. A rendezvény bár nem volt olcsó, mégis elég nagy tömeget mozgatott meg, hogy őszinte legyek sokkal kevesebb magyar résztvevőre számítottam. A konferencia, mely minden igényt kielégítő módon volt megszervezve, népszerűsége láttán elgondolkoztam, hogy a JUMon is több Scalás előadással készülhetnénk, mert az itt látottak alapján komoly érdeklődés van a Scála iránt Magyarországon.


Az első előadást Sadek Drobi tartotta, és a funkcionális programozás alapjairól beszélt, betekintést nyújtva példákon keresztül a composability szépségeibe. Röviden: minden funkciónak kötelezően van visszatérési értéke, és ezekkel a funkciókkal könnyedén lehet hívási láncokat alkotni. Az előadás végén elhangzott egy kérdés, miszerint mennyivel jobb megoldás ez, mint a Java 8-cal érkező Lambda kifejezések? A válasz tömör volt és egyszerű, mivel a Java hemzseg a void metódusoktól, igazán sosem lesz benne az az erő, amit a Scala nyújtani képes.


A második prezentációt Matt Hamer tartotta, és témának a többszörös JVM architektúrákat választotta. Számomra ez volt az egyik legérdekesebb előadás. Matt elmesélte, hogy az alkalmazás, amit fejlesztenek több részre van felosztva, egy prezentációs rétegre, ahol Play biztosítja a kiszolgálást, és n+1 business rétegre -szintén Play-, ami értelem szerűen backendként funkcionál a prezentációs rétegnek, és egyéb klienseknek. A szeparáció elsődleges okainak a következőket nevezte meg:
  • Könnyebb Load balancing
  • Izoláció a rétegek között
  • Különböző gyakorisággal történik a deployment
Miután ismertette az architektúrát, Matt részletesen kifejtette, a két réteg közötti kommunikációs lehetőségeket. Először számba vette a JSON küldözgetést, de ez két JVM között nem a legideálisabb választás, ezért áttért Java közelibb megoldásokra.
  • java.io.Serialize, mint az köztudott érzékeny a verziózásra, egyáltalán nem rugalmas
  • Protocol Buffer, sokkal jobb választás, de a konfigurációs állományokat állandóan karba kell tartani
  • Akka custom serialization, ez a megoldás bizonyult a legjobbnak, orvosolja a kompatibilitási problémákat, nem kell konfigurációs állományokat napra készen tartani, és nem utolsó sorban a Playben van beépített Akka

Egy rövid szünetet követően a harmadik téma következett "Build reactive apps on the JVM" címmel. Christopher Hunt saját fejlesztésű keretrendszerüket mutatta be. Megmondom őszintén nem tudtam teljesen elképzelni a felépítését, de nagy vonalakban egy Play frameworkre épülő, Rhino enginet használó JavaScript, igen jól olvastad JavaScript webframeworkről volt szó. A rendszerhez már most is elérhető néhány plugin (CoffeeScript, JS Hint, Less, Require.js, Jasmine). Ez jutott eszembe.

A következő, szám szerint a negyedik prezentáció Yann Simon nevéhez fűződik, témája pedig a Cake Patter In Scala gyakorlati bemutatása volt. A példa alkalmazás 3 részre volt tagolva, 1 frontend, és 2 backend modulból állt. Az első verzióban a frontend modulba be voltak égetve a backend modul szervizei, és a hívások. A megoldással a legnagyobb probléma, hogy a teszt-piramis (unit, component, integration) valójában nem is piramis alakú, hiszen kis számú unit teszten kívül lényegében mindent az integration teszt szintjén kell vizsgálni. A probléma felismerését követően elkezdte átalakítani az egyes rétegeket, amíg el nem jutott arra a szintre, hogy mindent a Scala Component based Dependency Injectionnel oldott meg. Én valahol a folyamat közepén vesztettem el a fonalat, mikor elszaporodtak a trait x extends y with z, meg class a extends b self: c kifejezések. Mindentől függetlenül 2 fontos dolgot leszűrtem:
  • Ez a megoldás annyival jobb más DI megoldásoknál (Spring, Guice), hogy már fordítási időben kiderül ha valami nem stimmel, és nem csak futás időben jönnek elő a problémák
  • Nem érdemes kimaxolni ezt a megoldást, mert az egyszerű példa esetén is 20 trait "osztályra" volt szükség, inkább az elégséges szinten érdemes megállni

Az ebéd szünetet követően Julien Tournay és Pascal Voitot tartott egy érdekes és egyben vicces szösszenetet "Typesafing your blobs" címmel. Hogy pontosan elénk tárhassák a problémát egészen az ősrobbanásig mentek vissza, majd az utazás a dinoszauruszok korán át egészen napjainkig tartott. Történelmi kalandozásuk során megálltak egy pillanatra a modern korba, amikor az emberek kísérletet tettek egy egységes protokoll kidolgozására, hogy a különböző informatikai rendszerek tudjanak egymással kommunikálni, és megszületett a SOAP. Folytatásként arról beszéltek, hogy a technológia fejlődésével egyre inkább ki vannak téve a webes szolgáltatások egymásnak és pontosan ezért egyre fontosabb, hogy a külső forrásokból érkező adatokat rugalmasan tudjuk kezelni és ellenőrizni. Példának hozták fel, hogy 2 éve a WebSoket még csak Sky-fi volt, ma pedig már szolgáltatások épülnek rá, utalva ezzel, hogy milyen rohamosan fejlődik a világ. A fent említett dolgok vezették rá a srácokat, hogy egy teljesen új, a régivel kompatibilis, de a mai kor elvárásainak megfelelő (vagy inkább jövőbe mutató) validációs API-t fejlesztettek a Playhez. Természetesen teljesen funkcionális szemléletben készült a kód, kihasználja a Scala macrokban rejlő lehetőségeket, és a case classoknak valamint az implicit deklarációnak köszönhetően marshallerek, és konfigurációk nélkül képes egy JSON stinget Scala osztállyá alakítani. Az API másik erőssége, hogy POST-olt adatok ellenőrzésére is alkalmas kvázi módosítás nélkül. A fejlesztés még nagyban folyik, nem production ready a cucc, de akit érdekel itt megtalálja a projektet.

Az utolsó előtti előadást Matthias Nehlsen tartotta, és reaktív alkalmazás-fejlesztésről beszélt Play, Scala.js, és ReactJs segítségével. Engem főleg a ReactJs fogott meg, régóta keresek valami hasonló megoldást, de az AngularJs, Ember.js, KnockOut.js eddig nem nagyon jött be. A ReactJs-nek nagyon rövid a tanulási görbéje, és a teljesítménye is kiemelkedő, köszönhető annak, hogy a valós DOM fa mellett van belül egy virtuális DOM, és a DOM manipuláció után csak a diff-et szinkronizálja a valós fával. Érdekes volt látni, hogy milyen egyszerűen kötötte össze a nézeteket renderelő ReactJst a Scalaból generált JavaScript kódot, és a Scala.js-t a Play-el Mathias, lényegében minden Scalában volt megírva, volt pár action binding JavaScriptben, és némi HTML-nek látszó ReactJs template.

Utoljára maradt Nicolas Martignole esettanulmánya, melyben két általa fejlesztett projekt sikertörténetét tárta a nagyérdemű elé. Webes rétegnek Play2-t választotta, adattárolásra és cachelésre pedig Redist. Láthatóan ő boldog volt a megoldással :).

Ennyi történt az első napon, igyekszem holnap is megjelenni/jegyzetelni. Folytatás...

2012. december 30., vasárnap

Reflection - a bajkeverő csodagyerek

Albert Hoffmann szavai csengnek a fülemben, amikor a Reflection-re gondolok: "bajkeverő csodagyerek". Egyfelől milyen hasznos, hogy futásidőben példányosítani lehet osztályokat, hogy feltérképezhetjük őket, hogy módisíthatjuk őket, stb. Aki dolgozott már modern webes keretrendszerrel az tisztában van a Reflection minden előnyével, hisz ez a legegyszerűbb módja feltölteni egy objektumot a kéréssel érkező paramétereknek, vagy egy proxy segítségével megváltoztatni egy adattag viselkedését. Ilyen értelemben elképzelhetetlen lenne a (mai értelemben vett) Java EE terjedése, mert Reflection nélkül még mindig a kőkörban élnénk.
RequestParams rp = new RequestParams(); 
final Field[] fields = rp.getClass().getDeclaredFields();
for (final Field field : fields) {
 firstChar = String.valueOf(field.getName().charAt(0));
 methodName = "set" + field.getName().replaceFirst(firstChar, firstChar.toUpperCase());
 try {
  method = rp.getClass().getMethod(methodName, String.class);
  method.invoke(rp, request.getParameter(field.getName()));
 } catch (final SecurityException e) {
 } catch (final NoSuchMethodException e) {
 } catch (final IllegalArgumentException e) {
 } catch (final IllegalAccessException e) {
 } catch (final InvocationTargetException e) {
 }
}
A példa jól szemlélteti, hogy pofon egyszerű feltölteni a kapott paraméterekkel egy objektumot, újabb paraméterek létrehozásakor nem kell setterekkel bajlódni, ráadásul a kód teljesen hordozható, így bármely osztályra dinamikusan alkalmazható (a példa csak String-eket kezel az egyszerűség kedvéért).
Itt merül fel a kérdés, hogy a Reflection nem egy kétélű fegyver? Mivel mondhatni teljes hozzáférést biztosít objektumjaink belső működéséhez és felépítéséhez, nem lehet ezt rosszra is használni? A válasz egyértelmű IGEN! Daily WTF-en találtam ezt a kedves kis szösszenetet:
class ValueMunger extends Thread {
    public void run() {
        while(true) {
            munge();
            try { sleep(1000); } catch (Throwable t) { }
        }
    }
    
    public void munge() {
        try {
            Field field = Integer.class.getDeclaredField( "value" );
            field.setAccessible( true );
            for(int i = -127; i <= 128; i++)
            field.setInt( 
                Integer.valueOf(i),
                // either the same (90%), +1 (10%), or 42 (1%)
                Math.random() < 0.9 ? i : Math.random() < 0.1 ? 42 : i+1  );
        } catch (Throwable t) { ; }
    }

}
A fenti kódrészlet az Integer wrapper cachet-t zagyválja egy kicsit össze, időnként nem a valós értéket kapjuk vissza, hanem az élet értelmét, ami üzleti kritikus alkalmazásokban igen nagy probléma, nem szeretnénk egy pészmékerben ilyesmi kóddal találkozni, de azt sem szeretnénk, ha a banki alkalmazás viccelné meg a bankszámlánkat időnként. A final mezőink, a private tagjaink sincsenek biztonságban, ahogy az Enum-jaink sem, és ezen a ponton a végtelenségi lehetne sorolni az ártalmas és vicces példákat, ugyanis kijelenthető, hogy (Sun/Oracle) Java verziótól függően szinte mindenhez hozzá lehet férni Reflection API segítségével, nincs menekvés.
Ártalmas kódot hagyhat hátra kilépő munkatárs, tölthetünk le ártatlanul egy külső osztálykönyvtárral, szóval több forrásból is beszerezhetőek, de vajon mit lehet tenni ellenük? Az egyetlen gyógyszer a tudatos és körültekintő felhasználás.

  • Használjunk statikus kódelemzőt, és szigorúan tartsuk számon azokat a részeket, ahol az alkalmazásunk Reflection API-t használ.
  • Használjunk nyílt forrású könyvtárakat, és fordítsuk magunk (kódelemzés után).
  • A letöltött osztálykönyvtárak hitelességét minden lehetséges módon ellenőrizzük, ugyanis koránt sem biztos, hogy senki nem törte fel kedvenc keretrendszerünk weboldalát, és cserélte ott le a letölthető jar-okat saját módosított verziójára (jó lenne a Linux repository-khoz hasonló központosított megoldás).
  • Csak a készítő által aláírt osztályokat, és jar-okat használjunk, ha külső forrásra kell támaszkodnunk.
  • Minimalizáljuk a külső forrásokat, ne használjunk két külön osztálykönyvtárat közel ugyanarra a feladatra.
Ha van még ötletetek, milyen módszerekkel védhetjük még a hátsónkat, kérlek ne tartsátok vissza magatokat, és írjátok meg, nekem hirtelen ennyi jutott eszembe. A bejegyzésben taglalt téma nem újkeletű, de azt hiszem sosem lehet róla eleget beszélni.

2012. november 4., vasárnap

Eclipselink beizzítása Jboss 7.1 környezetben

Valaha fejlesztettünk egy portált Liferay platformon. Mivel a fejlesztés elején a gyári Service Buildert elvetettük, teljesen kézenfekvő megoldás volt EJB/JPA párossal implementálni az üzleti és a perzisztens réteget. Első körben Hibernatere esett a választás, mert a Jboss alkalmazásszerver "natívan" támogatta, nem volt más dolgunk, mint deployolni az alkalmazást, és működött. Fejlesztés során nem is akadt semmi problémánk, ám az élesítés előtti utolsó hajrában szembesültünk azzal a ténnyel, hogy a perzisztens réteg teljesítménye a béka feneke alatt van, majd egy kis kutakodás után kikristályosodott, hogy a Hibernate nem bánik túl kedvezően az erőforrásokkal. Nincs mese, másodlagos cachet kell beüzemelni (Ehcachere esett a választás), de sajnos még ezzel sem hozta azt az alkalmazás, amit elvárnánk/megszoktunk. Ekkor jött az ötlet, hogy dobjuk ki a Hibernatet. Eclipselinkkel már volt tapasztalatunk, ráadásul nem is rossz, így megejtettük a váltást. Egyáltalán nem bántuk meg, a teljesítmény az egekbe szökött (pontos számadataink nincsenek, egyszerűen nem bírtuk elérni, hogy az adatbázisszerver CPU 10% fölé menjen, az addigi konstans 80% helyett). Eclipselinkre való átállás nem volt zökkenőmentes, nem pont ugyanúgy értelmezi a JPQL-t mint a Hibernate (egész pontosan az Eclipselink JPQL-t valósít meg, míg a Hibernate a saját HQL-jét használja), ezen felül a Jbossból is ki kellett gyomlálni pár Hibernate jart, úgy, hogy közben a Liferay, ami egyébként Hibernatre épül, még működőképes maradjon.

Az élesítés óta megjelent a Liferayből 2 újabb verzió, és szerettem volna frissen tartani a rendszert, de természetesen az új Liferayhez új Jboss is dukál. A frissítendő architektúra hibridsége miatt nem volt könnyű szülés a művelet, ennek lépéseit szeretném megosztani.

Az új Jboss architektúra teljesen modularizált, ezért egy 3rd party lib használata nem annyi, hogy bemásoljuk a megfelelő jar-t a classpathra, hanem modulként definiálnunk kell a rendszerben. Nincs ez másként az Eclipselinkkel sem. Modul létrehozásához annyit kell tennünk, hogy létrehozzuk a modules/org/eclipse/persistence/main könyvtárakat az alkalmazásszerverünkben, és a main könyvtárba bemásoljuk az eclipselink.jart, a konfigurációhoz pedig létre kell hoznunk ugyanitt egy module.xml-t, amiben a modult magát definiáljuk.


 
 
 
  
  
  
  
  
  
  
  
  
  
  
  
 

Az előző lépéshez hasonlóan egy Ant, és egy adatbázis modult is létre kell hozni (estemben egy PostgreSQLt).

    
        
    


    
        
    
    
        
        
        
    

Ezután nincs más dolgunk az alkalmazásszerveren, mint a standalone/configuration/standalone.xml-t szerkeszteni. Először is definiáljuk a szükséges DataSource-ot.
        
                
                    jdbc:postgresql://[hostname]:[port]/[database]
                    postgres
                    
                        14
                        20
                    
                    
                        postgres
                    
                
                
                    
                        org.postgresql.xa.PGXADataSource
                    
                
            
Lehet, hogy ez Liferay specifikus, de nálam volt Hibernate konfiguráció is ebben az xmlben, ami miatt timeoutolt a perzisztens réteg inicializálása, így azt egy kecses mozdulattal kitöröltem (egyelőre nem jelentkezett a hiányából fakadó probléma).

Miután az alkalmzásszerver konfigurációjával végeztünk, már csak az alkalmazásunkat kell egy kicsit reszelgetni. Ahol JNDI név szerint hivatkoztunk EJB Session Beanekre, ott a hivatkozott nevet célszerű átírni. A Jboss az EJB deployment során kiírja, hogy milyen neveken regisztrálta a beaneket, nekem nem mindegyik működött ??, de ez a forma bevált: global/[appname]/[serviceimplclassname].
A persistence.xml-ben is be kell állítanunk pár beállítást.
org.eclipse.persistence.jpa.PersistenceProvider
java:/jboss/datasources/LiferayPool

 
 


Utolsó lépésként az alkalmazásunkban be kell állítani, hogy milyen Jboss modulok a függőségei, ezt két féle módon is megtehetjük, vagy a META-INF/MANIFEST.MF állományban vesszük fel az alábbi sort:
Dependencies: org.eclipse.persistence
vagy létrehozunk ugyanitt egy jboss-deployment-structure.xml nevű konfigurációs állományt, az alábbi tartalommal:

  
    
      
    
  

Mindenkinek javaslom, hogy tegyen egy próbát az Eclipselinkkel, számos területen jobb, mint a Hibernate, nem csak teljesítményben és szabványkövetésben. Kedvenc tulajdonságom pl, hogy Hibernattel ellentétben a Session bezárása után is eléri még a DataSourcot (limitáltan), így a lustán inicializált relációkat nem kell kézzel betöltögetni még az üzleti rétegben, hanem a helyükre tett proxyn keresztül később is eléri azokat.

2012. június 27., szerda

Barátkozás a Groovyval

Régóta terveztem, hogy megismerkedek a Groovy rejtelmeivel, és most úgy alakult, hogy egy hosszan tartó barátság első napjait élem. A nyelv ismertetést nem is az alapoknál kezdeném, hiszen azok egy Java fejlesztőnek nem szabad, hogy gondot okozzanak, mivel visszafelé teljes a kompatibilitás. Sokkal inkább koncentrálnék a kedvenc programozói eszközökre, amikkel új dimenziókba lehet helyezni az eddig megszokott Java programozást.

Van két fontos különbség a Java és a Groovy között, amit mindenképpen megosztanék ezen a ponton. Groovyban nincsenek primitív típusok, még ha látszólag úgy is deklarálunk egy változót, az eredmény mindig egy példány lesz a memóriában. A másik, hogy a dupla egyenlőség vizsgálat, Javatól eltérően érték szerint hasonlít össze!

Minden Java fejlesztő rémálma a NullPointerException, éppen ezért Javaban a műveletek nagy részét megelőzi erre vonatkozó ellenőrzés, ami csak átláthatatlanabbá teszik a kódot. Groovyban ez a teljes vizsgálat elvégezhető egyetlen kérdőjel segítségével:

int foo = bar?.length ? bar.length : -1;
Az eredmény, sokkal tömörebb kód, miközben az olvashatóságot sem rontja a szintaxis. A történet egyszerű. A kérdőjel helyére egy null ellenőrzést ékel a fordító.

Következő hasznos egyszerűsítés amit meg szeretnék említeni az un. Elvis operátort.
int foo = bar ?: -1;
Az Elvis operátorral az alapértelmezett értéket lehet meghatározni, amennyiben az eredeti "érték" false vagy null.

Soron következő kedvencem a GString. Stringek összefűzésének problémájával a legtöbben már egészen biztos találkoztunk. Kis mennyiségű szöveg összefűzésénél még nem is akkora a probléma, mert az egy sorban elvégzett String összefűzés automatikusan egy StringBulder osztályra fordul. Nagyobb mennyiség esetén (hallottam olyan helyről, ahol a mai napig 80 karakter sorhossz) macerássá válik a művelet. A Groovy eszköztárában egy az Expression Language-re kísértetiesen hasonlító megoldást építettek.
String foo = "Foo"
String bar = "${foo} Bar" 
Ezt a funkcionalitást kombinálva a több soros stringek deklarációjával, máris kézzelfogható előnyhöz jutunk:
def sql = """
select * from ${table}
where bar = ${foo}
"""
Fontos tudni, hogy a szimpla idézőjelek között létrehozott 'stringek' hagyományos java.lang.String példányok lesznek, a duplával pedig GStringek, ezért ha nem szeretnénk a GString sajátosságait kihasználni, mindig szimpla idézőjellel példányosítsuk stringjeinket.

Reguláris kifejezések használatát is lényegesen leegyszerűsítették a Groovys srácok.
Pattern pattern = ~/(.*)/
boolean find = 'foo' ==~ pattern
Matcher m = 'foo' =~ pattern

Mint ahogy a bevezetőben említettem, Groovyban nincsenek primitív típusok, most lássuk, hogy ennek miért is van jelentősége. A fordító bizonyos operátorokat automatikusan átfordítja az objektum metódus hívásaira.
a + b // a.plus(b)
a − b // a.minus(b)
a ∗ b // a.multiply(b)
a ∗∗ b // a.power(b)
a / b // a.div(b)
a % b // a.mod(b)
a | b // a.or(b)
a & b // a.and(b)
a ^ b // a.xor(b)
a++ o r ++a // a.next()
a−− o r −−a // a.previous()
a [ b ] // a.getAt(b)
a [ b ] = c // a.putAt(b, c)
a << b // a.leftShift(b)
a >> b // a.rightShift(b)
~a // a.bitwiseNegate()
−a // a.negative()
+a // a.positive()
a <=> b : a.compareTo(b)
Ennek előnye egyrészt, hogy megkíméli a programozót rengeted felesleges gépeléstől, másrészt ezt a működést kihasználva saját osztályainkat is fel tudjuk készíteni, hogy értsék a különböző operátorokat. A Groovyban van is erre jó példa, pl. a Date osztályban.
def today = new Date()
def tomorrow = today + 1
def yesterday = today - 1
assert today.plus(1) == tomorrow
assert tomorrow.minus(1) == today
Fontos megértenünk 2 dolgot az operátorok átfordítása kapcsán. Az egyik, hogy vannak esetek, amikor a visszatérési objektum tipusa más lesz, mint a operandusé.
StringBuilder sb = 'bar' << 'foo'
A másik dolog, a túlcsordulást elhárító típusbővítés, ami azt jelenti például, hogy az 1 + 1.5 az ((BigInteger) 1.5).plus(1) -ra fordul, és az eredmény egy BigDecimal osztályban kerül tárolásra, hiába az Integer állt előbb. A Groovy decimális számok tárolására alapértelmezetten a BigDecimalt használja, elkerülendő a lebegőpontos számok ábrázolásából fakadó hibákat.

A következő érdekesség amire szeretném felhívni a figyelmet a Groovy osztálykezelése. A Groovy egy speciális osztályon keresztűl hozzáférést biztosít az osztályokhoz, és lehetőséget ad azok bővítésére.
String.metaClass.prefixFirstLette = { prefix ->
    return "${prefix}_${delegate.substring(0, 1)}"
}
println 'bar'.prefixFirstLette('foo');

Az előző példában egy újabb speciális Groovy osztállyal találkozhattunk, a Closure-val, mely osztály kiemelten fontos a nyelv szempontjából, és számtalan metódusnak átadható paraméterként.
Closure c = { i ->
    return i
}
println c.call(1)
A Closure segítségével a Groovy szimulálni tudja a Javaból egyébként igencsak hiányzó névtelen függvények használatát.

Következő témakör, amelyet fontos kihangsúlyozni a Groovyval kapcsolatban, hogy natív támogatást nyújt listák és mapok kezelésére, ráadásul számos olyan funkcióval egészítették ki ezen osztályokat, amik megkönnyítik a velük végzett műveleteket. Pár példa a teljesség igénye nélkül:
def words = ['ant', 'buffalo', 'cat', 'dinosaur']
assert words.findAll{ w -> w.size() > 4 } == ['buffalo', 'dinosaur']
assert words.collect{ it[0] } == ['a', 'b', 'c', 'd']

def list = [[1,0], [0,1,2]].sort { item -> item.size() }
assert list == [ [1,0], [0,1,2] ]

assert [1, 3, 5] == ['a', 'few', 'words']*.size() //minden elemen végrehajtja a size() metódust
A GDK plusz extraként kiegészít minden tömböt, kollekciót, és Stringet egy további toList() metódussal.
def greeting = 'Hello Groovy!'
assert greeting[6..11] == 'Groovy'
assert greeting[0,2,4] == 'Hlo'

A következő érdekesség az XML kezelés Groovyban. Okulva a Java hiányosságából, szintén natív támogatás van XML struktúrák kezelésére.
def builder = new groovy.xml.MarkupBuilder()
builder.book {
    author 'Bar Foo'
    title 'sometitle'
    properties {
        pages 42
    }
}
println builder

  Bar Foo
  sometitle
  
    42
  

A Streamek kezelésében is hoz változást a Groovy. Javaval ellentétben nem kell ciklust írnunk a tartalom áttöltéséhez.
def address = 'http://jpattern.blogspot.com/favicon.ico'
def file = new FileOutputStream(address.tokenize("/")[-1])
def out = new BufferedOutputStream(file)
out << new URL(address).openStream()
out.close()

Utoljára hagytam a legkevésbé fontos, de talán mégis hasznos újítást az importok területén. Lehetőség van Groovyban importált osztály-t aliasszal megjelelölni.
import org.springframework.context.i18n.LocaleContextHolder as LCH
...
def locale = LCH.getLocale()

A pozitívumok után következzenek a negatívumok, bár személy szerint nem sok ilyet találtam. Az első, hogy a Groovy nem támogatja belső osztályok definiálását, ami szerintem a Java eszköztárának egy fontos kelléke. A Másik, hogy dinamikus típusú nyelv lévén az IDE támogatás meg sem közelíti a Javaét. Bár mindhárom elterjedt IDE (Netbeans, Eclipse, IntelliJ) rendelkezik Groovy támogatással, Javahoz szokott fejlesztőként számtalan kényelmi funkciót kell nélkülözni.
Véleményem szerint egy elég erőteljes nyelv lett a Groovy, a fejlesztők igyekeztek a Java hiányosságaiból tanulni, miközben megőrizték a Javaban rejlő erőt teljes mértékben. Bár mindenki azt csinál amit akar, én személy szerint alapos Java ismeretek nélkül nem ajánlom a nyelvet kezdőknek, ugyanis ahhoz elég sok dologban tér el a Javatól, hogy rossz szokásokat fejlesszen későbbi Java programozáshoz. Ilyen pl. a dupla egyenlőség vizsgálat, a String automatikus StringBuilderré alakítása bizonyos operátorok használatakor, A streamek kezelése, stb. Remélem további ismerkedésre inspirál mindenkit ez a kis írás, és sokan kiegészítik ezzel a remek eszközzel programozói repertoárjukat.

2012. március 23., péntek

Könyvajánló: Java fejtörők

A Szabad Szoftver Konferencián volt alkalmam megvásárolni Joshua Bloch és Neal Gafter, Java fejtörők című könyvét. Bevallom őszintén elsősorban az akciós ára miatt vásároltam meg, ám azonban azóta az egyik kedvenc olvasmányom lett, mert mindamellett, hogy sokat lehet belőle tanulni, és megmozgatja a fogaskerekeket, még szórakoztat is.
Lássuk először a szerzőket. Joshua Block neve ismerősen csenghet, a Carnegie Mellon University-n doktorált, a Java 5 nyelvi bővítésein dolgozott és a Java Collections Framework tervezését és fejlesztését is irányította többek közt. Dolgozott a Sun Microsystemsnél és jelenleg a Google egyik főmérnöke. Szerzője a díjat nyert Hatékony Java című könyvnek. Ez utóbbi műve szerintem kötelező olvasmány minden Java fejlesztőnek! Neal Gafter a University of Rochesteren szerzett doktorátust, majd az Sun Microsystemsnél irányította a Java fordító fejlesztését rangidős mérnökként. Jelenleg ő is a Googlenél - kinél másnál - dolgozik szoftvermérnökként.
A könyv a Java nyelvben rejlő csapdákkal, buktatókkal, és olyan szélsőséges esetekkel foglalkozik, amelyekbe nap mint nap belefuthatunk munkánk/hobbink során, és kellő ismeret nélkül csak a fejünket vakargathatjuk a nem várt eredmény miatt. A könyv 9 gyakorlati fejezetbe kategorizálja az összesen 95 esetet, és az egyszerűbbtől a bonyolultabb példák felé halad, a leg harcedzettebbeknek is sokszor feladva a leckét. Nem spoilerezek többet, tessék megvenni a könyvet, ha még nincs meg :).

2012. január 21., szombat

Weak és Soft referenciák a Javaban

Több éves Java tapasztalattal a hátam mögött hallottam először a gyenge (weak) referenciák létezéséről a nyelvben, és úgy gondoltam érdemel pár szót a téma kör. Javaslom, akinek nem világos a JVM szemétgyűjtőjének (továbbiakban GC) működése, ezen a ponton olvassa el Viczián István idevágó bejegyzését. Tehát mint tudjuk a GC kidob a memóriából minden olyan objektumot, melyre nem mutat már egyetlen referencia sem. Amikor létrehozunk egy objektumot, Integer foo = new Integer(0), akkor a foo egy strong reference lesz rá, és amíg az objektum "strongly reachable", addig a GC-nek tabu. Mivel ezeket az objektumokat nem tudja a GC felszabadítani, a memória beteltével jön a fránya OutOfMemotyError, és az alkalmazás kilép. Egy darabig persze lehet növelni a memóriát, majd a fizikai határok elérésével lehet elosztani az alkalmazást, de előbb álljunk meg egy szóra! Vajon minden objektumra szükségünk van a memóriában? Kézenfekvő megoldás, hogy bizonyos objektumokat azonnal megszüntessünk, amint nincs rájuk szükség, viszont az objektumok újbóli létrehozása is nagy költség, ebben az esetben pedig értékes processzoridőt fecsérelünk az állandó memóriaallokációra, példányosításra, stb. Arany középútként az 1.2-es, igen az 1.2-es Java verzióban bevezették a weak referenciákat, melynek implementációja a WeakReference osztály. A WeakReference referenciát tárol az adott objektumra, annyi különbséggel, hogy ezt az objektumot a GC első futáskor, szó nélkül eltávolítja a memóriából, felszabadítva ezzel a helyet mások számára.

WeakReference<StringBuilder> weakString = new WeakReference<StringBuilder>(new StringBuilder());

int i = 0;
while (weakString != null && weakString.get() != null) {
 weakString.get().append(i++);
 System.out.println(i);
}

System.out.println("Finish");

Kezdetként futási paraméterként állítsuk be a JVM-nek a maximális memóriát mondjuk 2 Mb-ra (-Xmx2m), majd az értékkel játszva láthatjuk, hogy mennyit változik programunk kimenete. Ez a technika kiválóan alkalmas memória gyorsítótárak készítésére, ám amennyiben kulcs-érték-pár alapú gyorsítótárat készítünk a new WeakReference(myMapInstance) helyett használjuk, az erre a célra készített WeakHashMap implementációt. A WeakHashMap a kulcsokat őrzi weak referenciával, és a MapEntry automatikusan eltávolításra kerül, amikor a kulcs már nincs rendszeres használat alatt.

A WeakReference mellett létezik a SoftReference osztály is, amely azt garantálja, hogy az OutOfMemoryError előtt felszámolásra kerülnek az objektumok, helyet biztosítva az újonan létrejövőknek.

SoftReference softObj = new SoftReference(new Serializable() {
 @Override public void finalize() throws Throwable {
  System.out.print("Finalize runned");

  super.finalize();
 } 
});

StringBuilder sb = new StringBuilder("foobar");
while (true) {
  sb.append(sb.toString());
}
A kimenetben láthatjuk, hogy miközben az alkalmazás akkut memóriahiányban elhalálozott, még utolsó leheletével felszámolta osztályunkat.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Finalize runned at java.util.Arrays.copyOf(Arrays.java:2882)
 at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
 at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
 at java.lang.StringBuilder.append(StringBuilder.java:119)
 at com.blogspot.jpattern.Main.run(Main.java:36)
 at com.blogspot.jpattern.Main.main(Main.java:22)
Java Result: 1
Természetesen ennél complexebb program esetében az alkalmazás tovább tud dolgozni a felszabadult memóriával.A Java specifikáció szerint semmi garancia nincs a finalize() metódus futására!

Evezzünk egy kicsit sötétebb vizekre. Ha jól megnézzük a Reference API dokumentációját, láthatjuk, hogy van még egy ismert implementáció, a PhantomReference. Az első különbség társaihoz képest, hogy a bele helyezett objektumra soha nem tudunk referenciát kérni, ugyanis konstans null értékkel válaszol a get() hívására. Másik nagy eltérés lényege tömören, hogy az ilyen objektumokat a GC csak azelőtt rendezi sorba, mielőtt a fizikai memóriából kitakarítaná. Mivel úgy tudom Darth Vader nagyurat is ennek használata állította át az erő sötét oldalára, én magam nem merészkedtem ennél tovább (esetleg írhatnátok valami konkrét használati esetet).

Előfordulhat, hogy az alkalmazásunkban szükséges tudni, hogy mely objektumokat dobta már ki a GC, és melyeket nem. Ilyen esetben egy ReferenceQueue osztályt kell példányosítanunk, és a queue példányt átadni a WeakReference vagy SoftReference konstruktorának. A bejegyzésben említett referenciákkal óvatosan bánjunk, és csak alapos tervezés és tesztelést követően alkalmazzuk éles bevetésben őket.

2011. december 12., hétfő

Liferay Service Builder, a sötét oldal

Korábban már volt szó a Liferay Service Builderéről, akkor inkább a tudásáról esett szó, most pedig az árny oldalát szeretném taglalni. A Service Builder feladata, hogy egy egységes perzisztens réteget biztosítson anélkül, hogy a különböző WAR-ok eltérő kontextusaival, vagy az eltérő adatbázisok problémájával kellene bajlódnunk. A kezdetben kézenfekvő megoldások mára sajnos nem nyújtják azt a kényelmet, amit egy JPA/JTA-hoz szokott fejlesztő elvár egy efféle megoldástól. Lássuk mik azok a pontok, amiket én személy szerint fájlalok:

  • Az entitásokat leíró XML-ben csak primitív, String, vagy Date típust jelölhetünk meg az adatok tárolására, viszont osztály szinten a primitív adattagok inicializálódnak, tehát nem tudunk olyan mezőt felvenni, amelynek az értéke üres, vagyis NULL. Képzeljük el a User entitásunkat, egy opcionális életkor mezővel. Mivel az életkor alapértelmezetten 0 értéket vesz fel, nem tudjuk megkülönböztetni a csecsemő felhasználóinkat azoktól, akik nem töltötték ki az életkorukat. A példa légből kapott, de jól szemlélteti a primitív típusok hiányosságát. A Liferay készített egy workaroundot a probléma orvoslására, és használhatunk wrapper osztályokat is a primitívek helyett, viszont ebben az esetben szembesülnünk kell két dologgal, mégpedig, hogy a DTD miatt kedvenc IDE-nk figyelmeztetni fog, hogy nem megfelelő adatot írtunk be, másrészt hivatalosan a Liferay nem támogatja ezt a megoldást.
  • Rengetegszer futottam bele, hogy a  String mezők hossza alapértelmezetten 75 karakter széles. Ezen a ponton lehet vitatkozni, hogy valahol meg kellett húzni a határt az adatbázis méretének és a programozói munkának az optimalizációja közben, de véleményem szerint a 75 karakter a legtöbb esetben nem elég. Lehetett volna egy kicsit a felesleges adatbázis helyfoglalás felé billenteni a mérleget. A programozók feledékenyek, a tesztelők meg hanyagok, és ebből majdnem egyenesen következik, hogy 100 String mezőből legalább 1-nél csak túl későn derül ki, hogy nem elegendő a 75 karakter. Természetesen van megoldás, mégpedig az src/META-INF könyvtárban lévő  portlet-model-hints.xml fájlban kell a mezőkre "tippeket" adni a Liferaynek. Persze itt nem csak a mező szélességére adhatunk hasznos tanácsokat, egyéb dolgokat is beállíthatunk, ezeket most nem részletezném.
    A megoldás árny oldala, hogy EE verzió esetén megcsinálja az adatbázis módosítását a rendszer, azonban az általam próbált CE verziók nem módosították, az adatbázist, és a módosítást végrehajtó SQL-t is magamnak kellett megírnom. Ezen a ponton bukott meg az adatbázis-függetlenség. Nem ennyire elkeserítő a helyzet, mert írhatunk olyan általános SQL-t, amit a Liferay is használ a Service Builder tábláinak legenerálásához, és létezik egy osztály a Liferayben, ami az általános SQL-ből adatbázisnak megfelelő SQL-t generál, amit egy startup hookba ágyazva futtathatunk, de azt hiszem ez túl nagy ár a függetlenségért.
  • A tranzakciók kezelése sem túl kifinomult szerintem a Liferayben. A Liferay filozófiája, hogy a tranzakciók kezelését teljesen a Spring AOP-re bízza, amely a Liferay szívében van konfigurálva. Az alapértelmezett működés, hogy az add*, check*, clear*, delete*, set*, és update* karakter-lánccal kezdődő metódusok egy tranzakciót indítanak. Ettől eltérően csak annyit tehetünk, hogy a service.xml-ben az entitásnál a tx-required mezővel megadhatjuk, hogy ezen felül milyen metódusok indítsanak tranzakciót. Nekem nagyon kényelmes és kézenfekvő, hogy szabadon válogathatok a 6 féle tranzakció-kezelés közül, és bármikor eldönthetem, hogy becsatlakozok-e a meglévő tranzakcióba, hogy indítok-e újat, stb. Megint csak egy légből kapott példa, banki tranzakciót indítok a rendszerben, és a folyamat közben szeretném loggolni, hogy pontosan mi történt a tranzakcióval, de sajnos a log szerver timeoutol/betelik a lemez/stb. Ebben az esetben a loggolást végző kódrészlet hibája miatt gördül vissza az egész tranzakció, holott csak egy harmadlagos funkció nem teljesült. Létezik a Liferayben EXT plugin, amivel felül lehet írni a Spring konfigot, de a 7-es verziótól ezek egyáltalán nem lesznek támogatottak, így én sem javaslom senkinek, hogy ezt az utat válassza.
  • A service réteg deleteAll metódusai a perzisztes rétegen keresztül egyesével törlik az entitásokat, ami egy 10M+ rekordszámú táblánál órákba is telhet. Ezt különösen azért nem értem, mert a Liferay nem kezel entitás-kapcsolatokat, tehát feleslegesnek érzem az adatbázisból egyesével kikérni a rekordokat, és törlést végrehajtani rajtuk. Szerencsére erre is van egy kiskapu, közvetlenül el tudunk kérni egy JDBC kapcsolatot, ahol szabadon garázdálkodhatunk:
    DataAccess.getConnection.createStatement().execute("delete from MY_Entity");
    Természetesen a kapcsolat élet ciklusáról magunknak kell gondoskodnunk.
  • Mint fentebb is írtam a Liferay nem igazán kezeli az entitás relációkat. Meg lehet ugyan adni kapcsolatokat, de a OneToOne kapcsolaton kívül, amit kezel a rendszer, minden kapcsolat OneToMany esetén  csak egy long típusú mező, ManyToOne esetén pedig egy üres Collection lesz. a OTO kapcsolatot az service.xml-ben a reference tag segítségével adhatjuk meg. Ennek célja egyébként a minél szélesebb adatbázis paletta támogatása, de szerény véleményem szerint ez akkor plusz terhet ró a fejlesztőkre, és annyi hiba lehetőséget, és adatbázis-szemetet eredményez, ami nem biztos, hogy megéri ezt az árat. További hátránya a szemléletnek, hogy így az entitások közötti kapcsolatot View oldalon kell lekezelni, onnan kell az adatbázishoz fordulni minden kapcsolat esetén. A Liferay egyébként MVC patternt követi, de vallja azt a nézetet, hogy lekérdezés esetén lehet közvetlenül a Model-hez fordulni, egyszerűsítve a logikát, így viszont egy standard Liferay portlet plugin jsp-je tele van scriptletekkel, olvashatatlanná téve a kódot. Ízlések és pofonok, én személy szerint szeretem, ha jól el vannak szeparálva a dolgok egymástól, a jsp szerkesztésekor az IDE tud hasznos tanácsokat adni, és nincs tele a Problems fül felesleges figyelmeztetésekkel, ha a Model önállóan végzi a dolgát, és nem a View fejlesztőnek kell gondolkodnia a különböző service hívásokról.
Röviden ennyi jutott most eszembe a Liferay Service Builderének árny oldalairól, remélem ezzel nem vettem el senki kedvét a használattól, nem ez volt a célom. Véleményem szerint ha viszonylag nem túl nagy, hordozható, és/vagy SOAP-os megoldásra van szükség, jó választás lehet, de mielőtt nagy projekteket építenénk erre a komponensre mindenféleképpen végezzünk kutatásokat, kísérletezzünk, hogy kielégíti-e üzleti igényeinket maximálisan.

2011. október 30., vasárnap

OWASP AntiSamy Javaban

A webes biztonság kérdése egyidős magával az internettel, hiszen a hálózaton elérhető adatokat felhasználók milliói tekintik meg napról-napra. Mivel a weboldalak végeredményben a kliensek gépein futnak, hamar beláthatjuk, hogy a biztonsági kérdések nem elhanyagolhatóak, és mind a klienseknek, mind a weboldalak üzemeltetőinek fontos, hogy a kiszolgált tartalom hiteles és biztonságos legyen. Tipikus támadási forma az un. XSS, amikor pl. egy az oldalba ágyazott külső Javascript próbál szenzitív információkhoz hozzájutni a böngésző valamely biztonsági hibáját kihasználva. Az ilyen és ehhez hasonló problémák megoldására jött létre 2001-ben OWASP Antisamy Project, melynek célja nyílt forrású alternatívát, és szabványt kínálni az alkalmazások megvédésére.

Mint minden 3rd party fejlesztés, ez is úgy kezdődik, hogy letöltjük a programkönyvtár lehetőleg legfrissebb verzióját. AntiSamy jelenleg az 1.4.4-es verziónál tart. A dokumentációval ellentétbe a csomag függőségeit nem lehet letölteni, rákeresve a antisamy-required-libs.zip kulcsszavakra találtam egy oldalt. A készítők szerint deprecated ami a zip-be van, de a próba erejéig megteszi, éles használatra össze kell vadászni a függőségeket. A szoftver konfigurációja eléggé bonyolult, hosszas dokumentáció olvasással és tanulással egész biztos össze lehet dobni egy normális konfig XML-t, de szerencsére több nagyobb felhasználó is rendelkezésre bocsátotta saját összeállítását, így nyugodtan mazsolázgathatunk az eBay, MySpace, vagy akár Slashdot beállításaiból.

Az általam készített tesztprogram az alábbi (belecsempészve kis Java 7-et):

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.owasp.validator.html.*;


public class AntiSamyTest {

    public static void main(String[] args) {
        try {
            URL dirtyHtmlUrl = AntiSamyTest.class.getResource("dirtyHtml.html");
            Path dirtyHtmlPath = FileSystems.getDefault().getPath(dirtyHtmlUrl.getFile());
            List<String> lines = Files.readAllLines(dirtyHtmlPath, Charset.defaultCharset());
   
            URL configXmlUrl = AntiSamyTest.class.getResource("antisamy-ebay-1.4.4.xml");
            Policy policy = Policy.getInstance(configXmlUrl.getFile());
            
            AntiSamy as = new AntiSamy();
            CleanResults cleanResult = as.scan(concatString(lines), policy);

            System.out.println(cleanResult.getCleanHTML());
        } catch (IOException | PolicyException | ScanException ex) {
            throw new RuntimeException(ex);
        }
    }
 
    private static String concatString(List<String> input) {
        StringBuilder output = new StringBuilder();
        for(String line : input) output.append(line);
        return output.toString();
    }
}
A dirtyHtml.html fájl tartalma szabvány HTML (HTML, HEAD, BODY). A programot futtatva láthatjuk, hogy a body tartalmán kívül minden HTML taget kidobott az AntiSamy. Azért mondom, hogy minden HTML taget, mert ha van pl. title a headbe, a tartalma bizony ottmarad, tehát csak a sallang kerül ki. Szerintem ez utóbbi működés konfigurálható (fixme).

Az elméleti ismerkedés után ideje valami komolyabb megbizatást adni Samykének. Személy szerint Liferay fejlesztő vagyok, így szinte evidens, hogy erre esett a választásom. A Liferay 6-os verziója óta létezik egy Sanitizers-nek nevezett funkcionalitás, amely, bár még nem teljeskörű, mégis segít az igényes programozónak, a kritikus user inputokat szűrni. A funkcionalitás mint írtam nem teljeskörű, ugyanis egyelőre csak a Blog bejegyzéseket tudjuk kontrollálni out-of-the-box. A dolgunk egyszerű, a portal.properties-ben van egy sanitizer.impl paraméter, amit a portal-ext.properties-ben felül tudunk definiálni. Az alapbeállítás a com.liferay.portal.sanitizer.DummySanitizerImpl osztályra mutat, ami jóformán semmit nem csinál, viszont jó kiindulási pont lehet saját Saniterünk elkészítéséhez. Létezik a Liferaynek beépített osztálya com.liferay.portal.kernel.sanitizer.SanitizerUtil képében, választhatjuk ezt is, de saját megoldást is minden további nélkül.

Miután Blog bejegyzéseinket megvédtük a sokszor óvatlan bloggerektől, sajnos nem dőlhetünk hátra nyugodtan, mivel a Liferayben is, mint minden CMS-ben, nem csak Blogot szerkesztenek a felhasználók, hanem számtalan egyéb módon is lehetőségük van HTML szöveget a rendszerbe juttatni. Mivel "gyári" támogatás még nincs ezen bejegyzésekre, nincs más lehetőségünk, mint hook-ot írni. A Plugin tárolóban van egy "antisamy hook", amely alapján könnyedén megírhatjuk saját kiterjesztésünket. A legjobb módszer un. Model Wrapper Hook készítése. Hozzunk létre egy hook-ot, majd a liferay-hook.xml fájlba írjuk be a felülírandó szervíz definícióját

<hook>
    <service>
        <service-type>com.liferay.portlet.wiki.service.WikiPageLocalService</service-type>
        <service-impl>com.test.hooks.SaniterWikiPageLocalService</service-impl>
    </service>
</hook>
Majd írjuk meg saját osztályjunkat
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.wiki.model.WikiPage;
import com.liferay.portlet.wiki.service.WikiPageLocalService;
import com.liferay.portlet.wiki.service.WikiPageLocalServiceWrapper;

public class SaniterWikiPageLocalService extends WikiPageLocalServiceWrapper {

    public SaniterWikiPageLocalService(WikiPageLocalService wikiPageLocalService) {
        super(wikiPageLocalService);
    }
 
    public WikiPage addPage(
            long userId, long nodeId, String title, double version,
            String content, String summary, boolean minorEdit, String format,
            boolean head, String parentTitle, String redirectTitle,
            ServiceContext serviceContext)
            throws PortalException, SystemException {

        String sanitizedContent = SanitizerUtil.sanitize(
            serviceContext.getCompanyId(), serviceContext.getScopeGroupId(),
            userId, WikiPage.class.getName(), 0, ContentTypes.TEXT_HTML, content);

        return super.addPage(userId, nodeId, title, version,
            sanitizedContent, summary, minorEdit, format,
            head, parentTitle, redirectTitle,
            serviceContext);
    }
}
Bár én a Wiki-t választottam példának, ez alapján bármely más szervízre megírható a szűrés.

2011. szeptember 24., szombat

Liferay Portál OSGi bundle támogatással

Liferay és OSGi témák már többször is előkerültek, de most egy olyan egyedülálló kombináció bemutatását tűztem ki célul, mely egyesíti a két platform minden előnyét. A bemutatott módszer aktív fejlesztés alatt áll, éles használatát nem ajánlom senkinek. A fejlesztés Raymond Augé keze alatt történik, jelenleg a teljes kódbázis branch repóból érhető el. A cikk megírását az érdeklődés ihlette, és a remény, hogy másoknak is megtetszik a kezdeményezés, és programozók tucatjai állnak be Ray mögé, hogy minél előbb core feature legyen a dolog.

Első lépésként szükségünk lesz a módosított Liferay forrására, amit itt tudunk beszerezni. A letöltött forrást egyszerűen tömörítsük ki valahová. A fordításhoz egy Apache Antot is be kell üzemelnünk, ennek lépéseit nem részletezném. A kibontott Liferay gyökérében találunk egy app.server.properties állományt, melyben tudjuk konfigurálni, hogy milyen konténert szeretnénk használni. Amennyiben valami "különleges" igény nem szól közbe, én az Apache Tomcat verziót preferálom, ez egyébként az alapértelmezett. A properties fájlban 7.0.21-es verzió van beállítva, letöltés után a Liferay forrásunkkal párhuzamosan lévő bundles könyvtárba csomagoljuk is ki. Vagy az app.server.properties-ben írjuk át az útvonalat, vagy az apache-tomcat-7.0.21 könyvtárat nevezzük át tomcat-7.0.21-re.

A fordításhoz Antunkat is meg kell kicsit piszkálni. Először is az ecj.jar-t be kell másolni az Ant libjei közé (vagy szimlinkelni, vagy egyéb úton a classpath-ra tenni). Az ecj.jar beszerezhető a Plugin SDK lib könyvtárából. Be kell állítanunk az Ant memóriahasználatát is az alábbi módon.

ANT_OPTS="-Xmx1024m -XX:MaxPermSize=256m"
export ANT_OPTS
Nincs más hátra, mint elindítani a fordítást a Liferay gyökérkönyvtárában.
ant all

A fordítás befejeztével a ../bundles/tomcat-7.0.21/bin/startup.sh parancs futtatásával kelthetjük életre friss Liferayünket. Miután elindult, a Control Panel Server szekcióban találunk egy OSGi Admin menüpontot. Belépve láthatjuk, ha minden jól ment, hogy van egy OSGi System Bundle nevű batyu, ami aktív, tehát az OSGi konténer fut rendben.Az általam próbált verzióba Eclipse Equinox 3.7 volt integrálva, Ray elmondása szerint minden >=4.1-es implementációval változtatás nélkül működik.

Amit a rendszer tud jelenlegi állás szerint:

  • Az adminisztrátor portleten keresztül lehetséges OSGi batyuk hozzáadása, eltávolítása, frissítése, elindítása, újraindítása, és leállítása.
  • Egy saját deploy listenerrel a deploy könyvtárba másolt batyukat a Liferay megpróbálja telepíteni és elindítani.

Most, hogy működik a portálunk, nincs más dolgunk, mint nekiállni első OSGi Servletünk megalkotásához. A batyu készítésről itt írtam részletesen, így az alapokba nem is mennék bele. Hozzunk létre egy OSGi Plugin Projectet. A MANIFEST.MF állomány tartalma legyen a következő:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGiOnLiferay
Bundle-SymbolicName: OSGiOnLiferay;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: javax.servlet;version="2.5.0",
 javax.servlet.http;version="2.5.0",
 org.eclipse.equinox.http;resolution:=optional,
 org.osgi.framework;version="1.6.0"

Hozzunk létre a gyökér-könyvtárban egy plugin.xml-t, amelybe regisztráljuk a Servletünket.

   
   

Végezetül írjunk egy egyszerű Servletet.
public class TestServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  PrintWriter out = resp.getWriter();
  out.print("Java'nother blog");
  out.close();
 }
}
A plugin buildelése után egyszerűen másoljuk be a kapott JAR-t a deploy könyvtárba. A Liferay logot nézve az alábbi sor jelöli a sikeres telepítést.
12:14:29,798 INFO  [AutoDeployDir:167] Processing sample.jar
Az admin felületre látogatva remélhetőleg meg is találjuk batyunkat az OSGi Admin felületen, nincs más dolgunk, mint aktiválni.

És itt jön a szomorú, ám reményteli vég. Mint említettem eléggé fejlesztési fázisban van a projekt, ami sajnálatosan azt is jelenti, hogy ezen a ponton nem tudunk túljutni. Ray a közeljövőben publikál egy birdge Servletet, aminek a segítségével a Liferay a HTTP kéréseket az OSGi HttpService rétegén keresztül bármely, a keretrendszerbe regisztrált Servletnek vagy JSPnek továbbítani tudja. Tervezi a Plugin SDK bővítését is, hogy OSGi batyukat lehessen vele kényelmesen fejleszteni. Véleményem szerint ígéretes a kezdeményezés, hiszen a Liferay egy jól gyúrható platform, az OSGi, pedig égy végtelenül rugalmas keretrendszer.

2011. augusztus 27., szombat

Validálás, ahogy én csinálom

Egy programozó életében rendkívül gyakran előfordul, hogy entitásokat kell validálnia. Lényegében az üzleti logika mindig úgy kezdődik, hogy a bemeneti paramétereket, melyek legtöbbször felhasználói interakció által kerülnek be, validálni kell, hogy a hibás entitások a későbbiekben ne okozzanak hibát a program működésébe. A validálás az a dolog, amit egy kicsit mindenki másként csinál, mindenkinek megvan a saját módszere. Ebben az írásban bemutatnám, én hogyan is szoktam ezt a problémát orvosolni. A bemutatott megoldás nem tökéletes, de remélem gondolat-ébresztőnek megteszi.

A validálás alapproblémája szerintem a következő: az entitások validitását az üzleti réteg végzi el, amelyhez különböző kliensek kapcsolódnak. A különböző kliensek különböző nyelveken, és módon kívánják megjeleníteni a hibaüzeneteket, tehát az üzleti rétegbe írt return "Hibás a név!" és hasonlók megengedhetetlenek (később úgyis kiderül, hogy többnyelvű lesz a megjelenítés). A következő probléma, amivel szembesülünk, hogy egyszerre több hibát is tudni kell kezelni, hiszen egy bonyolult form kitöltésekor nem szerencsés egyesével untatni a felhasználót a hibák közlésével, meg egyébként sem tudjuk klienseink miként működnek. A nyelvi problémát orvosolni viszonylag egyszerű, vezessünk be jól dokumentált hibakódokat, klienseink, meg feldolgozzák saját belátásuk szerint. Az egyidejű több hibajelzés kérdésköre már egy kicsit rafináltabb dolog. A legegyszerűbb megoldás, egy hibakódokkal feltöltött tömb, vagy Collection visszaadása. Ezzel a megoldással a legnagyobb problémám, hogy rengeteg favágó programozással jár. Ha mindenhová, mind kliensben mind az üzleti rétegben kézzel írogatjuk a hibakódokat, akkor a hibaszázalékunk igencsak megnő, mert óhatatlanul elgépeljük valahol a kódot. Ha bevezetünk konstansokat, vagy Enumokat, akkor legalább a hibákat kiküszöböljük, de a ThisIsMyEntity.ERROR_NAME_REQ gépelésébe fogunk megőszülni.

Nekem a következő megoldás vált be: mivel 2db kettő hatványa sosem lesz egy harmadik kettő hatványával egyenlő, a hibakódot adjuk vissza kettő hatványaival. Tehát, ha hiányzik a név adjunk vissza 2-t, amennyiben a lakcím hiányzik adjunk vissza 4-et, ha pedig mindkettő hiányzik adjunk vissza 6-ot. A metódus implementációját személy szerint magába az Entitást megvalósító osztályba szoktam tenni, de ez ízlés dolga. Van aki szerint az Entitásnak csak egy DTO-nak kell lennie, és a validitás eldöntése az üzleti logika feladata. Véleményem szerint egy entitás tudja leginkább magáról eldönteni, hogy megfelelőek-e a benne foglalt adatok.

public int validate() {
            int valid = 0;
            
            if (foo == null || foo.isEmpty()) valid |= 1 << 1;
            if (bar == null) valid |= 1 << 2;
            if (param == null || param.isEmpty()) valid |= 1 << 3;
            
            return valid;
        }
Mivel későbbiekben is bitműveletekkel fogunk operálni, elegánsabbnak tartottam bitműveletekkel megoldani az összeadást, természetesen dolgozhatunk "hagyományos" számokkal is.

Kliens oldalon a hibakód lekezelése pofon egyszerű. Kiválasztunk egy tetszőleges 2 hatványt (érdemes a dokumentációban is említettet választani :)), mondjuk a 4-et, és ellenőrizzük, hogy adott bit, 2^2 az 100, tehát a harmadik, egyezik-e a visszakapott érték harmadik bitjével.

boolean somethingIsValid = (4 & entity.validate()) == 4;
Ez a módszer sem tökéletes, hiszen a hibákat csak bővíteni lehet, egyéb változtatás esetén már meglévő klienseink fognak hibásan működni. A teljes példaprogram az alábbi:
public class ValidatorTest {

    private static final Map<Integer, String> ERRORS = new HashMap<Integer, String>();

    static {
        ERRORS.put(2, "Foo is required!");
        ERRORS.put(4, "Bar is required!");
        ERRORS.put(8, "Param is required!");
    }

    public static void main(String[] args) {
        List<Entity> entities = Arrays.asList(new Entity[] {
            new Entity(null, null, null),
            new Entity("foo", null, null),
            new Entity("", null, "param")
        });

        for(Entity entity : entities) {
            System.out.println(entity);
            int valid = entity.validate();
            
            for(Integer code : ERRORS.keySet()) {
                if ((code & valid) == code) {
                    System.out.println(ERRORS.get(code));
                }
            }
        }
    }

    static class Entity {

        private String foo;
        private Integer bar;
        private String param;
        
        public Entity(String foo, Integer bar, String param) {
            this.foo = foo;
            this.bar = bar;
            this.param = param;
        }

        public int validate() {
            int valid = 0;
            
            if (foo == null || foo.isEmpty()) valid |= 1 << 1;
            if (bar == null) valid |= 1 << 2;
            if (param == null || param.isEmpty()) valid |= 1 << 3;
            
            return valid;
        }
    }
}

Nagyon kíváncsi vagyok a véleményetekre, illetve, hogy nektek milyen módszer vált be ebben a témakörbe.

2011. augusztus 15., hétfő

Collections.emptyList() és a generikus típuskényszerítés

Sajnos mostanában nem jut annyi időm blogolásra, mint szeretném, és ez a bejegyzés is mindössze egy szösszenet lesz.
A Collections osztály, mint nevéből is következik, a Collection példányok manipulálására, és előre definiált Collection-ök hozzáféréséhez nyújt kényelmes eszközt. Az osztály részleteibe nem szeretnék belemenni, ezt az olvasóra bízom. Kiszemelt áldozatom az emptyList metódus. A metódus forrását megnézve láthatjuk, hogy nem csinál egyebet, mint az osztályban statikus paraméterként deklarált nyers típusú EMPTY_LIST-et adja vissza a kívánt típusra alakítva. Természetesen ez a lista egy változtathatatlan lista, tehát elemeket ebbe nem tudunk tenni. Használata végtelenül egyszerű.
List<String> stringList = Collections.emptyList();
A fordító megpróbálja kitalálni, hogy milyen típusra kell alakítani az EMPTY_LIST példányt, teszi ezt úgy, hogy megnézi milyen típusba szeretnénk belepasszírozni a listát. Az esetek többségében sikerül is ez a művelet, ám mit tehetünk a következő esetben:
public static void bar() {
        foo(Collections.emptyList());
    }
    
    public static void foo(List<String> stringList) {}
Már az IDE is jelzi, hogy ez így nem lesz jó, nem tudja megállapítani milyen típusra kell alakítani a listát. Netbeans fel is ajánlja, hogy létrehoz egy foo(List<Object> stringList) metódust, de mi ezt nem szeretnénk. Egyik lehetőségünk, hogy a paramétert egy lokális változóba kimozgatjuk, és a helyes típussal adjuk át a metódusnak. Ez a megoldás célra vezető, viszont feleslegesen növeli kódunk méretét. A következő eshetőség, hogy a nyers EMPTY_LIST-et adjuk át paraméterként, viszont így hazavágjuk a típusbiztonságot. Nyers típusoknál a JVM egyszerűen levágja a típusellenőrzést. Ennek oka a visszafelé kompatibilitás, a generikusok megjelenése óta nem javallott a nyers típusok használata. Az elegáns megoldás egyszerű, segítsük a fordítót, és mondjuk meg neki milyen típusra alakítsa a listát.
foo(Collections.<String>emptyList());
Az itt említett módszer nem a Collections osztály sajátossága, alkalmazható minden generikus metódusra. Választásom azért esett erre az osztályra, mert ezt eléggé gyakran használja mindenki , aki Java-ban programozik, és része a Java SE-nek.

2011. április 28., csütörtök

JDK7 újdonság: java.util.Objects

A java.util.Arrays és java.util.Collections mintájára a Java következő verziója elhozza számunkra a java.util.Objects "eszközt". Lényegében semmi újdonság nincs az osztályban, egyszerűen olyan kényelmi funkciókat tartalmaz, melyek megkönnyítik a fejlesztők életét. Az objektumnak mindössze 9 statikus metódusa van, melyek az alábbiak:
  • compare(T a, T b, Comparator c):int - Mint a neve is mutatja objektumokat tudunk vele összehasonlítani, az egyetlen érdekessége, hogy Objects.compare(null, null) visszatérési értéke 0
  • equals(Object a, Object b):boolean - Szintén zenész Objects.equals(null, null) igaz értékkel tér vissza
  • deepEquals(Object a, Object b):boolean - Igazzal tér vissza, ha a két objektum "mélységében" egyenlő, minden más esetben hamis. Szintén elmondható, hogy Objects.deepEquals(null, null) igaz. Amennyiben mindkét átadott paraméter tömb, a metódus meghívja az Arrays.deepEquals-t ellenkező esetben az első objektum equals metódusának adja paraméterül a második paramétert
  • hashCode(Object o):int - Visszatér az átadott objektum hashCode-jával, null paraméter esetén 0-val
  • hash(Object... values):int - Az Arrays.hashCode(Object[]) mintájára az átadott paramétereknek kiszámolja a hashCode-ját. Gyakorlati haszna leginkább a DTO osztályoknál mutatkozik meg, ahol több objektum összessége alkot egy egységet.
    public class Student {
        private Integer id;
        private Mother mother;
        private Father father;
        ...
        @Override public int hashCode() {
            return Objects.hash(id, mother, father);
        }
    }
    
  • requireNonNull(T obj):T - Ez a metódus egy egyszerű validáció, amely NullPointerException-t dob, amennyiben az átadott referencia értéke null. A metódusnak átadhatunk még egy String paramétert, ekkor ez lesz a kivétel szövege
  • toString(Object o): String - Az átadott objektum toString metódusával tér vissza, null érték esetén "null" Stringgel. Második paraméternek egy String is átadható, ez a paraméter lesz a null érték toString-je
    logger.info(Objects.toString(bar, “Bar is null”));
    
Kettős érzelmekkel viseltetek az "újjítás" iránt. Egyrészt némi iróniával kijelenthető, hogy most aztán kitettek magukért a srácok, nem hiába csúszik a kiadás. Másfelöl viszont elég sok felesleges kódolástól szabadít meg az eszköz, és mivel része lesz a JDK-nak egy "szabványos" megoldást kínál hétköznapi problémákra.

2011. április 1., péntek

Liferay "gyári" Servicek és a DynamicQuery használata

Egy előző bejegyzésben foglalkoztam már a Service Builderrel, amivel saját szolgáltatásokat illeszthetünk a Liferay (továbbiakban LR) portleteinkbe. A LR természetesen a ServiceBuildert nem csak külső integrációhoz használja, hanem saját adatbázis-rétegének eléréséhez is. A szolgáltatások közül leginkább a ...LocalServiceUtil osztályokra lesz szükségünk. Ezen osztályok használatára nem szeretnék kitérni, statikus metódusaik lekérdezésével "egyértelmű", hogy melyik mire való. Amennyiben a "gyári" metódusokat használjuk legtöbb esetben sajnos számolnunk kell a LR sajátosságaival, szerencsére azonban van lehetőségünk saját SQL futtatására is, természetesen ebben az esetben nem ússzuk meg az adatbázis szerkezetének megismerését. A saját SQL-ek rendszerbe juttatásáról a DynamicQuery osztály gondoskodik. Ennek használata nem túl bonyolult, de mitől is lenne az.
DynamicQuery query = DynamicQueryFactoryUtil
    .forClass(User.class, PortalClassLoaderUtil.getClassLoader());
query.add(PropertyFactoryUtil.forName("email").eq("foo@bar.hu"));
query.addOrder(OrderFactoryUtil.asc("name"));
List fullResult = UserLocalServiceUtil.dynamicQuery(query);
List someResult = UserLocalServiceUtil.dynamicQuery(query, start, end);
long resultsNo = UserLocalServiceUtil.dynamicQueryCount(query);
Fontos tudnunk, hogy LR osztályok 2 fő csoportba sorolhatók. Az egyik a portal-impl, amely kívülről nem érhető el, ezek a LR saját belső osztályai, és a portal-service, amik pedig a kifelé ajánlott szolgáltatások. Sajnos nem sikerült a szétválasztást tökéletesen megvalósítani, legalábbis szerintem, így ha a PortalClassLoaderUtil.getClassLoader() paramétert nem adjuk meg a DynamicQueryFactoryUtilnak, akkora ClassNotFoundException-t kapunk mint a ház :). Ezt az egy "apró" részletet leszámítva viszonylag jól dokumentált a DynamicQuery használata, és az alapvető műveletekre elégséges.

2011. január 29., szombat

Liferay FriendyUrlMapping vs. IPC

Amikor először futottam bele a címben nevezett probléma-együttesbe, még azt gondoltam, hogy csak egyedi az eset, így a megolldást nem jegyeztem meg, és sajnos le sem. Viszont mikor másodjára is szembetalálkoztam vele, és újra végigjártam a szamárlétrát, gondoltam megkímélem az emberiséget, vagy legalább az olvasóimat ezen gyötrelmektől.
A portletek világában sajnos állandó problémát jelent a felhasználóbarát URL-ek kezelése. Ennek oka, hogy egy portletnek két fázisa létezik, az egyik a render-fázis, a másik pedig az action-fázis. Álltalános eset, hogy amíg egy portlet action-fázisban van, addig a többi render-fázisba. A böngészőből átvitt adatoknak a szerver felé tartalmazniuk kell valamiféle hivatkozást az adott portlet-példányról, hogy a portlet-konténer el tudja dönteni kinek is címezték a kérést. Paraméterben szokott szerepelni a portlet nézet beállítása (minimalizált, normál, maximalizált, exclusive, ez utóbbi liferay specifikus), valamint az életciklusra, és portlet módra (view|edit|etc) vonatkozó adat. Ennek eredménye valami ehhez hasonló URN: ?p_p_id=searchaction_WAR_fakeportletshoppingportlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_count=1&_searchaction_WAR_fakeportletshoppingportlet_javax.portlet.action=search. A probléma megoldására a Liferay többféle megoldást is kínál, de erről később. A probléma másik része, hogy a portlet-szabvány második verziója óta lehetőség van portletek közötti kommunikációra, röviden IPC-re. Az IPC lényege tömören, hogy az egyik portletünk kivált, publikál egy eseményt, míg egy vagy több másik portlet, amelyek előzőleg feliratkoztak az eseményre, elkapják azt. Az eseményben lehetőség van adatok átpasszíroázára is, így válik teljessé a kommunikáció. A dolog hátulütője, hogy kiváltani eseményt csak action fázisban lehet, és a kiváltott eseményt a másik portlet render fázisának egy bizonyos szakaszában képes elkapni, tehát érezhetően nem teljes a fejlesztői szabadság. Összegezve a problémát egy friendly url-en (továbbiakban FU) "figyelő" portletnek kell jeleznie állapotáról egy másik portletnek.

IPC kialakítása

A megvalósításhoz nincs más dolgunk, mint bejegyezni a portlet.xml-ben egy globális eseményleírást, ezzel rögzítve magát az eseményt.
<event-definition>
    <qname xmlns:x="http://jpattern.blogspot.hu/events">x:ipc.myEvent</qname>
    <value-type>java.lang.String</value-type>
</event-definition>
Ezzel művelettel a http://jpattern.blogspot.hu/events névtéren bejegyeztünk egy ipc.myEvent eseményt.
Ezután mindkét portletünket szerepkörüknek megfelelően felvértezzük a kommunikációval.
<supported-publishing-event>
    <qname xmlns:x="http://jpattern.blogspot.hu/events">x:ipc.myEvent</qname>
</supported-publishing-event>

<supported-processing-event>
    <qname xmlns:x="http://jpattern.blogspot.hu/events">x:ipc.myEvent</qname>
</supported-processing-event>
Az esemény publikálása az alábbi módon történik:
QName qName = new QName("http://jpattern.blogspot.hu/events", "ipc.myEvent");
response.setEvent(qName, "param"); //ActionResponse
Az esemény elkapásához portlet osztályunkban kell delegálnunk egy metódust:
@ProcessEvent(qname = "{http://jpattern.blogspot.hu/events}ipc.myEvent")
public void myEvent(EventRequest request, EventResponse response) {
    Event event = request.getEvent();
    String param = (String) event.getValue();
    response.setRenderParameter("param", param);
}
Ezek után a JSP-ben nincs más dolgunk mint kiszedni a requestből a paramétert:
renderRequest.getParameter("param");
Ennyi a varázslat, kipróbálásához házi feladat a portlet taglib actionUrl használata.

A FriendlyUrlMapping

Az említett több lehetőség közül nekem szimpatikusabb volt az XML szerkesztgetésnél, hogy létrehozok egy osztályt, amely beállítja a portletet az URL alapján. Az osztállyal szemben támasztott követelmény, hogy a com.liferay.portal.kernel.portlet.BaseFriendlyURLMapper osztály leszármazottja legyen.
public class MyFriendlyUrlMapper extends BaseFriendlyURLMapper {

private static final String MAPPER = "mappingstring"; //erre az URL darabra fog aktiválódni a mapper
 
@Override
public void populateParams(String friendlyURLPath,
        Map<String, String[]> params, Map<String, Object> requestContext) {
    addParameter(params, "p_p_id", getPortletId());
    addParameter(params, "p_p_lifecycle", "1"); //ez a paraméter kényszeríti action-fázisba a portletet
    addParameter(params, "p_p_mode", PortletMode.VIEW);
    addParameter(params, "p_p_state", WindowState.NORMAL);
    addParameter(getNamespace(), params, "javax.portlet.action", "actionMethod"); //Ez az action-metódus fog meghívódni
 }
 
@Override
public boolean isCheckMappingWithPrefix() {
    return false;
}
 
@Override
public String getMapping() {
    return MAPPER;
}
 
@Override
public String buildPath(LiferayPortletURL portletURL) {
    return null;
}

}
Miután elkészítettük az osztályt, be kell jegyeznünk a liferay-portlet.xml fájlban a megfelelő portletnél azt:
<friendly-url-mapper-class>com.blogger.jpattern.MyFriendlyUrlMapper</friendly-url-mapper-class>
Érdemes megjegyezni, hogy a FU kezelés a LR-ben úgy működik, hogy felveszünk egy oldalt, pl.: /oldal, amire kihelyezzük a portletet, és a LR /oldal/mappingstring URL esetén aktiválja a Mapper osztályt.
Amennyiben a portletünket render fázisban szeretnénk használni p_p_lifecycle=0, nincs más dolgunk, de mint említettem IPC esemény kiváltására csak action-fázisban van lehetőség. A LR-ben ki kell kapcsolnunk az "Auth token check"-et, ami annyit tesz, hogy minden action típusú kéréshez a LR generál egy egyedi azonosítót, és ennek hiányában nem enged hozzáférést az erőforráshoz. Nyilván FU használata esetén nem rendelkezünk ezzel az azonosítóval, ezért a portal-ext.properties fájlban vegyük fel a "auth.token.check.enabled=false" paramétert.