2008. december 27., szombat

Kérés tárgyának meghatározása és kódolások/ékezetek használata webes alkalmazásoknál

A cikk alapvetően a Servlet API-val kapcsolatos, de hasznos információkat tartalmazhat bármilyen webes keretrendszert használóknak. Az itt leírt dolgokat érdemes kipróbálni, esetleg a kérések, válaszok tartalmát egy HTTP proxy-val megvizsgálni.

Webes alkalmazások készítésekor ritkán van olyan eset, mikor minden URL-t különböző servlet szolgálna ki, és nem kapna az URL-ben különböző járulékos információkat. Emiatt érdemes az URL felépítését kicsit megvizsgálni, és megnézni, hogyan lehet a Servlet API felhasználásával az URL különböző részeit lekérdezni.

A leggyakoribb félreértés az URI, URL és URN közötti különbségekkel kapcsolatban szokott felmerülni. Az URI (Unified Resource Identifier), mely egy egyedi erőforrás azonosító. Az erőforrást lehet azonosítani névvel, elérési helyével vagy mindkettővel. Az URL (Unified Resource Locator) egy olyan URI, ami az erőforrást annak helyével azonosítja, azaz megadja, hogy kell ahhoz hozzáférni. Az URN (Unified Resource Name) szintén egy URI, mely a teljesen egyedi névvel azonosítja az erőforrást, és nem adja meg, hogy hogyan lehet ahhoz hozzáférni. Az URI szintaksztisát az RFC 3986 - Uniform Resource Identifiers Generic Syntax szabvány írja le, de hivatkozik több más szabványra is. Az URI, URL és URN közötti különbségeket a 1.1.3-as fejezet tartalmazza.

A webes alkalmazásokkal a böngészők a HTTP protokollon keresztül kommunikálnak, melyet a RFC 2616 - Hypertext Transfer Protocol - HTTP/1.1 szabvány tartalmaz. A lekérdendő erőforrást meg lehet adni abszolút URI-val, mely egy http sémájú URL.
GET http://jtechlog.blogspot.com/2008/13/servlet.html HTTP/1.1
Ennél gyakoribb megoldás, mikor a böngésző a HTTP kérés első sorában csak a host-hoz tartozó abszolút path-t adja meg, és Host nevű HTTP fejlécben adja meg a host-ot, pl.:
GET /2008/13/servlet.html HTTP/1.1
Host: jtechlog.blogspot.com

Ezen definíciókat átfordítva a Servlet API működésére az URL tartalmazza a teljes címet, sémával (protokollal), szervernévvel és porttal, míg az URI csak a szerveren belüli erőforrást azonosítja egyedileg. Mint azt láttuk, a teljes URL ugyan megjelenik a böngésző címsorában, de azt a böngésző általában nem küldi el egyben a webes alkalmazásnak, így nincs olyan metódus, mellyel ezt le lehet kérdezni. A HTTP kérés első sorában lévő értéket a HttpServletRequest interfész getRequestURI() metódusával lehet lekérni, mely a legtöbb esetben nem adja vissza a sémát, a szervernevet és a portot. Ezen értékeket rendben a ServletRequest interfész getScheme(), getServerName() és getServerPort() metódusaival lehet lekérdeni. A teljes URL előállítására a HttpServletRequest interfész getRequestURL() metódusa való, mely a háttérben az előbb említett metódusokat hívja meg, és így állítja össze a végeredményt. Összegezve:

requestURL = scheme + serverName + serverPort + requestURI

Dokumentáció szerint a tényleges, böngészőben megjelenő, és az összeállított URL-ek között minimális különbségek lehetnek, nevezetese, hogy a szóköz %20 helyett + (pluszjel) karakterként jelenik meg. Ezt Tomcat-nél nem tapasztaltam.

Ahogy a requestURI sem, így a requestURL sem tartalmazza a HTTP kérés paramétereit. A kliens oldalról paraméterek két féleképpen jöhetnek. Egyrészt a HTTP kérés törzsében név = érték formátumban, egymástól & karakterrel elválasztva. Másrészt jöhetnek az URL-hez hozzáfűzve is ugyanezen formátumban, ? karakter után. Ha űrlapot használunk POST metódussal, akkor a HTTP kérés törzsében jönnek a paraméterek, GET metódus esetén az URL-ben.

Megjegyzendő, hogy bár így szoktuk használni, és így érkezik be a HTML form-okról, nem kötelező a paramétereknek ezt a formátumot használniuk.

A requestURI is három részből áll. Egyrészt áll a web alkalmazást a szerveren belül egyedileg azonosító contextPath-ból. Ebből kiemelt a root contextPath, mely a szerver gyökerére vonatkozó kéréseket szolgálja ki. Áll a servletPath-ból, mely az adott web alkalmazáson belül a servlet-et jelöli (egy servlet több path-t is kiszolgálhat). Valamint az un. extra útinformációból (pathInfo), mely még a CGI-s időkből maradt ránk. A Servlet API-ból úgy kezelhetjük ezt, ha a servlet-ünket nem egy konkrét URL-re map-peljük rá, hanem wildcard karaktert alkalmazunk. Pl., nézzük a következő web.xml részletet.

