pátek 20. února 2004

Náhrada <xsl:call-template> pomocí <xsl:apply-templates>

Tento článek začíná úvahou nad formáty XML dat, které se používají pro zobrazení výsledku SQL dotazu. Nejde mi zde o konkrétní formáty, ale spíš o stavbu takového XML, jeho čitelnost a zpracovatelnost pomocí XSLT. Proč nemám jako Roman v oblibě formáty s neurčitými názvy tagů jako <datapacket> a naopak preferuji názvy tagů podle reálných objektů. Tato úvaha je jakýmsi "předskokanem" ke hlavnímu tématu článku, který už se chystám dlouho napsat, a jeho délkou si to tu na blogu zas vyberu na půl roku dopředu :-) - proč dávám v XSLT přednost aplikovaným šablonám (<xsl:template match=...>, <xsl:apply-templates ...>) před pojmenovanými (<xsl:template name=...>, <xsl:call-template ...>) a že běžné úlohy v XSLT jsou pomocí aplikovaných šablon nejen řešitelné, ale řešitelné mnohem elegantněji, byť na první pohled je čitelnější řešení s pojmenovanými šablonami (zvláště je-li ochucené <xsl:for-each>, při té příležitosti si vzpomínám na slavnou větu "Opravdový programátor umí psát fortranské programy v každém jazyce." :-).

V našich intranetových aplikacích je odpověď na request obvykle vytvořena z výsledku nějakého SQL dotazu. Pro zobrazení tohoto výsledku používáme XML data, ze kterých se následnou XSLT transformací vygeneruje HTML. Právě data tabulkového charakteru jsou nejčastěji používaným materiálem, a proto je důležité s nimi manipulovat efektivně při současném zachování čitelnosti kódu a výhod technologie XSLT. Pojďme si to demonstrovat na příkladu, kdy máme zobrazit výsledek dotazu select id, name, born from person.

Formát XML dat

Jedním z diskutovaných témat je formát XML dat. Tady stojíme na rozcestí. První způsob je, že použijeme formát, který se dívá na vrácená data primárně jako na tabulku a bude podle toho nazývat XML tagy. Kořenový element pak bude mít pod sebou strom, jehož hloubka nebude velká (zpravidla 2). XML pak může vypadat např. takto:

<datapacket>
 <rowdata name="person">
  <coldata name="id">1</coldata>
  <coldata name="name">Adámek</coldata>
  <coldata name="born">1.2.1934</coldata>
 </rowdata>
 <rowdata name="person">
  ...
</datapacket>

Některé takové formáty jsou známé (nevím zda přímo standardizované), setkal jsem se např. s formáty MIDAS a DIML. V každém případě kdo jednou četl z databáze a převáděl data na XML, toho muselo toto řešení napadnout a pokud nepoužil známý formát (nebo i specifikovaný vnitrofiremně), navrhl si nějaký vlastní. Výhody jsou zřejmé: jednoduchý kód pro tvorbu XML a snadno napsaná šablona. Nevýhodou však je, že takové XML nekoresponduje tak dobře s objekty reálného světa. Nezajímá nás přece, že máme řádek, nás zajímá, že máme osobu! Řádek je pouze implementační záležitost a takto nazvaný tag ztíží vše, co přinese případné zveřejnění dat (výměna dat, čitelnost, příkladů by se našlo jistě více). Název, který představuje podstatu objektu, by měl podle mého názoru být názvem tagu, zatímco atribut představuje vlastnosti, bez kterých objekt neztrácí svou podstatu.

Druhý způsob (hybridy mezi oběma způsoby pro nás nejsou zajímavé) je tedy např. následující XML formát:

<people>
 <person>
  <id>1</id>
  <name>Adámek</name>
  <born>1.2.1934</born>
 </person>
 <person>
  ...
</people>

Formát je čitelnější a z názvu tagů ihned vidíme, jaký objekt tag představuje. Má zde smysl, aby potomci tagu <person> byli dále strukturovaní, např. <children><child id="101" /><child id="102" /></children>, zatímco u tagu <coldata> by takové strukturování bylo matoucí.

Použití aplikovaných šablon

První z uvedených způsobů nás zláká ještě více v okamžiku, kdy zjistíme, že potřebujeme ve dvou různých XSL souborech zobrazit tabulku stejným způsobem pro různé dotazy. Např. mějme druhý dotaz select iddoc, title, content from document. Použijeme-li první způsob, je nejjednodušší v obou XSL souborech provést <xsl:include href="common.xsl" /> a v souboru common.xsl definujeme šablony

