11 lutego 2008

Montowania URLi w Wicket ciąg dalszy

Nigdy nie przypuszczałbym, że można nauczyć się wielu ciekawostek odnośnie projektu Wicket analizując jego przykłady. A jest ich co nie miara w dystrybucji Wicket 1.3.1. 1.3.1? Tak, właśnie! Kilka dni temu wydana została wersja 1.3.1, którą uaktywniłem w moim przykładowym projekcie - wicket-demo, co sprowadziło się do zmiany parametru wicket.version w pom.xml na 1.3.1.

Wracając do tematu przewodniego, to katalog src\jdk-1.5\wicket-examples w Wicket to skarbnica wiedzy. Wystarczy zbudować je poleceniem mvn package i uruchomić na Tomcat, Geronimo, GlassFish, czy co tam używasz i voila. Import projektu do Eclipse również nie nastręcza trudności, a sprowadza się do mvn eclipse:eclipse. W zasadzie wszystko, czego dowiedziałem się analizując przykłady możnaby podsumować zmianami w klasie aplikacji - pl.jaceklaskowski.wicket.WicketDemoApplication
package pl.jaceklaskowski.wicket;

import java.net.MalformedURLException;
import java.net.URL;

import org.apache.wicket.IRequestTarget;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.request.WebExternalResourceRequestTarget;
import org.apache.wicket.request.RequestParameters;
import org.apache.wicket.request.target.basic.URIRequestTargetUrlCodingStrategy;
import org.apache.wicket.request.target.coding.BookmarkablePageRequestTargetUrlCodingStrategy;
import org.apache.wicket.request.target.coding.QueryStringUrlCodingStrategy;
import org.apache.wicket.request.target.component.BookmarkablePageRequestTarget;
import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget;
import org.apache.wicket.util.resource.UrlResourceStream;

public class WicketDemoApplication extends WebApplication {

protected void init() {
mountBookmarkablePage("/home", HomePage.class);
mountBookmarkablePage("/daneosobowe", DaneOsobowe.class);
mount(new QueryStringUrlCodingStrategy("/daneosoboweQS", DaneOsobowe.class));
mount(new URIRequestTargetUrlCodingStrategy("/google") {
public IRequestTarget decode(RequestParameters requestParameters) {
IRequestTarget target;
try {
URL url = new URL("http://www.google.com/" + getURI(requestParameters));
target = new ResourceStreamRequestTarget(new UrlResourceStream(url));
} catch (MalformedURLException mue) {
target = new WebExternalResourceRequestTarget("/error/404.html");
}
return target;
}
});
// analogiczne do mountBookmarkablePage("/przechwyc", HomePage.class)
mount(new BookmarkablePageRequestTargetUrlCodingStrategy("/przechwyc", HomePage.class, null) {
public IRequestTarget decode(RequestParameters requestParameters)
{
return new BookmarkablePageRequestTarget(HomePage.class)
{
public void respond(RequestCycle requestCycle)
{
System.out.println("Przechwyciłem żądanie do strony HomePage (mógłbym ją również podmienić \"w locie\")");
super.respond(requestCycle);
}
};
}
});
// Potrzebne do włączania stron JSP
getRequestCycleSettings().setBufferResponse(false);
}

public Class getHomePage() {
return HomePage.class;
}

}
Znajdziemy tutaj wiele różnych odmian montowania URLi w Wicket. Montowanie URLi to nic innego jak ich upiększanie, a pasjonaci systemów UNIX od razu zorientują się w temacie. Montowanie strony do wybranego adresu to jak wydanie polecenia ln -s strona adres. Pytanie teraz, czy tylko tyle. Okazuje się, że nie, gdyż możemy wpłynąć na ostateczny wynik montowania (spodobała mi się ta nazwa niż mapowanie, więc będę od teraz jej używał).

Zacznę od pierwszej istotnej zmiany - wprowadzeniem org.apache.wicket.request.target.coding.QueryStringUrlCodingStrategy. QueryStringUrlCodingStrategy zmienia sposób przekazywania parametrów do strony z (domyślnego) /daneosobowe/imie/Jacek/nazwisko/Laskowski/login/jacek na /daneosobowe?imie=Jacek&nazwisko=Laskowski&login=jacek . Warto zapoznać się z javadoc dla tej klasy, aby zrozumieć powody, dla których możemy chcieć ją wykorzystać. Warto również wiedzieć, że taka możliwość istnieje.

Kolejna zmiana to wprowadzenie org.apache.wicket.request.target.basic.URIRequestTargetUrlCodingStrategy, który pozwala na dynamiczne budowanie montowań bazując na dalszej części adresu, po punkcie montowania. W powyższym przypadku powoduje, że /google/gmail przekieruje na stronę http://www.google.com/gmail bez zmiany faktycznego adresu w przeglądarce, którym będzie http://localhost:8080/wicket-demo/google/gmail. Przykładem możliwego zastosowania jest system skrótów, które przeprowadzą użytkownika do wybranej aplikacji po wpisaniu adresu /wicket-demo/aplikacje/notatnik (oczywiście w sensie aplikacji webowej). "Koniem pociągowym" dla tego rozwiązania jest org.apache.wicket.IRequestTarget, który reprezentuje docelową stronę żądania, czy to jest zasób lokalny w aplikacji czy zdalny (poza aplikacją, jak strona na Google). Za pomocą tej techniki istnieje możliwość w Wicket tworzenia stron dynamicznie. W przykładzie staticpages zaprezentowano tworzenie strony jako wynik transformacji XSLT.

