08 lutego 2008

Nawigacja w Wicket

Pora na kwestię nawigacji w Wicket. Waldi już Wam wszystko zdradził w Wicket po godzinnej przygodzie - moje trzy grosze..., ale ja lubię po malutku.

Porównując Wicketa do najbliższego znanego mi partnera, który odpowiadałby spojrzeniu na kwestię strona vs akcja w podobny sposób, czyli JSF, w Wicket próżno by szukać pliku z nawigacją typu faces-config.xml, gdzie definiujemy nawigację między stronami deklaratywnie. W przypadku Wicketa mamy do dyspozycji programistyczną nawigację.

Do aktualnej aplikacji, którą rozwijam od kilku dni rozpoznając Wicketa (Wicket i jego podejście zorientowane na widok, Wicket prościej z mavenowym archetypem wicket-archetype-quickstart czy Pierwsze kroki z Apache Wicket 1.3) dodaję nową stronę Powitanie.html
<html>
<head>
<title>Powitanie</title>
</head>
<body>
Witaj <span wicket:id="login"></span>!
</body>
</html>
oraz odpowiadającą jej klasę pl.jaceklaskowski.wicket.Powitanie.
package pl.jaceklaskowski.wicket;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

public class Powitanie extends WebPage {

private static final long serialVersionUID = 1L;

public Powitanie(String login) {
add(new Label("login", login));
}
}
Wiadomo, dlaczego konieczna jest para html-java do reprezentacji pojedyńczej strony w Wicket i klasa Powitanie nie powinna nastręczać problemów w zrozumieniu, co autor miał na myśli. Novum aplikacji polega na skorzystaniu z metody org.apache.wicket.Component.setResponsePage(Page page), która instruuje Wicketa o widoku do wyświetlenia. Podobnie jak ma to miejsce w JSF brak strony (dokładniej: wskazanie na nieistniejący identyfikator bez związanej z nim strony) lub zwrócenie null powoduje powrót do strony inicjującej żądanie, w Wicket brak wskazania kolejnej strony poprzez metodę Component.setResponsePage skutkuje powrotem do strony inicjującej żądanie.

Dla kompletnej prezentacji zmian klasa pl.jaceklaskowski.wicket.HomePage reprezentująca stronę HomePage.html. Należy zwrócić uwagę na użycie setResponsePage() w metodzie onSubmit(), która wywoływana jest jako wynik zatwierdzenia formularza loginForm.
package pl.jaceklaskowski.wicket;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.PropertyModel;

public class HomePage extends WebPage {

private static final long serialVersionUID = 1L;

private String login;

public HomePage(final PageParameters parameters) {

add(new Label("message", "If you see this message wicket is properly configured and running"));
Form loginForm = new Form("loginForm") {
public void onSubmit() {
setResponsePage(new Powitanie(getLogin()));
}
};
loginForm.add(new TextField("login", new PropertyModel(this, "login")));
add(loginForm);
}

public String getLogin() {
return login;
}

public void setLogin(String login) {
this.login = login;
}
}
Jedną z istotnych cech Wicketa jest dowolność w konstruowaniu stron reprezentowanych przez klasy Java. W przypadku klasy Powitanie konstruktor wymagał podania parametru typu String, który następnie wykorzystałem do zainicjowania parametru login, podczas gdy klasa HomePage udostępniała konstruktor public HomePage(final PageParameters parameters). W jaki sposób i kiedy tworzymy stronę decydujemy my. Z tym, że jeśli chcielibyśmy wywołać stronę z przeglądarki konieczne jest udostępnienie bezparametrowego konstruktora lub konstruktora z pojedyńczym parametrem typu org.apache.wicket.PageParameters, jak to ma miejsce w przypadku strony HomePage.

Wywołanie wybranej strony polega na podaniu jej w adresie URL - http://localhost:8080/wicket-demo/?wicket:bookmarkablePage=:pl.jaceklaskowski.wicket.HomePage, gdzie wartością wicket:bookmarkablePage jest strona do wyświetlenia. Próba wywołania strony Powitanie zakończy się błędem.