<xsl:template match="datapacket">
 <table>
  <xsl:apply-templates select="rowdata" />
 </table>
</xsl:template>

<xsl:template match="rowdata">
 <tr>
  <xsl:apply-templates select="coldata" />
 </tr>
</xsl:template>

V místě, kam se má zobrazit tabulka, pak stačí napsat

... <xsl:apply-templates select="datapacket" /> ...

Použijeme-li druhý způsob, musíme definovat šablonu v každém z nich, protože každá se matchuje na jiný tag. V prvním souboru se definuje tato šablona, analogicky tomu je ve druhém.

... <xsl:apply-templates select="people" /> ...

<xsl:template match="people">
 <table>
  <xsl:apply-templates select="*" />
 </table>
</xsl:template>

<xsl:template match="person">
 <tr>
  <xsl:apply-templates select="*" />
 </tr>
</xsl:template>

Máme sice pěkně pojmenované tagy, ale kód nám za prvé narostl a za druhé se stejné věci opakují na několika místech. Přitom nelze šablony dát v této podobě do common.xsl, protože se pokaždé matchují na jiný tag. Můžeme dát do commonu šablony <xsl:template match="people|documents"> a <xsl:template match="person|document">, ale není to dostatečně obecné řešení. Pro každý nový tag by se matchovací výraz musel rozšířit o další alternativu. Navíc si tím koledujeme o průšvih v případě, kdy by se "tabulkový" element jmenoval stejně jako "řádkový" element. Ukažme si radši jiné řešení, představím zde dvě:

1. Použít pojmenované šablony. V souboru common.xsl definovat následující šablonu. Bude se volat pomocí <xsl:call-template select="maketable"><xsl:with-param name="rootnode" select="people" >.

<xsl:template name="maketable">
<xsl:param name="rootnode">
 <table>
  <xsl:for-each select="$rootnode/*">
   <xsl:call-template name="makerow">
    <xsl:with-param name="rownode" select="." />
   </xsl:call-template>
  </xsl:for-each>
 </table>
</xsl:template>

Podobně definovat šablonu makerow atd. To je podle mého názoru druhý nejhorší způsob, jak to udělat. (Horší už je jen rozepsat tělo šablony makerow do šablony maketable a získat šablonu, která připomíná spíš program - jedna dlouhá nudle s několika vnořenými cykly.) Používání pojmenovaných šablon ve spojení s <xsl:for-each> není pro tyto případy vhodné, protože

  • mění kontextový uzel a tím narušuje přirozené procházení dokumentu pomocí aplikovaných šablon.
  • znemožňuje použít defaultně aplikovanou šablonu.
  • tím, že umožňuje v jedné šabloně definovat výstup pro více typů uzlů, přestává platit, že každý uzel je zodpovědný za svůj výstup, což dělá kód méně čitelným.
  • zatímco aplikovaných šablon se může matchovat k jistému uzlu více a existuje mezi nimi pravidlo o rozhodnutí konfliktů, pojmenovaná šablona se nesmí vyskytnout více než jednou (při vícenásobném výskytu dojde k chybě, pravidlo "pozdější vyhrává" zde neplatí). Tím je znemožněno definovat případy s výjimečným chováním.

Celkově shrnuto vede tento přístup k procedurálnímu stylu programování (snad proto je taky tak častý) a nevyužívá hlavní síly tohoto deklarativního jazyka. Tím nechci říct, že pojmenované šablony nemají žádné využití. Hodí se dobře jako náhražka za funkce, které ač je to s podivem, v XSLT 1.0 nejsou (náhrada řetězce v řetězci, práce s datumy). Dále se hodí pro generování fragmentů HTML kódu, které se opakují nebo které se nevztahují k žádnému uzlu v XML datech (pole navigačních buttonů). Samozřejmě mohou být užitečné, i když mají uzly jako parametry, pokud je to mimo přirozený průchod uzly pomocí <xsl:apply-templates>. Např. chceme obarvit řádky podle stáří lidí. Přes <xsl:apply-templates> dojdeme k tagu <person>, v šabloně matchované na tento uzel zavoláme <xsl:call-template name="colorize"><xsl:with-param name="bornnode" select="born">, tím se pouze nastaví pozadí elementu <tr>, ale k uzlu <born> pak za účelem vytvoření výstupu přistupujeme opět přes <xsl:apply-templates>.

