Archiwum miesięczne: Marzec, 2009
Reguły nawigacji w deskryptorze stron Seam’a
We wprowadzeniu do dokumentacji frameworka JBoss Seam twórcy podkreślają, że uciekają jak tylko się da od konfiguracji za pomocą plików XML na rzecz przekazywania informacji do kontenera za pomocą adnotacji. Nie mniej jednak każdy projekt napisany z pomocą Seam’a zawiera kilka plików konfiguracyjnych, jednym z nich jest deskryptor stron: pages.xml.
W pliku deskryptora (znajduje się on wewnątrz folderu WEB-INF) mamy możliwość definiowania naturalnych konwersacji (ang. natural conversation), stron błędów dla wyjątków (ang. exception handling) oraz zasad nawigacji dla stron (ang. page navigation). Ja skupię się na nawigacji.
Pusty deskryptor (zawierający jedynie węzeł główny) wygląda następująco:
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages
http://jboss.com/products/seam/pages-2.1.xsd">
</pages>
W deskryptorze możemy umieścić dowolną ilość węzów <page>, które stanowią definicję stron. Podstawowa definicja strony to podanie jej identyfikatora. Definicja taka wiele nie wnosi więc podam od razu przykład jak zdefiniować akcję dla strony (ang. page action):
<page view-id="/testPage.xhtml" action="#{testComponent.sampleAction}"/>
Tak podana definicja spowoduje wykonanie akcji sampleAction komponentu testComponent tuż przed wyrenderowaniem strony testPage. Oczywiście zawsze istnieje możliwość wykonania kilku akcji (także z różnych komponentów) na okoliczność żądania strony samplePage.xhtml:
<page view-id="/testPage.xhtml">
<action execute="#{testComponent.sampleAction}"/>
<action execute="#{testComponent.sayHello}"/>
</page>
Wykonanie akcji możemy obwarować warunkiem logicznym (zapisanym oczywiście za pomocą języka EL). Służy do tego opcjonalny atrybut if, na przykład:
<page view-id="/testPage.xhtml">
<action execute="#{testComponent.sampleAction}"/>
<action if="#{identity.isLoggedIn}" execute="#{testComponent.sayHello}"/>
</page>
Akcje wykonywane są w kolejności zapisania ich w pliku. Ważne jest to by pamiętać o tym, że po każdej akcji realizowane są reguły nawigacji dla strony, co oznacza, że koniec końców nie każda akcja może mieć szanse uruchomienia. A wspomniane reguły definiuje się następująco:
<page view-id="/testPage.xhtml">
<action execute="#{testComponent.sayHello}"/>
<action execute="#{testComponent.sayHi}"/>
<navigation>
<rule if-outcome="hello">
<render view-id="/home.xhtml"/>
</rule>
<rule if-outcome="hi">
<redirect view-id="/login.xhtml"/>
</rule>
</navigation>
</page>
Każda akcja może zwrócić wartość, wynik (ang. outcome). To właśnie ta wartość posłuży to testowania zasad nawigacji dla strony. Niech komponent testComponent, do którego odwołania występują w przykładach wygląda tak:
@Name("testComponent")
public class TestComponent {
@Logger private Log log;
public String sayHello() {
log.info("akcja sayHello zwraca outcome hello");
return "hello";
}
public String sayHi() {
log.info("akcja sayHi zwraca outcome hi");
return "hi";
}
}
Prześledźmy sytuację. Jeśli zażądamy strony testPage.xhtml, wykonana zostanie akcja sayHello(), której outcome to hello. W tej sytuacji wyrenderowana zostanie strona home.xhtml, co zmieni podmiot odwołania i akcja sayHi() nie będzie miała okazji się uruchomić. Wystarczy jednak zmienić kolejność akcji w pliku aby nastąpiło przekierowanie na stronę logowania – login.xhtml. Osiągniemy to jeśli najpierw wykona się sayHi():
<page view-id="/testPage.xhtml">
<action execute="#{testComponent.sayHi}"/>
<action execute="#{testComponent.sayHello}"/>
<!-- ... -- >
</page>
Podobnie jak w przypadku akcji, reguły nawigacji możemy również obwarować kilkoma obostrzeniami. Przede wszystkim możemy uzależnić wszystkie reguły nawigacji od tego czy to konkretna akcje została wykonana:
<page view-id="/testPage.xhtml">
<!-- ... -- >
<navigation from-action="#{testComponent.sayHi}">
<rule if-outcome="hello">
<render view-id="/home.xhtml"/>
</rule>
<rule if-outcome="hi">
<redirect view-id="/login.xhtml"/>
</rule>
</navigation>
</page>
Powyższy zapis sprawi, że reguły nawigacji zostaną uwzględnione tylko po wykonaniu akcji sayHi(). To pozwala na zdefiniowanie kilku bloków nawigacji, zależnych od wykonywanych akcji. Dodatkowo możemy w atrybucie evaluate dla elementu navigation umieścić dowolne wyrażenie (EL), które ma być wykonane.
Do każdej z zasad nawigacji możemy dodać warunek logiczny, który zostanie sprawdzony tuż obok sprawdzania outcome. Na przykład:
<rule if="#{identity.isLoggedIn}" if-outcome="hello">
<render view-id="/home.xhtml"/>
</rule>
Deskryptor pages.xml ma również szereg innych możliwości. Jedną z nich jest możliwość nakładania restrykcji na strony. Możemy żądać bycia zalogowanym oraz dodać metodę sprawdzającą dowolną inną rzecz, zdefiniowaną w zewnętrznym komponencie:
<page view-id="/testPage.xhtml" login-required="false">
<restrict>#{testComponent.checkMe()}</restrict>
</page>
W przypadku niespełnienia jednego z tych warunków zostanie wyrzucony wyjątek org.jboss.seam.security.NotLoggedInException.
W zasadzie to wszystko co dotyczy nawigacji. Na początku wspomniałem, że pominę konwersacje i skupię się na samym aspekcie nawigacji należy jednak dodać, że pomijając definiowanie naturalnych konwersacji deskryptor daje nam spore możliwości manipulowania konwersacjami wewnątrz mechanizmu nawigacji. Możemy rozpoczynać konwersacje, kończyć je, łączyć z innymi i nie tylko. Możemy również określać parametry stron, reguły ich przekazywania oraz zgłaszać wyjątki czy rozpoczynać lub kończyć zadania w ramach jBPM. Dostępnych jest szereg możliwości, o których na pewno poinformuje nas Eclipse a które ja po zgłębieniu być może opiszę :-)
Na koniec mała podpowiedź, którą pokazuje zamieszczony powyżej zrzut ekranu. Duży projekt, zwierający wiele stron i znaczną ilość definicji bardzo rozepchałby deskryptor stron. Aby zwiększyć czytelność i przejrzystość konfiguracji mamy możliwość zdefiniowana dla każdej strony oddzielnego pliku o nazwie *.page.xml, gdzie * oznacza identyfikator strony. W przykładach posługiwałem się stroną testPage, plik dla tej strony nazwyałby się testPage.page.xml