<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>jtechlog.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/MyServlet/*</url-pattern>
</servlet-mapping>

Ekkor a MyServlet nem csak a /MyServlet kérést fogja megkapni, hanem pl. a /MyServlet/jtechlog/servlet.html-re vonatkozó kérést is, és ebben az esetben a servletPath a /MyServlet lesz, míg a pathInfo a /jtechlog/servlet.html. A három érték lekérdezhető a HttpServletRequest interfész getContextPath, getServletPath és getPathInfo metódusaival. A pathInfo eredetileg arra volt használatos, hogy a fájlrendszerben lévő állományokat egy servlet szolgálja ki, így meghívva a getPathTranslated metódust visszaad egy abszolút fájl elérést, a pathInfo-t, mint relatív útvonalat véve a web alkalmazásunk főkönyvtárához képest. Amennyiben ez utóbbi nem érhető el, pl. az alkalmazás WAR-ból fut, null-t ad vissza.

Milyen kódolásokat kell használnunk ilyen környezetben?

Az első, és legegyszerűbb kódolás a HttpServletResponse encodeRedirectURL és encodeURL metódusa. Ezen metódusok valók arra, hogy a paraméterként megadott URL-hez abban az esetben, ha a kliens böngésző nem támogatja a sütik használatát (nem képes, vagy ki van kapcsolva), hozzáfűzze a session azonosítót. Így ilyen böngésző esetén a session követés csak abban az esetben fog működni, ha az URL-t így kódoljuk. Az encodeRedirectURL metódust csak akkor használjuk, ha a response.sendRedirect metódust akarjuk alkalmazni, ugyanis ebben az esetben a kódolás lehet, hogy másképp történik. Ha JSP-ben használunk URL-eket, akkor a JSTL core taglib-jének url tag-jét használjuk erre a célra.

Ennél bonyolultabb annak biztosítása, hogy a speciális és az ékezetes karakterek jól jelenjenek meg, illetve jól kerüljenek bevitelre. A kezelésüket is megjelenítés és bevitel szerint csoportosítjuk.

Amennyiben ékezetes karaktereket szeretnénk használni, javasolt az UTF-8 character set használata, hiszen ez egy unicode alapú kódolás, de nem fix 2 byte-on, mint ahogy a Java Virtuális Gép kezeli a String-eket magában, hanem változó hosszon, maximum 4 byte-on. A 7 bites ASCII karakterek kódja ugyanaz, azaz egy byte, míg az európai ékezetes karaktereket 2 byte-on kódolja.

Amennyiben előállt az ékezetes szöveg, és a böngészőnek küldjük, ennek kódolását a Content-Type HTTP fejlécben kell megadni. Ennek beállítása történhet servlet-ből, vagy JSP direktívával, mint ahogy a következő sorok mutatják.

response.setContentType("text/html;charset=UTF-8");

<%@page contentType="text/html" pageEncoding="UTF-8"%>

Régebbi böngészők közül sok úgy értelmezte, ha nem volt küldve character set, hogy akkor azt a böngészőnek kell kitalálnia. Ez kivédhető, hogy ISO-8859-1 character set esetén is küldjük azt a Content-Type fejlécben.

Ékezetes karaktereket lehetőleg adatbázisból, properties állományból, vagy külső erőforrásból vegyünk csak.

Itt jegyzem meg, hogy egy Properties objektumot az 1.4-es Java-ig bezárólag csak egyszerű properties állományból lehetett betölteni, ami csak ISO 8859-1 kódolású lehet. Más kódolású állományból ilyent készíteni a native2ascii programmal lehetséges, ahol a speciális karakterek unicode escape szekvenciával lesznek kódolva. Az 5.0-ás Java-ban megjelent, hogy a Properties objektumot már XML állományból is be lehet tölteni, itt bármilyen kódolás használható. A 6-os Java-ban a Properties osztálynak megjelent a load(Reader) metódusa is, melyel bármilyen kódolású properties állományt be lehet tölteni.

Nagyon távol tartanék mindenkit attól, hogy a forráskódban ékezetes karaktereket használjon. Amennyiben mégis, itt is javasolt az UTF-8 használata, de ekkor a fordítónak meg kell mondani, hogy a forrás állományok kódolása milyen. Ezt a -encoding utf8 parancssori paraméterrel kell megadni. Fontos, hogy ilyenkor figyeljünk oda, hogy a szövegszerkesztőnk nehogy odaírja a BOM (byte order mark) karaktereket a szöveges állományunk elejére, mert a fordító hibát fog jelezni (a Windows Notepad pl. odaírja).

Ennél bonyolultabb a speciális és ékezetes karakterek fogadása. Amennyiben ilyeneket akarunk átvinni, URL kódolást kell alkalmaznunk. Ennek neve Percent-encoding, azaz százalékjellel történő kódolás, vagy ismert URL encoding, azaz URL kódolás néven is (ez utóbbit fogom használni). Ezt is az említett RFC 3986 írja le. Lényege, hogy bizonyos karaktereket változatlanul hagy, bizonyos speciális karaktereket pedig egy százalékjellel és egy kétjegyű hexadecimális számmal ír le. Később alkalmassá tették bináris adatok átvitelére is, ahol egy bájtot a % jel utánis hexadecimális értékével reprezentálnak.

Amennyiben mi állítunk elő olyan linket, mely ilyen karaktereket tartalmaz, nekünk kell az URL-t kódolnunk. Amennyiben viszont egy form-on használunk ékezetes karaktereket, a böngésző automatikusan kódolja azokat. A szerver oldalon ennek megfelelően dekódolásnak kell következnie.

Ebből következik, ha speciális karaktereket akarunk átküldeni, egyszer URL-ben, egyszer pl. form hidden field-ben, akkor nekünk kell arra figyelnünk, hogy URL esetén manuálisan kódoljuk, form esetén ne kódoljuk az átküldendő karaktereket. (A legjobb persze, ha nem akarunk speciális karaktereket átvinni.)

A manuális kódolást az URLEncoder.encode és a manuális dekódolást az URLDecoder.decode metódusokkal tudunk végezni. Ez elvégzi a kódolást application/x-www-form-urlencoded MIME formátumra, és vissza, melyet a HTML szabvány tartalmaz. Ennek külön érdekessége, hogy ez különbözik az URL kódolás szabványától, ami azt írja, hogy a szóköz karaktert %20 karaktersorra kell leképezni, nem pedig + (plusz) jelre (ezt az magyarázza, hogy az application/x-www-form-urlencoded az URL kódolás egy korai verziójából származott le). Amennyiben szabályos kódolást akarunk, használjuk az URI osztályt, és annak toURL() metódusát.

Az ékezetes karaktereknek a bájt kódja kerül átküldésre, viszont a nehézség az, hogy a böngésző nem küldi át, hogy az adott bájt sorozat milyen character set-en (pl. UTF-8-on) értelmezett. Ezért az ékezetes karakterek kezelésére a Servlet API automatizmust sem tartalmaz, nekünk kell azt biztosítani.

A beérkező adatok kódolását és dekódolását is két csoportra kell osztani. Egyrészt az URL-ben érkező szövegek, másrészt a HTTP kérés törzsében érkező paraméterek.

A böngésző általában az ékezetes karaktereket abban a character set-ben küldi el, ahogyan a választ is kapta. Így érdemes úgy írni az alkalmazásainkat, hogy a böngésző felé küldött szövegek, és a böngésző felől érkező szövegek is UTF-8 character set-ben legyenek.

Az URL-ben érkező szöveget a konténer dekódolja. Tomcat esetén ez a server.xml-ben konfigurálható a connector-nál kell beírni a URIEncoding="utf-8" paramétert. Amennyiben ilyent nem adunk meg, a dekódolás a ISO-8859-1 karakter set-tel történik.

Bizonyos metódusok elvégzik a dekódolást, de bizonyos metódusok nem. Lássuk:

getRequestURInincs dekódolás
getContextPathnincs dekódolás
getServletPathvan dekódolás
getPathInfovan dekódolás

Emiatt a következő képlet írható fel:

dekódolt(getRequestURI) = dekódolt(getContextPath) + getServletPath + getPathInfo

A paramétereket két csoportra oszthatjuk: az URL-ben és a HTTP törzsben érkező paraméterekre.

Az URL-ben érkező paramétereket lekérdezni a HttpServletRequest interfész getQueryString metódusával lehet, mely nem végez dekódolást, gyakorlatilag a kérdőjel utáni szöveget kapjuk vissza. A getParameter és hasonló metódusok meghívásakor ezt dekódolja a konténer a connector beállításai alapján.

A HTTP törzsben érkező paramétereket is alapértelmezetten a ISO-8859-1 karakter set-tel dekódolja, és ha ezt módosítani akarjuk, akkor be kell állítanunk a HTTP kérés karakter set-jét a HttpServletRequest setCharacterEncoding metódusával. Megjegyzendő, hogy ez csak Servlet 2.4+ konténereknél használható. Valamint az első paraméter kiolvasás, vagy a getReader által visszaadott Reader-ből való olvasás előtt kell meghívni, különben nem lesz hatása.

Hogy ezt ne kelljen minden egyes servlet elejére beírni, gyakran alkalmazott módszer egy servlet filter alkalmazása, mely minden kérésre meghívja az előbb említett metódust. A Spring alapban tartalmazza ezt az osztályt org.springframework.web.filter.CharacterEncodingFilter néven, melynek encoding init paraméterben kell megadni a kódolást. Ez az osztály az org.springframework.web.filter.OncePerRequestFilter leszármazottja, mely a request scope-ban egy változó elhelyezésével biztosítja, hogy kérésenként csak egyszer legyen meghívva.

A HttpServletRequest setCharacterEncoding metódusával biztosítjuk tehát azt, hogy a dekódolás a getParameter és társai metódusok segítségével milyen character set-tel történjen.

A org.apache.catalina.util.RequestUtil osztályt érdemes olvasgatni az URL dekódolással, valamint a paraméterek feldolgozásával kapcsolatban.

Összefoglalásképp:

  • Ha mi magunk állítunk össze link-et használjuk az encodeRedirectURL és encodeURL metódusokat a session követés érdekében.
  • Ha mi magunk állítunk össze paramétert, akkor URL-ben mi gondoskodjunk annak kódolásáról, form esetén a böngésző elvégzi helyettünk.
  • Állítsuk be, hogy a konténer milyen kódolás alapján dekódolja az URL-eket. Tomcat esetén URIEncoding paramétere a Connector konfigurációnak.
  • Használjunk CharacterEncodingFilter-t a form-ról érkező ékezetes szövegek helyes dekódolásához.

Amennyiben bináris adatot szeretnénk URL-ben, vagy form-ban átvinni, figyelni kell arra, hogy URL-ben kell kódolást végeznünk, form-ban nem. Ezt kikerülhetjük úgy is, hogy más kódolást alkalmazunk, melynek kimenete nem tartalmaz speciális karaktereket. Sajnos a Base64 nem alkalmas erre, mivel a kódolt szöveg tartalmaz olyan karaktert, melynek URL-ben speciális jelentése lehet. Alkalmazhatjuk ehelyett egy speciális típusát, a Base64u-t, mely kimenetében nem szerepelhet + / és = jel.

Gyakran használt alkalmazások

A leghasznosabb alkalmazásokat általában nem a neten találom, hanem valamelyik ismerősöm mutatja, vagy valamilyen blogban olvasok róluk. Emiatt én is közzéteszem gyakran használt alkalmazásaim listáját (általános, FAR plugin-ek, Firefox plugin-ek, fejlesztéshez használt), hátha más merít belőle ötleteket. A lista megtalálható a http://delfin.unideb.hu/~vicziani/alkalmazasok.htm címen.

2008. december 17., szerda

NetBeans 6.5 és GlassFish v2 ur2

A Számalk Továbbképzés keretein belül JP-05 - Alkalmazásfejlesztés Java EE környezetben tanfolyamot tartva több fejlesztőeszközzel és alkalmazásszerverrel megpróbálkoztunk már. Ezek a következők voltak:

  • Eclipse IDE for Java EE Developers Europa (3.3) + JBoss 4.2.2 GA
  • Oracle JDeveloper 10.1.3.4 + OC4J
  • NetBeans 6.5 + GlassFish v2 ur2
Ebben a bejegyzésben megpróbálom ezeket összehasonlítani.

Az Eclipse + JBoss kombinációt először a JBoss Tools 2.0.0GA Eclipse plugin-nel kezdtük, de annyira bugos volt, hogy hamar átálltunk arra, hogy csak az alkalmazásszerver elindítására és leállítására használtuk.

Sajnos ez a párosítás nem felel meg az oktatásra, ugyanis a JBoss-nak olyan hibaüzenetei vannak, melyek egyrészt több oldalas, több egymásba ágyazott exception-t tartalmazó stacktrace-ek, másrészt kezdő, avatatlan szem számára teljesen semmitmondóak. Az oktatás gyakorlatilag abból telt, hogy a hallgatók által generált hibákat nekem kellett visszafejtenem, hiszen a hibaüzenet számukra nem sugallta a megoldást.

Egyik hallgatónál a teszt eset futtatása során, amit pontosan ugyanúgy írt meg, mint a többiek, a következő hibaüzenet jött elő:

Could not connect to:  : 3393
java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
...

Ezzel kapcsolatban találtam is egy blog bejegyzést, a megoldás itt is ugyanaz volt, Eclipse újraindítás. A következő probléma az volt, hogy a JBoss esetén, ha egy EJB 3.0 Named Query szintaktikailag hibás volt, onnantól kezdve hiába deploy-oltam újra az alkalmazást, nem segített, a teljes alkalmazásszervert újra kellett indítani. Ez sem ritka kezdőknél.

Sajnos a hallgatók rossz szájízzel távoztak, a Java EE technológia még kiforratlan. Azóta kijött az Eclipse 3.4, Ganymede, azzal még nem próbálkoztunk.

A következő oktatás a JDeveloper + beépített pehelysúlyú konténerrel, az OC4J-vel történt. Talán összehasonlítva az oktatásokat, a legkevesebb probléma ezzel a kombinációval volt. Összeértek az eszközök, a sebességre sem volt panasz. Azóta kijött a 11g, mely már nem az OC4J-t, hanem a BEA WebLogic Application Server-t tartalmazza alkalmazásszerverként, hát vannak fenntartásaim.

A harmadik, legfrissebb tanfolyam a NetBeans + Glassfish párosítással történik. Egy ilyen oktatás már lement, és nagyon kedvezőek voltak a tapasztalatok, bár régebbi verziókkal történt. A jelenlegi összkép leírhatatlan, csupán az első példa alkalmazásnál (ami egy szál Session Bean, valamint ehhez tartozó Application Container-ben futó kliens) a következő hibákba botlottunk.

Minden egyes indításnál, telepítésnél a következő hibaüzenetet írja a konzolra:

Error attempting to process extensions from the manifest of JAR file C:\jtechlog\EnterpriseApplication1\dist\gfdeploy\EnterpriseApplication1-ejb.jar; ignoring it and continuing
java.io.FileNotFoundException: C:\jtechlog\EnterpriseApplication1\dist\gfdeploy\EnterpriseApplication1-ejb.jar (A rendszer nem találja a megadott fájlt)
     at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.(ZipFile.java:114)

Ezzel egy másik bejegyzés is foglalkozik, mely azt írja, hogy ez amiatt van, hogy a telepítésnél a NetBeans nem EAR-t, és abba csomagolt JAR-t deploy-ol, hanem rögtön kicsomagolva. Ez eddig szép is, de én tökéletesen egyetértek az egyik hozzászólóval, hogy próbáljuk követni a Linux szemléletet. Ha minden simán működik, akkor ne írjunk ki semmit, ha valami baj van, akkor viszont a lehető leginformatívabb szöveget. A hallgatókat sokszor megzavarja a hibaüzenet.

A következő probléma az volt, hogy bizonyos (!) hallgatóknál egy, az alkalmazásszerverben futó Session Bean-hez kapcsolódni kívánó távoli klienst nem lehetett lefuttatni, ugyanis a következő hibaüzenetet dobta.

Warning: Could not find file C:\jtechlog\ApplicationClient1\${wa.copy.client.jar.from}\ to copy.

A tanári gépen a hiba nem volt reprodukálható, de itthon új telepítéssel újra előjött. Megoldás nem lett, pár próbálkozás után "megjavult". Ezzel kapcsolatosan egy és két hiba is regisztrálva van a NetBeans Issue Tracker-ében, valamint egy konfig buherálás is ismeretes.

No, ha már elindult az alkalmazásunk, mutassuk be, hogy a Glassfish képes arra, hogy az EAR-ba csomagolt alkalmazás klienst kliens konténerben (Application Client Container - ACC) képes futtatni, méghozzá az adminisztrátori felületről is indítva, Java Web Start-tal. Ez sem sikerült, a Glassfish-ben ez egy ismert hiba, ha Java SE 6 update 6 utáni verzióval futtatod az alkalmazásszervert.

A következő hibát a Message Driven Bean-nél találtuk, hiszen üzenet fogadásakor a Glassfish logban mindig a következő üzenet jelent meg:

DirectConsumer:Caught Exception delivering messagecom.sun.messaging.jmq.io.Packet cannot be cast to com.sun.messaging.jms.ra.DirectPacket

A hiba szintén ismeretes a Glassfish-nél, megoldása, hogy az adminisztrációs konzolon, a Configuration -> Java Message Service menü alatt a típust "Embedded"-ről "Local"-ra kell váltani.

A hab a tortán az volt, hogy ugyan nagyon kényelmes eszköz van arra, hogy JPA esetén a persistence unit létrehozása közben a NetBeans-ből tudunk DataSource-ot létrehozni az alkalmazásszerveren, de ez csak a hallgatói gépek felén működött, a másik felén nem jelent meg az alkalmazásszerveren, csak a NetBeans Server Resources/sun-recources.xml állományban, így azoknak kézzel kellett felvenniük az adminisztrációs felületen.

Az oktatás alatt első körben csak pár soros példa kódokat használunk. Amennyiben ezen egyszerű példák esetén is ennyit hibáznak ezek az eszközök, mennyit hibázhatnak éles fejlesztés során? Mire való, hol van az a rengeteg automatikus teszt eset, ami pont az ilyen egyszerű eseteket tesztelné? Egyáltalán bevállalhatóak-e ilyen silány minőségű eszközökkel projektek?

Tovább keresem az oktatásra megfelelő párosítást.

2008. december 10., szerda

Tomcat 6 osztálybetöltők és naplózás

Az Apache Tomcat 6.0 implementálja a Servlet 2.5 and JavaServer Pages 2.1 specifikációkat. További újítások:
  • Javított memóriafelhasználás
  • Haladó IO képességek
  • Átalakított fürtözés
Ezen kívül a naplózásban is történtek változások. Az 5.5-ös Tomcat-ben jelent meg a juli csomag, melynek két Java osztálya két Logging API osztályt származtatott le, az egyik a org.apache.juli.ClassLoaderLogManager extends java.util.logging.LogManager, valamint a org.apache.juli.FileHandler extends java.util.logging.Handler. Az első főbb előnye, hogy osztálybetöltőnként lehet konfigurálni a Java Logging API-t, azaz logging.propperties állományt megadni, tehát web-alkalmazásonként külön állományban szabályozható a naplózás. A FileHandler főbb újdonsága, hogy {prefix}.{date}.{suffix} nevű állományhoz adja hozzá a napló bejegyzéseket.Az 5.5-ös osztálybetöltő-hierarchiája a következőképp néz ki:

Ahol minden alant lévő osztálybetöltő által betöltött osztály hozzáfér a felette lévő osztálybetöltő által betöltött osztályokhoz. A Catalina osztálybetöltő által betöltött osztályokhoz a web-alkalmazások nem férnek hozzá.

A 6-os Tomcat módosított a naplózáson, és az osztálybetöltők hierarchiáján is. Egyrészt a juli csomagban megjelent egy logging csomag is, mely a következő négy osztályt tartalmazza:

  • DirectJDKLog
  • Log
  • LogConfigurationException
  • LogFactory
Ezek valójában a Commons Logging csomag megfelelő osztályai, azzal a különbséggel, hogy egyrészt más a csomag nevük, valamint a Commons Logging-gal ellentétben nem dinamikusan választja ki a konkrét naplózó implementációt, hanem automatikusan a JDK 4-ben bevezetett Java Logging implementációt használja. Az Apache Tomcat minden osztálya így a org.apache.juli.logging.LogFactory osztályt (mely DirectJDKLog osztályt gyárt), és Log interfészt használja. Ezáltal gyorsabb, egyszerűbb, átláthatóbb.

Ahhoz, hogy cseréljük a naplózó implementációt (pl. Java Logging API helyett Log4J-t használjunk), le kell cserélnünk a bin könyvtár tomcat-juli.jar állományát, méghozzá azzal, melyet forrásból, az extras.xml Ant build fájl felhasználásával készíthetünk. Ez letölti a Commons Logging implementációt, mindenhol kicseréli a csomagnevet org.apache.commons-ról org.apache.juli-ra, és így egy olyan könyvtárhoz jutunk, ami már dinamikusan tölti be a megfelelő naplózó implementációt.

Ezzel egyetemben a az osztálybetöltő-hierarchia is változott, egyszerűsödött:

Azon kívül hogy a ClassLoaderLogManager osztálybetöltőnként tárolja a logger példányokat, kiegészítette a konfigurálási lehetőségét is a Java Logging API-nak a következőkkel:

  • képes különböző napló bejegyzéseket külön fájlba is kiírni, ezt úgy oldja meg, hogy egy osztályhoz több prefixet is lehet írni
  • logger-hez handler-t lehet megadni a loggerName.handler property beállításával
  • alapértelmezésben a logger nem delegál a szülőjének handler-e felé, ezt állítani a loggerName.useParentHandlers property-vel lehet
  • a root loggernek is lehet megadni handler-t a .handlers property használatával
  • kicseréli a property állományban betöltéskor a ${sytstemPropertyName} mintákat, a kapcsos zárójelek között megnevezett rendszerváltozók értékeivel
A konfigurációs állományában (/conf/logging.properties) a handler-eket a következőképp lehet megadni:
handlers = 1catalina.org.apache.juli.FileHandler,
2localhost.org.apache.juli.FileHandler,
3manager.org.apache.juli.FileHandler,
4admin.org.apache.juli.FileHandler,
5host-manager.org.apache.juli.FileHandler,
java.util.logging.ConsoleHandler
A ClassLoaderLogManager readConfiguration metódusa ezt úgy oldja fel, hogy az első pontig lévő részt (prefix) levágja (ez csak az egyediséget biztosítja), és a maradék osztályt példányosítja. Persze a további konfigurációt még a prefixszel együtt olvassa be (level, handlers).

A logging.properties-ban root handler-nek a 1catalina.org.apache.juli.FileHandler és a java.util.logging.ConsoleHandler van beállítva. Minden előtte el nem fogott naplózás ide kerül.

A különböző, előre telepített webes alkalmazások (manager, host-manager), valamint később telepíthető alkalmazás (admin) a ServletContext log metódusát hívja, ami a Tomcat alapértelmezett implementációját használja, ami a org.apache.catalina.core.ContainerBase.[engine].[host].[/context] (pl. org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager]) log-gal naplóz. Azon alkalmazások, melyek ugyanezen metódusokat használják naplózásra, ha naplóznak, annak a napló bejegyzéseit a 2localhost.org.apache.juli.FileHandler handler kapja el, ilyen lehet pl. a balancer.

2008. december 9., kedd

Erőforrások betöltése

Az alkalmazás által használt erőforrások olyan adatok, melyek a kódban nem helyezhetőek (nem ajánlott elhelyezni), mint pl. kép, hang, videó, szöveg (a szövegek pl. sablonok, felhasználói felület feliratok, stb.).

Az erőforrások elérésének két módja van. Egyrészt lehet direkt elérés, amikor a programban abszolút módon meg kell adni az erőforrás helyét (pl. konkrét fájl path vagy URL), és lehet indirekt, un. helyfüggetlen (location-independent) elérés is, mikor az alkalmazásban egy szimbólikus nevet adunk meg, és a környezet oldja fel ezt egy abszolút helyre. Ez a feloldás pont emiatt környezetfüggő.

Lehetőleg kerüljük a direkt elérést, az alkalmazásunkban mindig logikai neveket használjunk. A logikai nevekhez rendelhetünk magunk konkrét értékeket, pl. JNDI-ből oldjuk fel (pl. web konténerben, alkalmazásszerverben konfiguráljuk), vagy használhatjuk a Java beépített mechanizmusát, ha az erőforrást classpath-ból tudjuk betölteni (amikor az erőforrást pl. az alkalmazásunkhoz csomagolva, a JAR, WAR, EAR, RAR állományokban tudjuk elhelyezni).

Az erőforrások nevei különböző részekből állnak, melyek / karakterrel vannak elválasztva. Mindegyik rész egy Java azonosító. Ezzel a konvencióval egy hierarchiát lehet kialakítani az erőforrások elhelyezkedésében. Az utolsó rész az erőforrás egyszerű neve, ami tartalmazhat egy kiterjesztést is (ez csak egy konvenció), ponttal elválasztva, és szintén Java típusú azonosító.

Az elválasztójel mindig / jel, ennek feloldásáról pl. fájlrendszerre, URL-re, stb. a környezet gondoskodik.

A környezetet Java nyelvben a ClassLoader osztály leszármazottai biztosítják. Ezek megfelelő metódusai oldják fel az erőforrás neveket abszolút hivatkozásokra, és ezek is töltik be azokat.

Az erőforrások lehetnek rendszer-erőforrások, aminek betöltéséről a statikus ClassLoader.getSystemResource és ClassLoader.getSystemResourceAsStream metódusok gondoskodnak. Ez a CLASSPATH bejegyzéseket végigjárva próbálja az adott erőforrást megtalálni.

A nem rendszer-erőforrások betöltéséért a ClassLoader.getResource és ClassLoader.getResourceAsStream példány metódusok gondoskodnak. Ezek implementációja leszármazottanként más és más lehet, emiatt eltérhet az erőforrás-betöltés mikéntje. Minden osztálybetöltő azonban először rendszer-erőforrásként próbálja betölteni az erőforrást.

Az erőforrásokat ajánlott az osztályok mellé elhelyezni, ugyanazon csomag struktúrába. Ekkor a nevének megadásakor a csomag minősített nevét is meg kell adni, de elválasztáshoz . helyett a már említett / jelet kell használni.

A könnyebb használhatóság érdekében a bevezették a Class.getResource és Class.getResourceAsStream metódusokat is, melyek több könnyebbséget adnak:

  • Bizonyos osztályok betöltését az un. elsődleges osztálybetöltő végzi. Ekkor a Class.getClassLoader metódus null-t ad vissza. Ezt a Class.getResource és Class.getResourceAsStream metódus ellenőrzi, és ebben az esetben rendszer-erőforrásként próbálja betölteni az erőforrást.
  • Ha nem adjunk meg a / jelet az erőforrás első karaktereként, akkor relatív hivatkozásnak veszi, és automatikusan elérakja az osztály csomagjának minősített nevét, a pontokat / jelre cserélve. Erőforrás megadása azért lehet követendő példa, mert ilyenkor csomag átnevezésekor nem kell a forráskódban is változtatni az erőforrás nevét, aminek elmaradása ráadásul futásidejű hibát eredményez.
  • Ennek használatakor nem kell a getClassLoader security permission.
Az osztályra való hivatkozást meg lehet szerezni FooClass.class.getResource és egy példányon keresztül a foo.getClass().getResource módokon is. Az előbbi használata javasolt, mert:
  • Nem kell hozzá példány.
  • Ha örököltetünk, és egy másik csomagba kerül a gyermek osztály, akkor a foo.getClass() a leszármazott osztályt fogja visszaadni, így a relatív névmegadással nem lehet az erőforrást megtalálni.

Ehhez érdemes kicsit megérteni az osztálybetöltőket. Ha egy osztályt az elsődleges osztálybetöltő tölt be, tipikusan a java.* csomag bizonyos osztályait, akkor az null-t ad vissza. Ez azért van, hiszen az elsődleges osztálybetöltő C-ben van írva. Hiszen ha ez is egy normál osztály lenne, akkor ezt mi töltené be (tyúk-tojás probléma)? Osztálybetöltők:

  • Bootstrap class loader: boot classpath-on lévő osztályok, nem kell őket ellenőrizni
  • Extension class loader
  • System class loader
Osztálybetöltők között egy futásidejű fa hierarchia alakul ki. Osztálybetöltéskor un. delegation model van alkalmazva, azaz az osztálybetöltő először a szülő osztálybetöltővel próbálja betöltetni az osztályt, csak utána próbálja meg önmaga. Ha ő sem tudja, ClassNotFoundException kivétel váltódik ki.

http://java.sun.com/javase/6/docs/technotes/guides/lang/resources.html
http://www.javaworld.com/javaworld/javaqa/2002-11/02-qa-1122-resources.html
http://java.sun.com/developer/technicalArticles/Networking/classloaders/index.html

2008. december 3., szerda

Oracle Fusion Middleware - SOA

Egy előző bejegyzésben áttekintettem a BEA felvásárlással kapcsolatos Oracle Fusion Middleware infrastrukturális elemeket, most következzen az Oracle termékskáláján közvetlenül e felett lévő szint, a SOA Suite, BPM és SOA Governance Suite. A SOA eszközök felelősek a szolgáltatásorientált architektúra megvalósításáért. A BPM az üzleti folyamatok tervezéséért, fejlesztéséért, szimulációjáért és monitorozásáért. A SOA Governance felelős azért, hogy irányítás alatt tartsuk, felügyeljük a SOA architektúrában definiált szolgáltatásokat (pl. szabványok használatával, változás követésével). Minden terméknél leírom hogy az Oracle vagy a BEA terméke-e, valamint az Oracle stratégiáját.

SOA

Az ide tartozó termékeit az Oracle csomagban, Oracle SOA Suite néven is forgalmazza.
  • Oracle Data Integrator: heterogén adatmigráció, batch ETL (Extract, Transform, and Load - eszköz adatok kinyerésére külső adatforrásokból, ezek transzformálása az üzleti igényeknek megfelelően, majd ezek betöltése, pl. adattárházba): stratégiai termék. Heterogén, szemben az Oracle Warehouse Builderrel, mely csak Oracle adatbázisok között teszi ez meg.
  • Oracle Service Bus (AquaLogic ServiceBus & Oracle ESB)‏: itt egyik terméket sem tudják a másik fölé helyezni, integrálják a két terméket, stratégiai termék.
  • Oracle BPEL Process Manager: BPEL motor, folyamatok irányítására, stratégiai termék.
  • Oracle Complex Event Processor (Oracle CEP): Oracle Event-Driven Architecture-ba illeszkedő, eseményeket feldolgozó alkalmazások fejlesztésére szolgáló környezet. Stratégiai termék.
  • Oracle Business Activity Monitoring (BAM): üzleti tevékenységek, KPI (key performance indicator - mérőszámok arra, hogy mennyire teljesülnek az üzleti célok) monitorozására szolgáló eszköz. Stratégiai termék.
  • BEA WebLogic Integration: konvergáló termék. A BEA ezen a fronton elvesztette a csatát, valószínűleg azért, mert nem a szabvány BPEL-re épít ezen folyamatirányító alkalmazásuk.
  • BEA Cyclone & RFID Server: nem támogatott BEA termék, karbantartott termékek kategóriába került.

SOA BPM

Cél rendszer-központú, ember-központú, dokumentum-központú, és döntés-központú üzleti folyamatok elemzésére, tervezésére (modellezésére), implementálására, futtatására, szimulálására, követésére (monitorozására) szolgáló környezet felállítása, ami képes követni a gyorsan változó igényeket (szemben az IT álmával, az állandósággal). Ebből a csoportból minden termék stratégiai termék. Egyben, Oracle BPM Suite csomagban is kapható.
  • Oracle BPA Designer: tervező eszköz modellezéshez és folyamat tervezéshez.
  • Oracle BPM Studio (BEA AquaLogic BPM Designer)‏ - Oracle eszközhöz képest agilisebb tervező eszköz.
  • Oracle BPM Server (BEA AquaLogic BPM): folyamat irányításra, XPDL nyelven adható meg.
  • Oracle Document Capture & Imaging: papír alapú dokumentum és képek feldolgozására való folyamatirányító.
  • Oracle Business Rules - sűrűn változó üzleti szabályok deklaratív megadására, lehetőleg fejlesztés nélkül. Tárolhatók benne döntési táblák, amik ugyan relációs adatbázisban is tárolhatóak lennének, de ott dimenziók növekedésével ez egyre körülményesebb lenne. Lehetőség van feltételek megadására is.
  • Oracle User Interaction: humán workflow.
  • Oracle WebCenter
Valamint az előzőekben már említett Oracle BPEL Process Manager és Oracle BAM. A jelenlegi helyzet szerint a BEA-tól átvett, és átnevezett Oracle BPM Studio és Oracle BPM Server működik együtt (XPDL alapú), valamint az eredeti Oracle JDeveloper alapú Oracle BPA Designer (11g-ben még nem elérhető) és a Oracle BPEL Process Manager működik együtt (BPEL alapú). A kettő ugyan együttműködik, de a jövőképben egy tervező és futtató rendszer jelenik meg.

SOA Governance

  • BEA AquaLogic Enterprise Repository: termékek összegyűjtése, megosztása, változás és életciklus kezelése, stratégiai termék.
  • Oracle Service Registry: UDDI registry, stratégiai termék.
  • Oracle Web Services Manager: biztonsági szabályok, stratégiai termék.
  • EM Service Level Management Pack: válaszidő és elérhetőség mérésére, stratégiai termék.
  • EM SOA Management Pack: teljes SOA architektúra menedzseléséhez, stratégiai termék.
  • BEA AquaLogic Services Manager: konvergáló termék.
A SOA Governance keretén belül érdemes tárolni a következőket: XSD, WSDL állományok, üzleti követelmények, modellek, szabályok és szabványok, autit adatok és metrikák, verziók, kapcsolatok és függőségek, SLA, felelősök, biztonsági előírások.

2008. december 2., kedd

Minősített elektronikus aláírás

A post-ot 2010. április 6-án frissítettem.

Több projekt kapcsán is előjött az elektronikus aláírás témakör, hogyan kell minősített aláírást készíteni. Több dokumentum szól arról, hogy mi az az elektronikus aláírás, tanúsítványok, hitelesítésszolgáltatások, de kevés helyen van összefoglalva, hogy Magyarországon milyen lehetőségeink vannak, és ezek milyen szabványokra épülnek.

Mi köze ennek a Java-hoz? Egyrészt feladat lehet elektronikus aláírást készíteni, illetve ellenőrizni Java alkalmazásból, másrészt van olyan elektronikus aláírást létrehozó termék is, melyet Java-ban fejlesztettek.

Ezzel a témakörrel kapcsolatban magyar nyelven rengeteget lehet megtudni Almási István "Elektronikus aláírás és társai" című könyvéből.

Én most csak arra térnék ki, hogy mi kell az elektronikus aláírás készítéséhez? Egyrészt kell egy tanúsítvány, mely tartalmazza a tulajdonos személyes adatait, a tulajdonos nyilvános kulcsát, valamint hitelesítve van egy harmadik fél, a hitelesítésszolgáltató által (ez a tanúsítvány hitelesítésszolgáltató általi elektronikus aláírásával történik). Szükség van egy aláíró eszközre, mely általában egy token, mágneskártya, USB eszköz stb. Ez az eszköz végezheti az aláírást oly módon, hogy beküldésre kerül az aláírandó adat, és az aláírást adja vissza (megakadályozva ezzel a magánkulcs másolásának lehetőségét) - ennek neve a BALE, biztonságos aláírás-létrehozó hardver eszköz. Valamint szükség van egy környezetre, melyben az aláírás történik, mondjuk egy személyi számítógépre, és különböző szoftver eszközökre, melyek az aláíró eszközzel kommunikálnak.

A könyv megírása óta a magyar hitelesítésszolgáltatókkal (certificate authority or certification authority - CA) kapcsolatban több változás is történt, melyeket nyomon lehet követni a Nemzeti Hírközlési Hatóság Elektronikus aláírásokkal kapcsolatos nyilvántartásokat tartalmazó honlapján.

Itt fel vannak sorolva a nem-minősített és minősített szolgáltatók is, a minősítettek a következők:

A GIRO HIT@LES szolgáltatás és a Magyar Telekom (volt Matáv) e-Szignó is megszűnt, a megszűnő szolgáltatásokat átvevő szervezet a NetLock. Az Educatio viszont nem minősített, csak fokozott biztonságú hitelesítés szolgáltató, érdekesség vele kapcsolatban, hogy a MÁV Informatika Zrt. az alvállalkozója, és tanúsítványkiadói, és részben ügyfélszolgálati feladatokat lát el, de a jogi felelősséget az Educatio viseli.

Valamint létrehoztak egy Közigazgatási Gyökér Hitelesítés-szolgáltatót (KGYHSZ) is, melynek jellegtelen honlapján megtudható, hogy az előbb felsorolt hitelesítésszolgáltatók mindegyikét felülminősítette, azaz egyaránt jogosultak közigazgatási felhasználásra szolgáló, hivatali aláíráshoz és ügyfél által használt aláíráshoz kapcsolódó tanúsítványok kibocsátására.

Ahhoz, hogy minősített aláírást hozzunk létre, a minősített tanúsítványunkon kívül szükség van egy biztonságos tanúsított minősített aláírást létrehozó termékre is, melyet jelenleg két szervezet tanúsít, hogy az adott hardver (ide tartozik a BALE is) és szoftver megfelel-e aláírás létrehozására:

Az elektronikus aláírás és annak rokon, kapcsolódó és származékos technológiák népszerűsítésére létrejött a Magyar Elektronikus Aláírás Szövetség, mely kiadott egy MELASZ-Ready Ajánlást, melynek célja, hogy a magyar közigazgatás számára kidolgozzon egy olyan feltételrendszert az elektronikus aláírás formátumára, és egy olyan tanúsítási eljárást, mellyel biztosítható a különböző eszközök által készített aláírások kompatibilitása. A szabvány a W3C XML Advanced Electronic Signatures (XAdES) szabványon alapul. A XAdES XML formátumú, és hátránya, hogy az aláírt dokumentum megtekintéséhez először ki kell csomagolni azt. Mivel a PDF a PKCS szabványok közül válogatott, az a XAdES-szel nem kompatibilis, előnye viszont, hogy az aláírt dokumentum azonnal megnyitható, kicsomagolás nélkül.

A következő aláírási termékek tanúsítottak, melyek képesek minősített aláírás létrehozására. Mindegyiknél jelzem, hogy Melasz Ready-e.

  • Polysys CryptoSigno, a Magyarországon első platform független, Java-ban implementált fejlesztő készlet. Melasz Ready.
  • Microsec e-Szignó, mely két fő részből áll: XadesSigner megbízható aláírás létrehozó modul, és EsignoNav felhasználói felületből. Microsoft Visual C++ fejlesztés. Ennek létezik egy e-Szignó Automata nevezetű változata is, mely több platformon is használható, és parancssorosan, valamint C-ből, Java-ból és COM csatoló felületen is el lehet érni. Van böngészőbe telepíthető plugin is. Melasz Ready.
  • Argeon Infosigno egy SDK, melyet C++ és .NET programozási nyelveken tudunk használni. Melasz Ready.
  • Grepton MultiSigno (korábban Kopint-Datorg fejlesztés, melyet a Grepton vásárolt meg). Kicsit mostohán bánik vele, a honlapján nem igazán lehet megtudni, hogy a termékcsaládnak milyen részei vannak, használható-e külső alkalmazásból, milyen programozási nyelven lehet megszólítani. Ugyan egy dokumentációban olvasható, hogy Melasz Ready, de a Melasz honlapján nem szerepel.
  • E-Group SDX egy termékcsalád, mely tartalmaz szerver oldali és kliens oldali (van böngészőbe épülő változata is) aláíró modulokat. Melasz Ready.