2. Použít aplikované šablony s módem. Poslední nevýhoda prvního řešení nás vede zpět k deklarativnímu přístupu a k použití aplikovaných šablon. Je třeba najít způsob, jak zachovat "pojmenování" šablony, a přitom mít šablonu aplikovanou. A tady nám pomohou módy. Volání bude tentokrát vypadat <xsl:apply-templates select="." mode="maketable"> a šablona v common.xsl bude vypadat takto:

<xsl:template match="*" mode="maketable">
 <table>
  <xsl:apply-templates select="*" mode="makerow" />
 </table>
</xsl:template>

Tím se odstranily prakticky všechny nevýhody prvního řešení: při konstrukci tabulky se prochází uzly pomocí <xsl:apply-templates> a jednotlivé uzly mají ve svých šablonách pouze kód bezprostředně zodpovídající za jejich zobrazení. Jestliže se má některá tabulka (nabo řádek nebo buňka) vykreslit jinak než obvykle, výjimka se snadno dosáhne definováním šablony s vyšší prioritou (deklarované později než šablona v commonu). Přitom je zachována možnost mít uzly pokaždé jinak nazvané a současně ti, kdo používají první způsob formátování dat, mohou tento způsob využívat také.

Takto jsme nahradili pojmenovanou šablonu aplikovanou šablonou a přitom název šablony pouze přešel v název módu. Uzel rootnode předávaný původně jako parametr je v našem případě kontextovým uzlem šablony. Obecně ale nemusí mít pojmenovaná šablona žádné parametry a do match se pak může uvést jakýkoli uzel, který je v XML jen jednou, já jsem si zvykl psát match="/". BTW i aplikované šabloně lze zadat parametry pomocí <xsl:with-param>.

Framework pro podobné dokumenty

Zmíněná náhrada nám usnadní práci v případě, kdy vytváříme několik dokumentů, které mají stejnou kostru, ale liší se v detailech. Pak je možné si kostru definovat v common.xsl a v jednotlivých šablonách pouze specifické detaily. Příklad: Chceme, aby první stránka nás "přivítala" alertem, zatímco jiná stránka má mít zvláštní styly. V common.xsl bude toto:

<xsl:template match="/">
<html>
 <head>
  <xsl:apply-templates match="/" mode="specialni-styly" />
 </head>
 <body>
  <xsl:apply-templates match="/" mode="uvitaci-alert" />
  <xsl:apply-templates select="root" />
 </body>
</html>
</xsl:template>

<xsl:template match="/" mode="specialni-styly" />

<xsl:template match="/" mode="uvitaci-alert" />

V XSL souboru pro první stránku pak bude pouze toto:

<xsl:template match="/" mode="uvitaci-alert">
 <xsl:attribute name="onload">alert('ahoj Hele, tady Jů!');</xsl:attribute>
</xsl:template>

Podobně v XSL souboru pro stránku se zvláštními styly bude pouze template s mode="specialni-styly". Soubor common.xsl musíme samozřejmě vložit na začátek, aby se uplatnilo pravidlo "poslední vyhrává". Kdybychom použili instrukci <xsl:call-template>, museli bychom ve všech šablonách kromě té výjimečné definovat prázdné tělo a při přidání nové šablony do kostry bychom museli provést zásah do všech šablon. Takto jsme vlastně dosáhli možnosti nepovinného vložení šablony. Může se stát, že nějaká šablona je tak důležitá, že vyžadujeme její realizaci v každém XSL souboru, v takovém případě je použití pojmenované šablony vhodné.

Závěr

Formáty XML dat, které vyjadřují tabulková data, a které se liší názvy tagů a přitom mají stejnou nebo podobnou strukturu, je možné převádět na výstup jednotným způsobem. XSL má dostatečně silné jazykové prostředky na to, aby se maximálně eliminovalo opakování stejných částí kódu. Neomezuje tedy návrh formátu XML dat. Případné požadavky na výjimky je možné snadno vyjádřit předefinováním některých částí, takže je dotčeno jen tolik šablon, kolik je nutné. Instrukcí <xsl:apply-templates> je možno nahradit instrukci <xsl:call-template>, což vede k efektivnějšímu návrhu koster.

Desktopové aplikace v Jave, utopie nebo realita?

Mnoho vývojářů začínajících v Jave zajímá možnost vývoje desktopových aplikací. Kdybych nechtěl zabíhat moc do detailu asi bych se o možnostech vyjádřil jako o naplnění myšlenky write once, run anywhere. V podstatě existují dva pohledy na javovské GUI technologie Swing/AWT. Ten první je o přenositelném ne bezchybném řešení, ten druhý mnohem kritičtější je o těžkopádném nabubřelém řešení.

