A következő címkéjű bejegyzések mutatása: J2EE. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: J2EE. Összes bejegyzés megjelenítése

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.

2010. június 24., csütörtök

Barátkozás a Liferay Portlettel, telepítés

Az utóbbi időben sajnos, vagy inkább szerencsére több időt szántam a tanulásra, mint az írásra. Egy munka-hely váltás kapcsán  találkoztam a Liferay Portletek téma-körével, és mivel hosszas küzdelem árán sikerült beüzemelni a dolgot, gondoltam talán másoknak is hasznos lehet, ha lejegyzem.
A Liferay egy Java alapú, nyílt forrás-kódú, "Enterprise" CMS rendszer, ami annyiba tér el más hasonló alkalmzásoktól, hogy a tartalmat Portletek segítségével állítja elő. A Portletek lényegét röviden úgy foglalhatnánk össze, hogy az egyes hagyományos értelembe vett modulok külön életet élnek, és egy HTML oldal legenerálása a szerveren nem a megszokott módon történik, hanem a Portlet konténer, a megjelenítendő HTML-t "össze-ollózza" a Portletektől, azaz a Portleteket megkéri, hogy az állapotuknak megfelelő HTML kimenetet biztosítsák számára. Portlet konténerből számtalan implementáció létezik, ki jobban, ki kevésbé tér el a specifikációtól, a Liferay fejlesztői úgy döntöttek, hogy saját megoldást dolgoznak ki, így született meg a Liferay Portlet. Számomra a megismerést nagyban nehezítette a név-választás, ami egyébként logikus, ugyanis a netet böngészve, linkre kattintva nehéz hirtelen eldönteni, hogy vajon a portálról, vagy a portletről van éppen szó.
  • A tanulás/fejlesztés első lépése, hogy letöltjük a conténert. Bár széles palettán válogathatunk a társított szerverek terén - Tomcat, JBoss, Glassfish...-, mégiscsak a Liferay fejlesztők száj-íze, és verziói szerint kell dolgoznunk. Személy szerint nekem nem szimpatikus, hogy csak egyben tudjuk beszerezni a web-konténert, a Portlet-konténert, és ráadásként a Liferay Portalt, találkoztam olyan Portlet-konténerrel, amely meglévő alkalmazás-szerverünket frissítette, meghagyva a választás szabadságát, illetve futó alkalmazásainkat.
  • Kicsomagolás közben bőven lesz időnk tanulmányozni a dokumentációt.
  • Miután elindítottuk a szervert azonmód elérhető a Lifery Portal rendszer (jó esetben a http://localhost:8080 címen), amire Portlet fejlesztőként egyelőre nem lesz szükségünk.
  • A dokumentáció szerint függőségként telepítenünk kell az Apache Antot (Mavennel is működésre lehet bírni, de ezt nem próbáltam), valamint a Jikes fordítót.
  • Az első Portletünk létrehozását érdemes a Liferay Plugins SDKn keresztül elvégezni, amely számtalan mintával könnyíti meg a fejlsztést.
  • Az SDK gyökér-könyvtárában található build.properties állományban állítsuk be a kívánt web-konténert, a megfelelő elérésekkel. Amennyiben több felhasználó is használja ugyanazt az SDK-t, a buil.properties mellé hozzunk létre felhasználónként egy-egy build.${username}.properties fájlt, amelyben perszonalizálni tudjuk a beállításokat.
  • Következő lépésként az SDK portlets könyvtárában hozzunk létre egy hello-world projektet.
    ./create.sh hello-world "Hello World"
    Buildfile: build.xml
    
    create:
        [unzip] Expanding: /media/data/code/liferay/sdk/portlets/portlet.zip into /media/data/code/liferay/sdk/portlets/hello-world-portlet
        [mkdir] Created dir: /media/data/code/liferay/sdk/portlets/hello-world-portlet/docroot/WEB-INF/tld
         [copy] Copying 6 files to /media/data/code/liferay/sdk/portlets/hello-world-portlet/docroot/WEB-INF/tld
    
    BUILD SUCCESSFUL
    Total time: 1 second
    
  • A script által létrehozott hello-world-portlet könyvtárba lépve vegyük rá az Antot, hogy készítsen egy buildet.
    ant deploy
    Buildfile: build.xml
    
    compile:
    
    merge:
        [mkdir] Created dir: /media/data/code/liferay/sdk/portlets/hello-world-portlet/docroot/WEB-INF/classes
        [mkdir] Created dir: /media/data/code/liferay/sdk/portlets/hello-world-portlet/docroot/WEB-INF/lib
         [copy] Copying 5 files to /media/data/code/liferay/sdk/portlets/hello-world-portlet/docroot/WEB-INF/lib
        [javac] Compiling 1 source file to /media/data/code/liferay/sdk/portlets/hello-world-portlet/docroot/WEB-INF/classes
    
    merge:
    
    war:
    
    clean-portal-dependencies:
          [zip] Building zip: /media/data/code/liferay/sdk/dist/hello-world-portlet-5.2.3.1.war
    
    deploy:
         [copy] Copying 1 file to /media/data/code/liferay/deploy
    
    BUILD SUCCESSFUL
    Total time: 4 seconds
    
  • Utolsó simításként annyi van hátra, hogy Bruno (Admin) nevében jelentkezzünk be az alapértelmezett Portal oldalon (email: bruno@7cogs.com, password: bruno, de szerencsére kattintós módszer is van), és a Welcome -> Add Application menüpont alatt adjuk hozzáadjuk a "Hello World"-öt az oldalhoz (érdemes rákeresni).
Szeretném megjegyezni, hogy eléggé sok fejfájást okozott az, hogy eleinte a megszokott módon szerettem volna az alkalmazás-fejlesztést vezérelni az IDE-ből. Ennek érdekében először a Liferay Eclipse Pluginjével próbálkoztam (elég sokat), amit végül nem sikerült rávenni, hogy tegye a dolgát. Ezután megpróbáltam olyan projektet létrehozni az IDE-ben, ami kapcsolatban áll a web-konténerrel, és vezérli a szinkronizációt és magát a szervert (itt is próbálkoztam pár kombinációval hátha csak én vagyok a hüje), de sajnos ez sem vezetett eredményre. Az járható út egyelőre, hogy az IDE-ből fejlesztés közben/után WAR fájlba csomagolva a konténer auto-deploy könyvtárába exportáljuk a produktumot, és a konzolból indított szerver pedig teszi a dolgát. Szerény véleményem, hogy ez eléggé fa-pados, és időigényes módszer, hiszen a konténer az egész alkalmazást újra húzza, és nem csak a szükséges részt frissíti. Szerk.: A Liferay Eclipse Plugin sajnos csak 6-os verziónál >= Liferayyel kompatibilis, így csak az új fejlesztések tehetők kényelmesebbé vele. Régebbi verzió esetén eléggé fapados sajnos a fejlesztés.
Pozitívumként tapasztaltam, hogy létezik Magyar Liferay közösség, továbbá egy hazai Liferay specialista az I-Logic képében, és az elengedhetetlen Facebook csoport.

2010. március 23., kedd

Egységesített "Global JNDI name" kezelés EJB 3.1 környezetben

Az alkalmazás-szerver, amikor létrehozunk egy Session Bean-t, automatikusan regisztrálja azt a JNDI kontextusban. Az regisztrált Bean-ekre referenciát legegyszerűbben a függőség-injektálással szerezhetünk, a szerver leveszi a direkt név-feloldás terhét vállunkról. Az említett módszer egységesen működik minden alkalmazás-szerveren, viszont a megvalósítása már korántsem egységes. Minden gyártó saját elképzelése szerint oldotta meg a feladatot.
  • JBoss global JNDI name: ejbapp/BeanName/local
  • GlassFish global JNDI name: InterfaceClass
  • WebSphere Community Edition global JNDI name: ejbapp-ejb/BeanName/InterfaceClass
  • Oracle Application Server (OC4J) global JNDI name: BeanName
Mivel az alkalmazás-szerver elrejti előlünk a feloldás problémáját, egészen addig amíg van lehetőségünk a függőség-injektálásra támaszkodni, kódunk hordozható marad. Viszont abban a pillanatban, amikor pl. egy nem menedzselt objektumból, vagy egy unit-tesztelőből kényszerülünk direkt JNDI név-feloldásra, kénytelenek vagyunk gyártó specifikus kódot készíteni.
Egy egyszerű példa szemlélteti a problémát EJB 3 környezetben:
@Remote
public interface JNDITestBeanInterface {
    String test();
}
@Stateless(name="JNDITestBean")
public class JNDITestBean implements JNDITestBeanInterface {
    public String test() {
        return "ok";
    }
}
public class JNDITestBeanClient {
    public static void main(String[] args) throws Exception {
        Context context = new InitialContext();
        JNDITestBeanInterface testBean = (JNDITestBeanInterface) context.lookup("JNDITest/JNDITestBeanInterface/remote");
        System.out.print(testBean.test());
    }
}
Az EJB 3.1 specifikáció megoldást kínál, mégpedig úgy, hogy egységesíti a név-regisztráció módját, így azon alkalmazás-szerverek, melyek implementálni szeretnék a specifikációt kénytelenek követni is azt.


java:global[/<application-name>]/<module-name>/<bean-name>#<interface-name>
@Stateless
@LocalBean
public class JNDITestBean {
    public String test() {
        return "ok";
    }
}
public class JNDITestBeanClient {
    public static void main(String[] args) throws Exception {
        Context context = new InitialContext();
        JNDITestBean testBean = (JNDITestBean) context.lookup("java:global/JNDITest/JNDITest-ejb/JNDITestBean");
        System.out.print(testBean.test());
    }
}
Az "interface-name" elhagyható, amenniyben interface nélküli Bean-re szeretnénk referenciát szerezni, továbbá ha az egyszerűsített deployment technológiát használva a WAR-ba helyeztük az EJB réteget az "application-name" azaz az alkalmazás neve opcionálissá válik.

2010. március 9., kedd

JRebel, avagy hogyan tegyük egyszerűbbé az EEletet

Bár elkötelezett híve vagyok a nyílt forrású szoftverfejlesztésnek,a címben említett eszköz mellett mégsem bírtam elmenni csukott szemmel. Mentségükre legyen mondva, a fizetős terméküket bármely szabad szoftver fejlesztéséhez ingyen biztosítják. A JRebel projekt célja, hogy olyan megvalósításokkal támogassa meg a fejlesztőket, melyek nem része a JVM-nek, így hagyományos módon vagy sehogy, vagy igen körülményesen oldhatóak csak meg. A fejlesztők 5 fő pontot neveznek meg, melyek az alábbiak:
1. Új osztályok hozzáadása
A HotSwap protokoll csak meglévő osztályok frissítését tesz lehetővé futás-időben, mivel egy egyedi megfeleltetés van az osztály neve és tartalma között. Ahhoz, hogy új osztályt adhassunk hozzá, kell válasszunk egy class loadert, mivel minden osztály egy specializált loaderben töltődik be. Sajnos ezt a szolgáltatást nem támogatja a HotSwap.
2. Erő-forrás-állományok frissítése
Az osztályaink mellé gyakran teszünk erő-forrás-állományokat melyek leggyakrabban xml vagy properties fájlok. A HotSwap technológia nem támogatja ezen állományok dinamikus módosítását, azaz minden változtatást deployolni kell a szerverre. Ez elég nagy probléma, főleg ha a szerveren van már pl. feltöltött állomány, mert a deploy folyamat esetlegesen törli azokat.
3. Webes erő-források
Minden weben megjelenő alkalmazásban vannak képek, HTML, CSS, JS, stb állományok, melyek az előző ponthoz hasonlóan nem frissülnek automatikusan a web-konténerben.
4. Gyorsító-tárak
Enterprise környezet nagy előnye, hogy a konténer, legyen szó web-konténerről vagy alkalmazás-konténerről, gyorsító-tárrazza osztályainkat. Ez ez előny egyben egy hátrány is, hiszen új elemek, pl. metódusok hozzáadása után a tár törléséig az előző állapot érhető el. Megjegyzem a legtöbb IDE támogatja a "deploy on save" metódust, mely az osztály mentésekor automatikusan frissíti a szerveren az osztályt.
5. Menedzselt komponensek
Egy alkalmazásban számtalan un. menedzselt komponens (EJB-k, Spring bean-ek, stb.) vannak. Az ilyen osztályokra jellemző, hogy általában van függőség-injektálással a konténer által behelyezett osztály, némely állapota társítva van un. instance vagy instance pool-lal, a funkcionalitás megvalósítása különböző rétegeken történik. Frissítésénél nem elegendő magát az osztályt frissíteni, hanem a különböző rétegeket, injectált osztályokat is.
 
Mindezen problémák feloldására a JRebel egy sajátságos metodikát ajánl, amit egy egyszerű, mégis hétköznapi példán mutatnék be. Jelenleg a 2.2.1-es verzió a legfrissebb, amit a letöltés oldalról szerezhetünk meg, 30 napos próbára. A telepítés grafikusan történik pár lépésben. A telepítő felajánlja, hogy befejezés után futtatja is a varázslót, amely megjegyzem példásan kivitelezett alkalmazás, minek segítségével pofon egyszerűvé válik a konfigurálás. A varázslót "UNIX like" környezetben a bin/jrebel-config.sh script futtatásával "varázsolhatjuk" elő.
  • Nincs más dolgunk mint első lépésben a megfelelő IDE-t kiválasztani, én Netbeans-el próbálkoztam, de támogatott az Eclipse, IntelliJ is.
  • Második lépésben felajánlja a varázsló az IDE-knek megfelelő plugint, Netbeans esetén a beépített pluginek közül a legegyszerűbb beszerezni.
  • Következő, sorban a harmadik lépés, hogy alkalmazásunkat felkészítsük a JRebel használatára. A segédlet mindent érthetően elmagyaráz, EJB projekt esetén a források gyökerébe, web projekt esetén a WEB-INF/classes mappában kell egy-egy rebel.xml állományt elhelyeznünk, és az xml-ben specifikálni, hogy mely osztályokat és/vagy erő-forrásokat szeretnénk ha a JRebel kezelné. Itt térnék ki kicsit a működésre. A JRebel egy újabb rétegként rátelepszik az alkalmzásunkra, és a betöltendő osztályokat vagy erő-forrásokat a rebel.xml alapján közvetlenül a fejlesztőkörnyezetből tölti be. Biztos mindenkinek volt már problémája a dinamikusan fordított web-oldalakkal, amikor is a WEB-INF/classes-ban elhelyezett translate.properties-t minden apró változtatás után deployolni kellett. Mivel ez egy gyakori eset, ebből a problémából indultam ki.
<?xml version="1.0" encoding="UTF-8"?>
<application
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.zeroturnaround.com"
xsi:schemaLocation="http://www.zeroturnaround.com http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd">
<web>
<link target="/"><dir name="~/NetBeansProjects/webapp/webapp-war/web"/></link>
</web>
</application>
  • Negyedik lépéséként választanunk kell Java verziót, operációs rendszert, és konténert, utóbbiból eléggé széles a paletta, én Glassfish 3-at használok, ezért azt választottam. Glassfish esetén az adminisztrációs felületen a JVM Setting / JVM Options alatt 2 paramétert kell megadnunk a JVM-nek, mégpedig a -noverify és -javaagent:/utvonal/jrebel.jar.
  • Ötödik lépésben az ügynök, na jó az agent beállításait kell megadnunk. Az advanced gombra kattintva állíthatjuk be pl. azt, ha nem akarunk a rebel.xml-ben abszolút eléréseket megadni, ennek módját részletesen tárgyalja a varázsló. A beállító-panelt "UNIX like" környezetben a bin/agent-settings.sh script futtatásával indíthatjuk.
  • Nincs más dolgunk, mint újraindítani az alkalmazás-szervert, deployolni a rebel.xml-el megfejelt alkalmazásunkat, és máris élvezhetjük a produktumot.
Bár a JRebel fizetős eszköz, a honlapján elhelyezett kalkulátor segítségével gyorsan kiszámolható megéri-e az árát, viszont a pénzben nem mérhető könnyebbséget még a kalkulátor sem tudja számításba venni.

2010. március 2., kedd

EJB 3.1 Lite

A Java EE 6 megjelenése számos újdonságot hozott, már itt is volt szó róla, ezek közül egy érdekesség az un. web-profilok létrehozása. Mint EE fejlesztő gyakran megfordult már a fejemben, hogy vajon tényleg mindig szükség van a teljes enterprise környezetre, hogy vajon nem-e ágyúval verébre amit csinálok, hiszen a szolgáltatások tárházának töredék részét használom egy-egy projektben. Az érem másik oldalán viszont mindig ott van az egyszerű fejlesztés, az átlátható logika (amit egy kicsit az architektúra is kikényszerít), valamint az élesen elkülönített szerep-körök. Ezt a problémát gondolták orvosolni az EE 6 fejlesztői a web-profilok segítségével, mégpedig úgy, hogy az alkalmazásunkhoz igazíthatjuk a felhasznált és szállított rétegeket. A szintén újdonságként megjelenő egyszerűsített deployment technológiának köszönhetően, melynek lényege, hogy az ejb-réteg behelyezhető a web-rétegbe megspórolva a két különálló projektre, némi megkötéssel egy web-konténer, is képes futtatni enterprise alkalmazásunkat. Az EJB Lite technológia nem más, mint egy előre csomagolt web-profil, az alábbi kritériumokkal:
  • Stateless, Stateful, és Singleton session beaneket használhatunk
  • csak lokális EJB hívásokat végezhetünk
  • nem támogatja az aszinkron hívásokat
  • támogatja a deklaratív és programozott tranzakciókat
A csomag egy annyira "lebutított" profil, hogy használatához web-konténer sem kell, ennek köszönhetően bármely hagyományos alkalmazásunkban használható, mindössze a javax.ebj és glassfish-embedded-static-shell könyvtár-gyüjteményt (jar) kell importálnunk.
import javax.ejb.Stateless;

@Stateless
public class TestBean {
public String test() {
   return "ok";
}
}
import java.util.Properties;
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import javax.naming.NamingException;

public class Main {
public static void main(String[] args) throws NamingException {
   Properties properties = new Properties();
   properties.put("org.glassfish.ejb.embedded.glassfish.installation.root", "");
   EJBContainer container = EJBContainer.createEJBContainer(properties);
   Context namingContext = container.getContext();

   TestBean testBean = (TestBean) namingContext.lookup("java:global/classes/TestBean");

   System.out.println(testBean.test());
}
}
Az alkalmazást futtatva az alábbi kimenetet kapjuk:
Mar 3, 2010 8:47:28 AM org.glassfish.ejb.embedded.EJBContainerProviderImpl getValidFile
SEVERE: ejb.embedded.location_not_exists
Mar 3, 2010 8:47:31 AM com.sun.enterprise.v3.server.AppServerStartup run
INFO: GlassFish v3 (74.2) startup time : Embedded(2591ms) startup services(492ms) total(3083ms)
Mar 3, 2010 8:47:31 AM org.glassfish.admin.mbeanserver.JMXStartupService$JMXConnectorsStarterThread run
INFO: JMXStartupService: JMXConnector system is disabled, skipping.
Mar 3, 2010 8:47:31 AM com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates
INFO: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate
Mar 3, 2010 8:47:31 AM AppServerStartup run
INFO: [Thread[GlassFish Kernel Main Thread,5,main]] started
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __ds_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __cp_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __xa_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __dm_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.SecurityLifecycle 
INFO: security.secmgroff
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.ssl.SSLUtils checkCertificateDates
SEVERE: java_security.expired_certificate
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.SecurityLifecycle onInitialization
INFO: Security startup service called
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.PolicyLoader loadPolicy
INFO: policy.loading
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.auth.realm.Realm doInstantiate
INFO: Realm admin-realm of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created.
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.auth.realm.Realm doInstantiate
INFO: Realm file of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created.
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.auth.realm.Realm doInstantiate
INFO: Realm certificate of classtype com.sun.enterprise.security.auth.realm.certificate.CertificateRealm successfully created.
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.SecurityLifecycle onInitialization
INFO: Security service(s) started successfully....
Mar 3, 2010 8:47:33 AM com.sun.ejb.containers.BaseContainer initializeHome
INFO: Portable JNDI names for EJB TestBean : [java:global/classes/TestBean, java:global/classes/TestBean!ejblite.TestBean]
ok
Bár nem hiszem, hogy a technológia leggyakoribb alkalmazási területét sikerült megragadnom, mégis ezen egyszerű példából kiindulva csak a fejlesztő kreativitásán múlik, hogy milyen környezetbe ágyazva alkalmazza az EJB réteget.
Aki részletesebben szeretne tájékozódni a témában, megteheti Roberto Chinnici blogjában, vagy a TheServerSide portálon. Az egyszerűsített deploymentről ebben a cikkben található részletesebb leírás.

2010. február 6., szombat

@Aszinkron EJB >=3.1 hívás

Az EJB 3.1 megjelenése számos újítást és egyszerűsítést hozott a rendszerbe, a fejlesztők, mint ahogy a 2.x-ről 3.0-ra váltáskor is, igyekeztek még kényelmesebbé tenni az enterprise alkalmazások készítését. Mivel mi Glassfish alkalmazás-szervert használunk, és a kiadást követő napokban kezdtünk bele egy új projektbe, úgy határoztunk, hogy a friss és ropogós EJB-t használjuk (eddig nem bántuk meg) az implementáláshoz. Az új EJB-t tanulmányozva szembeötlött egy lehetőség, ami mellett nem tudtam szó nélkül elmenni. Úgy néz ki, hogy nem csak nekem jutott eszembe, hogy az alkalmazást, amit fejlesztek párhuzamosítsam, hanem az EJB fejlesztőinek is, ezért megalkották az aszinkron EJB hívás fogalmát. A dolog igen egyszerű, a hagyományokhoz híven @Annotációval kell megjelölni azokat a SessionBean metódusokat, amiket nem a hagyományos módon szeretnénk kiajánlani.
package sample;

import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class TestBean {

    @Asynchronous
    public void asyncMethod() {
     //some functionality
    }
}
A metódus meghívása a megszokott módon történik, viszont az abban elhelyezett utasítások egy új szálon fognak futni. Ez eddig egyszerű, de mi történik, ha szeretnénk, hogy a hívás valamilyen visszatérési értéket is prezentáljon? Ezt úgy tudjuk megtenni, hogy visszatérési értékként a java.util.concurrent.Future<V> interfészt jelöljük meg, és annak valamely implementációját adjuk vissza.
package sample;

import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class TestBean {

    @Asynchronous
    public Future<String> asyncMethod() {
        try { Thread.sleep(1000);
        } catch (Exception ex) {}
        return new AsyncResult<String>("finish");
    }
}
A dolog érdekessége a hívó oldalon történik, hiszen valahogy kezelni kell tudni, hogy az aszinkron hívás éppen milyen állapotban van.
Future<String> asyncResponse = testBean.asyncMethod();
//some functionality
while (!asyncResponse.isDone()) {
    try { Thread.sleep(100);
    } catch (Exception ex) {}
}

if (!asyncResponse.isCancelled()) {
    try {
        String response = asyncResponse.get();
    } catch (InterruptedException e) {
    } catch (ExecutionException e) {}
}
Láthatjuk mi módon tudjuk "összeszinkronizálni" a hívásokat a szülő szálban. Természetesen meg is lehet szakítani a kérést, a Future cancel metódusával, illetve a rendelkezésünkre állnak a hagyományos Thread notify, notifyAll, és wait metódusai.