V předcházející podkapitole jsme zmínili pojmy analyzátor a
mapovač. Analyzátor je instance třídy FastPageParser,
která analyzuje a rozebere požadovanou HTML stránku a
vrátí daty naplněnou instanci třídy
Content2HTMLPage.
Mapovač je instance třídy implementující rozhraní DecoratorMapper, která rozhoduje o příslušném dekorátoru, který bude vybrán pro odekorování požadované stránky.
Jak analyzátory, tak mapovače konfigurujeme v souboru
/WEB-INF/sitemesh.xml naší aplikace.
<sitemesh>
<property name="decorators-file"
value="/WEB-INF/decorators.xml" />
<excludes file="${decorators-file}" />
<page-parsers>
<parser content-type="text/html"
class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
</page-parsers>
<decorator-mappers>
<mapper class="cz.morosystems.sitemesh.mapper. \
SessionDecoratorMapperForSpringSessionLocaleResolver" />
<mapper
class= "com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
<param name="decorator" value="print" />
<param name="parameter.name" value="printable" />
<param name="parameter.value" value="true" />
</mapper>
//rozšíření com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper
<mapper
class="cz.morosystems.sitemesh.mapper.ConfigDecoratorMapperForMVCProjects">
<param name="config" value="${decorators-file}" />
</mapper>
</decorator-mappers>
</sitemesh>
V souboru vidíme zaznačenu cestu k souboru uchovávající relace
název dekorátoru a jeho realizaci (cestu k fyzickému umístění
dekorátoru), případně vzor URL, na kterou je daný
dekorátor namapován. Ale o tom až později. Je tu uvedena také definice
objektu třídy FastPageParser, který je zodpovědný
za parsování odpovědí požadavků s typem obsahu
text/html. Dále vidíme definici samotných mapovačů.
Definujeme dva námi vytvořené mapovače a jeden mapovač z distribuce
rámce. Jejich funkcionalitu si vysvětlíme později.
V současné distribuci rámce SiteMesh je k dispozici pouze parser odezvy s obsahem typu text/html, nicméně autoři tvrdí, že připravit analyzátor pro parsování odezvy s jiným typem obsahu není problém. Nicméně tuto vlastnost zřídkakdy využijeme, proto se raději zaměříme nejen na typy mapovačů distribuovaných společně s rámcem, ale i na přípravu našeho vlastního řešení.
Nyní si osvětlíme závislosti mezi samotnými mapovači. Platí zde pravidlo, že mapovač definovaný dříve je
potomkem mapovače uvedeného později. Pokud bychom si to měli ukázat na
příkladu, potom instance třídy
SessionDecoratorMapperForSpringSessionLocaleResolver
je potomkem instance třídy
PrintableDecoratorMapper, která je potomkem
instance třídy
ConfigDecoratorMapperForMVCProjects.
Pokud nyní příjde požadavek na HTML stránku
naší aplikace, nejdříve se volá metoda
getDecorator() instance třídy
SessionDecoratorMapperForSpringSessionLocaleResolver.
Pokud tento mapovač najde příslušný dekorátor, vybraný dekorátor je
touto metodou vrácen a stránka jím odekorována.
Pokud tento mapovač nenajde patřičný dekorátor, volá se metoda
getDecorator() instance třídy jeho rodiče. V
našem případě se jedná o metodu getDecorator()
instance třídy PrintableDecoratorMapper. Pokud
ani tento mapovač nenajde správný dekorátor, je volána metoda
getDecorator() instance objektu
ConfigDecoratorMapperForMVCProjects, který je
vždy posledním mapovačem v řadě. Za definicí tohoto mapovače se již
nemůže objevit žádná jiná.
Pokud bychom soubor /WEB-INF/sitemesh.xml v
naší aplikaci nenadefinovali, SiteMesh by použil implicitní
nastavení. Jako parser by byl použit
FastPageParser a dekorátory by byly vybírány
následujícím řetězcem mapovačů.
PageDecoratorMapper
FrameSetDecoratorMapper
PrintableDecoratorMapper
FileDecoratorMapper
ConfigDecoratorMapper
V distribuci rámce je připraveno několik typů mapovačů. Zmiňme např. mapovače rozhodující o dekorátorech na základě cesty k požadované stránce, na základě data, času, nastavení jazyka či typu prohlížeče, apod. Některé z nich si nyní podrobněji představíme.
ConfigDecoratorMapper je hlavní
implementací rozhraní DecoratorMapper. Instance
této třídy čte definice dekorátorů ze souboru
/WEB-INF/decorators.xml a dekorátory vybírá na
základě URL požadované stránky a vzorů
URL jednotlivých dekorátorů, jak si můžeme všimnout
na příkladu z naší aplikace.
<decorators defaultdir="/WEB-INF/decorators">
<decorator name="admin-en" page="admin-en.jsp">
<pattern>/secure/admin/*</pattern>
</decorator>
<decorator name="admin" page="admin.jsp">
<pattern>/secure/admin/*</pattern>
</decorator>
<decorator name="public-en" page="public-en.jsp">
<pattern>/*</pattern>
</decorator>
<decorator name="public" page="public.jsp">
<pattern>/*</pattern>
</decorator>
<decorator name="print-en" page="print-en.jsp" />
<decorator name="print" page="print.jsp" />
</decorators>
Na příkladu vidíme, že v aplikaci máme definováno několik dekorátorů. U každého evidujeme jméno, vzor URL a cestu k JSP stránce realizující dekorátor.
Pokud nyní příjde požadavek na stránku
/secure/admin/kopana/events/prvni-liga a jako
mapovač se použije pouze ConfigDecoratorMapper,
potom bude vybrán dekorátor se jménem admin pro
odekorování požadované stránky. Nebo že by se tak nestalo?
Vzory URL můžeme zadávat pomocí ANT notace. Tato notace obsahuje hvězdičku jako zástupný symbol s následujícím významem.
/* - libovolná URL
/secure/* - libovolná URL začínající na /secure
/*/admin/* - URL, ve které se za druhým lomítkem
vyskytuje slovo "admin"
/**/admin/**/* - URL, ve které se někde vyskytuje slovo "admin"
SiteMesh je jedinečným aplikačním rámcem. Nic však není
perfektní a SiteMesh trpí jedním neduhem, který je pro mnoho aplikací
zásadní. Instance třídy ConfigDecoratorMapper
nedokáže korektně vyhodnotit požadovanou URL v
aplikacích postavených nad rámcem MVC.
Námi připravený mapovač reprezentovaný třídou
ConfigDecoratorMapperForMVCProjects řeší tento
neduh originální distribuce následovně.
Při bližším pohledu na třídu
ConfigDecoratorMapper můžeme zjistit, že v
metodě getDecorator(), vracející vybraný
dekorátor, se, pro nás neočekávaně, ale podle specifikace, vyhodnocuje
URL požadované stránky.
public Decorator getDecorator(HttpServletRequest request,
Page page) {
//kritické místo
String thisPath = request.getServletPath();
...
}
Podle definice vrací metoda
getServletPath() třídy
HttpServletRequest lomítko a cestu k samotnému
servletu, tak jak je uvedena ve web.xml. Jenomže
v našem případě a ve všech dalších případech aplikací postavených na
architektuře MVC, je tímto servletem myšlen v
terminologii rámce Spring DispatcherServlet
, který je mapován ve web.xml na
všechny požadavky (/*). Jelikož metoda
getServletPath() vrací v těchto aplikacích
podle definice prázdný řetězec, následně je vybrán dekorátor se
špatným jménem. V případě naší aplikace by se vybral dekorátor se
jménem public, což není očekávané chování.
Jelikož my potřebujeme v naší aplikaci různé dekorátory mapované na různé vzory URL, musíme si tuto třídu upravit tak, aby správně vyhodnocovala požadovanou adresu a vracela tak dekorátor, který očekáváme.
V naší nové třídě
ConfigDecoratorMapperForMVCProjects využijeme
API aplikačního rámce Spring [5]
a použijeme jeho třídu URLPathHelper pro
zjištění celé adresy požadované stránky.
public Decorator getDecorator(HttpServletRequest request, Page page) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
String thisPath = urlPathHelper.getPathWithinApplication(request);
...
}
Po tomto zásahu se náš objekt typu
ConfigDecoratorMapperForMVCProjects chová
stejně jako dříve zmiňovaný objekt třídy
ConfigDecoratorMapper s tím, že se korektně
vyhodnocují a porovnávají adresy požadovaných stránek.
LanguageDecoratorMapper je mapovač, který rozhoduje o výběru dekorátoru na
základě jazyka nastaveného v uživatelově prohlížeči.
V našem případě definujeme
LanguageDecoratorMapper v
sitemesh.xml jako potomka mapovače
ConfigDecoratorMapperForMVCProjects
následovně.
<mapper class="com.opensymphony.module.sitemesh.mapper.LanguageDecoratorMapper"> <param name="match.en" value="en" /> </mapper>
Pokud nyní příjde požadavek na aplikaci, výběr dekorátoru bude
svěřen LanguageDecoratorMapperu. Mapovač
nejdříve zavolá metodu getDecorator() svého
rodiče, aby získal patřičný dekorátor. V našem případě bude volána
metoda getDecorator() objektu třídy
ConfigDecoratorMapperForMVCProjects, která na
základě URL vybere příslušný dekorátor. Řízení bude
opět navráceno do LanguageDecoratorMapperu,
který zjistí z požadavku hlavičku Accept-Language
a v případě, že se rovná hodnotě en, se mapovač
pokusí vybrat nový dekorátor. Jméno tohoto nového dekorátoru se bude
rovnat jménu původního dekorátoru s přiřetězenou příponou rovnající se
hodnotě atributu value. V našem případě se tato
hodnota opět rovná en. Pokud se hodnota
Accept-Language nerovná hodnotě
en, bude vrácen původně vybraný dekorátor.
Jednoduše řečeno. Pokud někdo požaduje adresu
/secude/admin/ s anglickým prohlížečem, tato
stránka bude odekorována dekorátorem se jménem
admin-en.jsp, jinak bude použit dekorátor
admin.jsp.
Pokud bychom měli v naší aplikaci definovaný mapovač
AgentDecoratorMapper, potom bychom také
rozlišovali dekorátory na základě typu prohlížeče.
<mapper class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper"> <param name="match.MSIE" value="ie" /> <param name="match.Mozilla/" value="mz" /> <param name="match.Opera/" value="op" /> </mapper>
Pokud nyní příjde požadavek na naši aplikaci, bude vybrán
příslušný dekorátor, k jehož jménu se přiřetězí jedna z hodnot
atributu value v případě, že odešleme požadavek
z některého ze zmíněných prohlížečů. Pokud dekorátor s tímto jménem
existuje, stránka bude odekorována právě jím, jinak bude odekorována
původním dekorátorem.
Na stránky každé webové aplikace bývá zpravidla požadavek jejich
optimalizace pro tisk. Pro tyto účely většinou bývá k dipozici
alternativní styl, který je oproti běžnému stylu velmi zjednodušen. Ne
zřídka bývá k dispozici také tiskový náhled. SiteMesh tuto potřebu reflektuje a poskytuje mapovač
vhodný právě pro tyto účely. Jedná se o
PrintableDecoratorMapper.
<mapper class= "com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper"> <param name="decorator" value="printable" /> <param name="parameter.name" value="printable" /> <param name="parameter.value" value="true" /> </mapper>
Definice tohoto mapovače obsahuje tři parametry. Parametr decorator určuje jméno dekorátoru použitého pro dekorování tiskových náhledů. Pokud Sitemesh najde parametr definovaný atributem parameter.name, který je nastaven na hodnotu definovanou atributem parameter.value v uživatelském požadavku na aplikaci, odekoruje tímto dekorátorem danou stránku.
Pokud nyní příjde požadavek na naši aplikaci s parametrem
printable a jeho hodnotou
true, bude vyhledán dekorátor se jménem
printable. Pokud takový existuje, bude jím
požadovaná stránka odekorována, jinak se výběr dekorátoru předá
rodičovskému mapovači.
Dekorátory nemusí být vybírány pouze na základě mapovačů a
jejich vlastností definovaných v sitemesh.xml a
decorators.xml, nýbrž mohou se také vybírat za
běhu, na základě vlastností uvedených v
<meta> elementech požadované stránky či v
URL požadavku.
PageDecoratorMapper rozhoduje o vybraném
dekorátoru na základě hodnot uvedených v elementu
<meta> cílové stránku. Tento mapovač můžeme
nadefinovat nasledovně.
<mapper class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper"> <param name="property.1" value="meta.decorator" /> </mapper>
Pokud požadovaná stránka bude obsahovat následující definici, bude vybrán dekorátor se jménem client.
<meta name="decorator" content="client" />
ParameterDecoratorMapper získává
informace o jménu dekorátoru z požadavku na aplikaci.
<mapper class= "com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper"> <param name="decorator.parameter" value="decorator" /> <param name="parameter.name" value="confirm" /> <param name="parameter.value" value="true" /> </mapper>
Pokud uvedeme tuto definici v sitemesh.xml
naší aplikace a na apliakaci příjde požadavek podobný
/secure/admin/index.html?decorator=public&confirm=true,
bude daná stránka odekorována dekorátorem se jménem
public. Pokud v požadavku nebude uveden parametr
decorator a parametr
confirm s hodnotou true,
tento mapovač nevybere žádný dekorátor a nechá výběr dekorátoru na
svém rodiči.
Aplikační rámec SiteMesh poskytuje další, méně využívané mapovače. Zde je výčet některých z nich.
CookieDecoratorMapper - vybírá
dekorátor podle jména uchovávaného v cookie prohlížeče
SessionDecoratorMapperr - vybírá
dekorátor podle jména uvedeného v uživatelském sezení
(session)
RobotDecoratorMapper - vybírá
dekorátor na základě hodnoty uvedené v parametru
decorator samotného mapovače v případě, že
zdroj požadavku byl vyhodnocen jako vyhledávací robot.
V některých případech se může stát, že v distribuci dodávané mapovače nejsou vhodné pro všechny druhy našich aplikací. Není však nic lehčího, než si takový mapovač vytvořit přímo „na míru“ samostatně.
V naší aplikaci potřebujeme zajistit internacionalizaci.
Aplikační rámec Spring poskytuje řešení tohoto problému v podobě
SessionLocaleResolver, který zjistí jazyk
aplikace z Locale umístěné v uživatelském
sezení. Tohoto řešení chceme využít i v naší aplikaci. Po aplikaci
také požadujeme, aby na základě této Locale byl
vybrán takový dekorátor, který odpovídá jejímu jazyku.
Definice našeho nového mapovače je velmi jednoduchá. Do
sitemesh.xml stačí přidat následující kód.
<mapper class="cz.morosystems.sitemesh.mapper. \
SessionDecoratorMapperForSpringSessionLocaleResolver" />
Nový mapovač se svojí funkcionalitou velmi blíží mapovači
reprezentovaného třídou
LanguageDecoratorMapper. Pokud nyní příjde
požadavek na aplikaci, bude výběr dekorátoru svěřen
SessionDecoratorMapperForSpringSessionLocaleResolveru.
Ten zavolá metodu getDecorator() svého
rodiče, odkud získá příslušný dekorátor. Mapovač z uživatelského
sezení, pokud existuje, získá současnou hodnotu jazyka naší aplikace v
podobě Locale. Tento jazyk bude tvořit příponu
jména nového dekorátoru. Mapovač zjistí, zdali takový dekorátor v naší
aplikaci existuje. Pokud ano, odekoruje jím požadovanou stránku. Pokud
nový dekorátor neexistuje, pro odekorování cílové stránky se použije
dekorátor původní.
Pro ilustraci si zde uvedeme metodu
getDecorator() třídy
SessionDecoratorMapperForSpringSessionLocaleResolver.
public Decorator getDecorator(HttpServletRequest request, Page page) {
try {
Decorator result = null;
//získání příslušného dekorátoru od rodiče našeho nového mapovače
final Decorator decorator = super.getDecorator(request, page);
//modifikace jména získaného dekorátoru v závislosti na jazyku aplikace
String path = modifyPath(decorator.getPage(), getExtension(request));
//v případě existence dekorátoru s naším novým jménem ho vrátíme pro
//odekorování požadované stránky
File decFile = new File(config.getServletContext().getRealPath(path));
if (decFile.isFile()) {
result = new DefaultDecorator(decorator.getName(), path, null) {
public String getInitParameter(String paramName) {
return decorator.getInitParameter(paramName);
}
};
}
//jinak vrátíme původní dekorátor získaný z rodiče našeho mapovače
return result == null ? super.getDecorator(request, page) : result;
} catch (NullPointerException e) {
//pokud někde při výběru vznikne chyba, použijeme dekorátor rodiče
return super.getDecorator(request, page);
}
}
Při vytváření dekorátorů můžeme použít uživatelské značky rámce SiteMesh. Pojdmě se na ně nyní podívat a vysvětleme si, jak je v naší aplikaci používáme a k čemu jsou vhodné.