Cílem tohoto spotu není obhajovat jeden či druhý pohled, názor si musí udělat každý sám. O možnostech vývoje desktopových aplikací se pokusil Andrei Cioroianu v článku Java Desktop Development.

Související články

čtvrtek 19. února 2004

A je tu další local root...

Včera obletěla svět security advisory, která oznamovala další chybu v Linuxových jádrech - konkrétně řady 2.6,2.4 a 2.2. Proto byly vydány nová jádra v každé z těchto řad, která mají chybu opravenu. Zdá se, že Linus považuje za dobré chybu v jádře neodhalovat dokud není vydáno stabilní jádro - prý byla chyba ošetřena v jednom z -rc jader již před cca 14-ti dny - v changelogu jsem však o opravě nic nenašel.

V každém případě jde o několikátou chybu v jádře, která je dost závažná - bylo tu už několik chyb na ptrace a nyní mremap - ta se odlišuje tím, že je kernel write a to znamená že i když máte nainstalované grsecurity či jiný patch v jádře pro zvýšení bezpečnosti, tak touhle chybou to obejdete a získáte absolutní moc nad strojem. Na minulý mremap není exploit, který by roota získal, existuje alespoň exploit který umí totálně zastavit počítač.Na ten včerejší už exploit koluje po netu, ale nezkoušel jsem ho - předpokládám že to bude něco jako ten minule.

Asi si na podobné věci budu muset zvyknout - ještě teď si pamatuju jak mě zpráva o ptrace šokovala. Není to ale nic proti chybám ve Windows - například chyba MS04-007, která umožňuje spuštění kódu na vzdáleném počítači - zatím jsem viděl jen exploit na restart mašiny, ale byl dobře okomentovaný, takže snad i ne-céčkař jako já by byl schopen si ho upravit na něco užitečnějšího nez jen na restartování počítače. Ta chyba se vztahuje na službu "server" (sdílení složek/tiskáren), kterou mají snad všechny windows v defaultu zaplou.

Možná se někomu bude zdát, že srovnávám hrušky s jablky, ale ono jde o bezpečnost systému jako takového - je pravda, že tu máme chyby v programech jako je rsync, které umožňují získat roota vzdáleně, ale nikdo vás nenutí při instalaci Linuxu nainstalovat si rsync, kdežto při instalaci Windows je to již jaksi v defaultu zaplé. Navíc Linux == jádro -> Linux != aplikace.

A má rada na závěr? Updatovat, updatovat a updatovat... jinak to asi nepujde :]

středa 18. února 2004

Servlet 2.4 a JSP 2.0 prakticky

Pokud si chcete vyzkoušet novinky v rámci Servlet specifikace 2.4 a JSP 2.0 pak můžete začít článkem Bruce W. Perry Six Cool New JSP and Servlet Features.

  • Servlet jako welcome file
  • Mapování filtru na RequestDispatchers
  • Použití ServletRequestListener a ServletRequestAttributeListener.
  • Použití Expression Language (EL)
  • Ukázka tag files
  • Ukázka vlastní Expression Language funkce

Nový web framework Chrysalis

Na stránkách serveru TheServerSide.com byl představen open source web framework Chrysalis. Krom architektury vzor MVC bylo cílem vývojářu poskytnout odlehčenou alternativu ke komplexním Struts. Mezi inovace tohoto webwork patří

  • hiearchická konfigurace
  • client side validace vstupních parametrů
  • podpora Filters i pro starší kontejnery(Servlet 2.2)

úterý 17. února 2004

Protunelováno!

Nedalo mi to a zkusil jsem si napsat jednoduchý javovský kód, který by mi dokázal protunelovat skrz proxy nějakou aplikaci - a povedlo se :). Sice ta moje třída byla hodně neohrabaná a hlavně zatím neuměla zpracovat více streamů/socketů po sobě, ale na ssh to stačilo (ssh jen otevře spojení a drží - oproti http, které otevírá spojení pro každý request - když se bavíme o HTTP/1.0). Mluvím v minulém čase a to proto, že po napsání a otestování alespoň na ssh jsem narazil na projekt, který od základu řeší můj problém - jmenuje se JSecureTunneling. Program umí navázat spojení s ssh serverem v Internetu (samozřejmně, že nejen přes http proxy, ale i socks proxy) a protože ssh není kdejaké ořezávátko, tak umí pomocí tohoto protokolu udělat další tunely i mimo cílový počítač - tím pádem pro mě není problém protunelovat téměř cokoli :)...

Snad vám jen nebude vadit, že sem nedám link na ten můj zdroják - asi by se některým neudělalo dobře :]