W końcu ostatnią zaprezentowaną zmianą jest skorzystanie z możliwości klasy org.apache.wicket.request.target.coding.BookmarkablePageRequestTargetUrlCodingStrategy, który pozwala na podmianę (przechwycenie) zlecenia do strony na inną. Dzięki temu podejściu możliwe jest przechwycenie wyświetlenia strony i wykonania dodatkowej czynności, np. wyświetlenie zawartości strony, co w moim przykładzie sprowadziło się do wyświetlenia komunikatu, acz przykład w Wicket jest znacznie bogatszy.

Do pełni szczęścia, przed migracją aplikacji korzystającej ze znaczników JSP, potrzebne mi było rozeznanie możliwości współistnienia elementów wicketowych i JSP. Dla zmniejszenia początkowych trudów przy migracji chciałem, aby możliwe było pozostawienie kilku stron JSP i dołączenia ich do docelowej aplikacji opartej o Wicket. Dzięki JSP and Wicket, sitting in a tree... rozwiązałem i to. Wystarczy skorzystać z pomocy org.apache.wicket.markup.html.WebMarkupContainer i po temacie. Zgodnie z wpisem należy jeszcze pamiętać o kilku sztuczkach, jak
getRequestCycleSettings().setBufferResponse(false);
, ale to tyle, albo aż tyle i dla celów migracji (raczej) wystarczy. Dla zobrazowania tematu zmodyfikowałem klasę pl.jaceklaskowski.wicket.HomePage
package pl.jaceklaskowski.wicket;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.wicket.Application;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebRequestCycle;

public class HomePage extends WebPage {

private static final long serialVersionUID = 1L;

private String login;

public HomePage(final PageParameters params) {
Form loginForm = new Form("loginForm") {
private static final long serialVersionUID = 1L;

public void onSubmit() {
setResponsePage(new Powitanie(getLogin()));
}
};
loginForm.add(new TextField("login", new PropertyModel(this, "login")));
add(loginForm);
add(new WebMarkupContainer("jsp") {
private static final long serialVersionUID = 1L;

protected void onRender(MarkupStream markupStream) {
String jsp = params.getString("jsp");
if (jsp == null) {
super.onRender(markupStream);
return;
}
markupStream.next();
try {
WebRequestCycle cycle = (WebRequestCycle)RequestCycle.get();
ServletRequest request = cycle.getWebRequest().getHttpServletRequest();
ServletResponse response = cycle.getWebResponse().getHttpServletResponse();
ServletContext context = ((WebApplication)Application.get()).getServletContext();
context.getRequestDispatcher("/jsp/" + jsp + ".jsp").include(request, response);
}
catch (Exception e) {
throw new WicketRuntimeException(e);
}
}
});
}

public String getLogin() {
return login;
}

public void setLogin(String login) {
this.login = login;
}
}
oraz samą stronę HTML - HomePage.html
<html>
<head>
<title>Wicket Demo App</title>
</head>
<body>
<strong>Wicket Demo App</strong>
<br/><br/>
Wynik uruchomienia wbudowanej strony JSP: <span wicket:id="jsp"></span>
<hr>
<form wicket:id="loginForm" action="something">
Login: <input type="text" wicket:id="login"/>
<br>
<input type="submit" value="Wcisnij mnie"/>
</form>
</body>
</html>
Dla zainteresowanych poznawaniem tajników Wicket polecam w konfiguracji projektu zarządzanego przez Maven - pom.xml (co w aktualnej aplikacji ma miejsce) dodać poniższy wpis:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
</configuration>
</plugin>
Spowoduje to włączenie źródeł Wicketa do projektu i tym samym umożliwi ich analizę. Po tej zmianie należy wykonać polecenie mvn eclipse:clean eclipse:eclipse i wcisnąć F5 (Refresh) zaznaczywszy projekt w Eclipse. Każda z zależności zostanie "rozszerzona" o swoje źródła i podświetlenie typu (klasa/interfejs) Wicketa wyświetli jego źródła. Nie można tego nie wykorzystać podczas poznawania Wicketa.

W międzyczasie uaktualniłem Eclipse do wersji 3.4 M5 (stabilna wersja rozwojowa - oksymoron?), bo strasznie spodobała mi się funkcjonalność All Markers view, Breadcrumb navigation in the Java editor, Annotation formatting improvements, Christmas wrapping...for your trim!!, Close tabs with middle click, Code completion helps with casts, Format edited lines only, Javadoc hover shows constant value, Improved unnecessary code detection i jeszcze kilka innych, których nie ma sensu wymieniać, bo te wymienione wystarczą dla mnie. Jak oni wymyślają te nowinki?!

p.s. Konkurs Blog Roku 2007 wciąż trwa, a Notatnik nie drgnął z 9. miejsca, chociaż sama liczba głosów - 70 - jak najbardziej. Głosy są oddawane i należą się podziękowania kolejnym 3. głosującym. Wystarczy wysłać SMSa o treści B00248 pod numer 71222, a kolejne podziękowania będą dla Ciebie!