Analyzátory a mapovače aplikačního rámce SiteMesh

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čů.

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

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ě.

ConfigDecoratorMapperForMVCProjects

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

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.

AgentDecoratorMapper

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.

PrintableDecoratorMapper

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

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

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.

Ostatní dekorátory

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ě.

SessionDecoratorMapperForSpringSessionLocaleResolver

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é.

Komentáře

Téma neobsahuje žádné komentáře.

Vložit komentář

Můžete používat značkovací jazyk Texy!


Jméno:
E-mail:
Url:
Komentář:
1 + 2 =
 
MoroSystems, s.r.o.