p.s. Kontynuując moje p.s.-owanie odnośnie konkursu Blog Roku 2007 muszę przyznać, że ostatnimi czasy bardzo jesteście pracowici. Z 9. miejsca z 49 głosami Notatnik przesunął się na 7. miejsce z 57 głosami! Należą się podziękowania wszystkim, którzy zechcieli brać udział w konkursie. Dziękuję! Gdyby jednak porównać to ze statystykami poczytności Notatnika, gdzie do ponad 100 skrzynek trafia streszczenie brakuje mi kolejnej 50-tki bez wysłanego SMSa (treść B00248 pod numer 71222). Gdyby do tego dodać, że każdy ma dziewczynę, chłopaka, dzieci, znajomych i wszyscy mają komórki to możnaby tak spróbować przesunąć Notatnik ponad robale i ipoda. Niewiele brakuje, ale i czasu coraz mniej.

6 komentarzy:

  1. Napiszę tak, jak pierwszy raz przeczytałem ten wpis to kompletnie nie zrozumiałem co chciałeś w nim przedstawić.
    Dopiero przy drugim podejściu załapałem ;-)
    I tu jest kolejna rzecz, która mi się nie podoba w Wickecie: wicket:bookmarkablePage, to taka trochę proteza ;D i kolejna sprawa, którą uświadomił mi twój wpis, to brzydkie bum!, gdy zapomnimy od domyślnym konstruktorze albo specjalnym z PageParameters. Można to bum! jakoś przechwycić, albo zmusić Wicketa, żeby pokazał jakąś inną stronę?

    OdpowiedzUsuń
  2. Ten wpis pisałem szybko i "na kolanie". Chciałem zobaczyć, czy krótkie streszczenie tematu będzie bardziej pomocne niż elaboraty. Widać nie udało się, ale będę eksperymentował, aby pisać więcej ale krócej i bardziej zrozumiale. Najlepiej, abym pisał krótko acz treściwie - do tego zmierzam.

    Z tym parametrem wicket:bookmarkablePage to zapewne ciekawiej byłoby, gdyby była możliwość powiązania strony z adresem typu /wicket/nazwa strony/?parametry. Na razie nie wiem, czy jest to możliwe. Odnośnie konstruktora, to Wicket przypomina o tym, ale dopiero podczas uruchomienia. Kłania się wykorzystanie jakiegoś narzędzia do testowania stron, np. Selenium, przed publikacją aplikacji. Dostałem książkę o Selenium i twill, więc niedługo i nimi się zajmę.

    Daj znać jak coś wydedukujesz.

    OdpowiedzUsuń
  3. Myślę, że na końcu wpisu oczekiwałem jakiejś puenty, która podsumuje to co przeczytałem. I chyba jej brak spowodował, że musiałem jeszcze raz przeczytać twój wpis, żebym zrozumiał.
    Przynajmniej taki miałeś do tej pory styl ;-)

    OdpowiedzUsuń
  4. Jeżeli chodzi o uzyskanie urli w stylu:
    /wicket/nazwa strony/?parametry
    należy użyć odpowiedniej strategii ich kodowania. Zresztą nie trzeba daleko szukać, bo są w przykładach:
    http://wicketstuff.org/wicket13/niceurl/the/homepage/path

    Jeżeli chodzi o samego Wicketa, to korzystam z niego od prawie roku (w połączeniu m.in. ze Springiem i OSGi) i mogę tylko powiedzieć, że nie żałuję decyzji o jego wyborze. Polecam ;)

    OdpowiedzUsuń
  5. Cześć Daniel,

    Jakkolwiek wiem jak możnaby zaprząc do pracy Springa (i planuję to niebawem), to nie wiem jaki miałby zysk wdrożenie OSGi mając na pokładzie Spring + Wicket. Możesz przedstawić jak powinienem zmierzać do tej wymarzonej konfiguracji. Odpowiada ona dokładnie moim zapatrywaniom na zajęcie najbliższych tygodni. Nie możesz zostawić mnie tak samodzielnie bez dalszych wskazówek. Po prostu nie możesz! ;-)

    OdpowiedzUsuń
  6. Cześć,

    Ok nie mogę Cię tak zostawić :). Jeżeli tylko jakieś z tych wskazówek okażą się do czegoś pomocne, to będzie mi miło.
    Jak widać po Twoich wpisach OSGi nie jest Ci obce, dlatego zdziwiło mnie zdanie: "to nie wiem jaki miałby zysk wdrożenie OSGi mając na pokładzie Spring + Wicket." :) Przecież korzyści jakie płyną z wykorzystania OSGi są w miarę jasne, chociażby podstawowa: dobre wsparcie dla modularyzacji aplikacji.

    Wicket z OSGi integruje się dosyć łatwo. Można to zrobić publikując servlet wicketowy jako serwis OSGi, który następnie zostanie zlokalizowany przez odpowiedni OSGi HTTP service (np. standardowo dostępny Jetty, jeżeli korzystamy z Equinoxa: http://www.eclipse.org/equinox/server/) i zarejestrowany. Oczywiście możemy włączać/wyłączać bundla z naszą aplikacją webową w trakcie działania aplikacji, możemy też w jednym środowisku uruchomić wiele takich aplikacji jednocześnie. Cała integracja sprowadza się w praktyce do zaimplementowania kilku (w moim przypadku pięciu) klas: "osgi-owych" wersji WicketServlet i WicketFilter, OsgiWicketServiceRegistration - zawierającą info o rejestrowanym serwisie, no i jeszcze proste application factory i application factory bean (aplikację konfigurujemy za pomocą Springa, jako bean będący właśnie taką faktorką), który w praktyce rejestruje i wyrejestrowywuje serwis z naszą aplikacją. Dalej już korzystamy ze standardowych mechanizmów Wicketa. Jest jeszcze projekt pod egidą OPS4J - Pax Wicket, gdzie też jest zrobiona taka integracja.

    Kilka dni temu ukazała się oficjalna wersja Spring Dynamic Modules (wcześniej Spring-OSGi - http://www.springframework.org/node/585), która ułatwia pewne rzeczy tj. definiowanie i konsumowanie serwisów OSGi (są tu pewne analogie do declarative services). Jeżeli używamy Springa, to pod OSGi wykorzystanie tych mechanizmów wydaje się naturalne. Serwisy definiujemy jako normalne beany w Springu i publikujemy za pomocą elementu [osgi:service] (oczywiście wszedzie zamiast [ ] wstaw < >), np.:
    [osgi:service interface="com.naszafirma.najlepiejJakisInterface" ref="beanZeSpringa" /]
    a tam gdzie korzystamy z tego serwisu, uzyskujemy do niego dostęp za pomocą:
    [osgi:reference interface="com.naszafirma.najlepiejJakisInterface" id="nazaBeanaPodJakaBedzieOnWidoczny" /]
    Proste prawda ;). Ciekawe porównanie sposobów obsługi serwisów jest tu: http://www.eclipsezone.com/articles/extensions-vs-services/

    Na koniec integracja Wicketa ze Springiem. Najwygodniej jest chyba korzystać z adnotacji @SpringBean. Jeżeli mamy zdefiniowanego beana o [bean id="contactStore" class="com.naszafirma.OracleDataStore" /], gdzie com.naszafirma.OracleDataStore implementuje interfejs DataStore, wtedy wstrzyknięcie beana do dowolnego komponentu (np. strony, czy panelu) wygląda tak:

    class MyPanel extends Panel {
    @SpringBean(name = "contactStore")
    private DataStore contactStore;

    public MyPanel(String id) {
    super(id);
    contactStore.someMethod();
    }
    }

    czyli już w konstruktorze mamy dostęp do tego beana. Oczywiście możemy go wstrzykiwać jako interfejs albo klasę. Wicket utworzy nam dla niego odpowiednie proxy, które będzie serializowalne w komponencie, natomiast nie będzie serializowalny bezpośrednio wstrzykiwany bean. Jeżeli chodzi o użycie tego w OSGi to nie ma problemu dopóki nie podzielimy aplikacji na więcej modułów i gdy pojawia się wiecej niż jeden application context (w praktyce każdy bundle ma swój własny). Ale to już inna historia, o której może innym razem ;).

    OdpowiedzUsuń