01 lutego 2008

Pierwsze kroki z Apache Wicket 1.3

Wśród moich postanowień noworocznych znalazło swoje miejsce rozpoznanie Apache Wicket. Jest kilka powodów, dla których Wicket zwrócił moją uwagę, ale najmniej technicznym acz najważniejszym jest zwykła ciekawość zrozumienia, co powoduje wzrastające zainteresowanie tym rozwiązaniem wśród twórców aplikacji internetowych w Javie. Do tej pory jedynym słusznym szkieletem tworzenia aplikacji webowych dla mnie był JavaServer Faces z pewnymi przebłyskami w postaci GWT. Ich zaletą było ukrycie wszystkich tych technologii webowych jak JavaScript, (D)HTML czy CSS i zastąpienie ich javą. Tym razem będzie trochę inaczej, gdyż Wicket bezinwazyjnie wplata się w HTML (za pomocą standardowych rozszerzeń w jego elementach) powodując, że rozdzielenie prac między zespołami odpowiedzialnymi za interfejs użytkownika (w aplikacji webowej) a "wnętrzem" aplikacji (uruchomionym po stronie serwera aplikacyjnego) faktycznie staje się możliwe i to bez konieczności poznawania specjalnego języka skryptowego (JSTL i znaczniki JSP) czy korzystania ze specjalizowanych narzędzi edycyjnych do modyfikacji stron HTML. To oczywiście nakłada na mnie pewien obowiązek powrotu do HTMLa, jednakże zamierzam powracać do niego wyłącznie na niezbędną chwilę. Interesują mnie cechy Wicketa, które przyciągają programistów Javy, którzy zazwyczaj są zachwyceni siłą jego możliwości, a nie są mocni w HTMLu i temu podobnym. Mówi się, że Wicket to taka mieszanka JSF i Apache Tapestry bez "udziwnień" JSP, więc takim cechom nie sposób się oprzeć.

Poznawanie Apache Wicket czas zacząć.

Pobieżne przejrzenie dokumentacji dostępnej na jego stronie dało mi przegląd co i jak, aby utworzyć prostą aplikację webową. Wartościowym dla mnie sposobem na poznawanie danego rozwiązania jest czytanie cudzego kodu źródłowego w postaci przykładów, ale zdecydowanie najbardziej przemawiającym do mojej wyobraźni jest próba stworzenia czegoś podobnego samodzielnie. Właśnie to spowodowało, że po przeczytaniu Hello World! postanowiłem spróbować własnych sił i utworzyć podobną aplikację.

Zacząłem od utworzenia projektu aplikacji internetowej w NetBeans 6.1 M1.

Po pobraniu wersji dystrybucyjnej Apache Wicket 1.3 zdefiniowałem bibliotekę ApacheWicket w projekcie z pojedyńczym plikiem wicket-1.3.0.jar. W ten sposób miałem projekt aplikacji internetowej rozszerzony o bibliotekę Apache Wicket.

Zmieniłem index.jsp na następującą zawartość:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Wicket Demo</title>
</head>
<body>
<span wicket:id="komunikat">Obszar komunikatów</span>
</body>
</html>
Na uwagę załuguje atrybut wicket:id, który instruuje Wicketa, które elementy strony są specjalnego traktowania. W tym przypadku nazywamy element span identyfikatorem komunikat, co powoduje, że każdorazowe odwołanie się do pola o nazwie komunikat w kodzie opartym o Wicketa będzie wskazywało właśnie na nie. Pozostałe rzeczy są doskonale znane twórcom stron HTML i nie są niczym nowym i wartym omawiania.

Kolejnym krokiem było utworzenie podstawowego elementu aplikacji korzystającej z Wicketa - pl.jaceklaskowski.wicket.WicketDemoApplication, - klasy rozszerzającej org.apache.wicket.protocol.http.WebApplication, która jest "punktem wejściowym" dla jego mechanizmów.
package pl.jaceklaskowski.wicket;

import org.apache.wicket.protocol.http.WebApplication;

public class WicketDemoApplication extends WebApplication {

@Override
public Class getHomePage() {
return WicketDemo.class;
}

}
Klasa aplikacyjna w metodzie getHomePage() wskazuje na klasę realizującą pojęcie strony domowej w Wicket - pl.jaceklaskowski.wicket.WicketDemo.
package pl.jaceklaskowski.wicket;

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

public class WicketDemo extends WebPage {

public WicketDemo() {
add(new Label("komunikat", "Komunikat z wnętrza strony startowej"));
}

}
Pozostaje zmodyfikować deskryptor wdrożenia aplikacji webowej - web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>Wicket Demo</display-name>
<filter>
<filter-name>WicketDemoApplication</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>pl.jaceklaskowski.wicket.WicketDemoApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>WicketDemoApplication</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
i uruchomić aplikację.

Niefortunnie, próba uruchomienia aplikacji zakończyła się błędem o niedostępności klas z projektu SLF4J. Pobrałem go i utworzyłem nową zależność z dwoma plikami slf4j-api-1.4.3.jar oraz slf4j-simple-1.4.3.jar jako biblioteka SLF4J.

Kolejna próba uruchomienia i na konsoli GlassFisha pojawiły się następujące komunikaty:

265531 [httpWorkerThread-4848-1] null org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=INewBrowserWindowListener,
method=public abstract void org.apache.wicket.markup.html.INewBrowserWindowListener.onNewBrowserWindow()]
deployed with moduleid = wicket-demo
281 [httpWorkerThread-4848-1] INFO org.apache.wicket.Application -
[WicketDemoApplication] init: Wicket core library initializer
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IBehaviorListener,
method=public abstract void org.apache.wicket.behavior.IBehaviorListener.onRequest()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IBehaviorListener,
method=public abstract void org.apache.wicket.behavior.IBehaviorListener.onRequest()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IFormSubmitListener,
method=public abstract void org.apache.wicket.markup.html.form.IFormSubmitListener.onFormSubmitted()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IFormSubmitListener,
method=public abstract void org.apache.wicket.markup.html.form.IFormSubmitListener.onFormSubmitted()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=ILinkListener,
method=public abstract void org.apache.wicket.markup.html.link.ILinkListener.onLinkClicked()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=ILinkListener,
method=public abstract void org.apache.wicket.markup.html.link.ILinkListener.onLinkClicked()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IOnChangeListener,
method=public abstract void org.apache.wicket.markup.html.form.IOnChangeListener.onSelectionChanged()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IOnChangeListener,
method=public abstract void org.apache.wicket.markup.html.form.IOnChangeListener.onSelectionChanged()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IRedirectListener,
method=public abstract void org.apache.wicket.IRedirectListener.onRedirect()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IRedirectListener,
method=public abstract void org.apache.wicket.IRedirectListener.onRedirect()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IResourceListener,
method=public abstract void org.apache.wicket.IResourceListener.onResourceRequested()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.RequestListenerInterface -
registered listener interface [RequestListenerInterface name=IResourceListener,
method=public abstract void org.apache.wicket.IResourceListener.onResourceRequested()]
297 [httpWorkerThread-4848-1] INFO org.apache.wicket.protocol.http.WebApplication -
[WicketDemoApplication] Started Wicket version 1.3.0 in development mode
********************************************************************
*** WARNING: Wicket is running in DEVELOPMENT mode. ***
*** ^^^^^^^^^^^ ***
*** Do NOT deploy to your live server(s) without changing this. ***
*** See Application#getConfigurationType() for more information. ***
********************************************************************
W przeglądarce wyłącznie komunikat, który umieszczony jest w samej stronie HTML.


Gdybym czytał dokładniej dokumentację to z pewnością nie natrafiłbym na ten problem. Wyraźnie napisano w dokumentacji Wicket 1.3 - Hello World!, że:

In all the Wicket examples, you have to put all files in the same package directory. This means putting the markup files and the java files next to one another. It is possible to alter this behavior, but that is beyond the scope of this example. The only exception is the oblibatory web.xml file which should reside in the WEB-INF/ directory of your web application root folder.

Przenoszę index.jsp do katalogu z klasami (WEB-INF/classes) w pakiecie pl.jaceklaskowski.wicket i ponownie uruchamiam aplikację.


Po chwili zastanowienia i ponownej lektury dokumentacji znajduję rozwiązanie, które polega na zmianie nazwy strony index.jsp na WicketDemo.html, czyli dokładnie tak, jak nazwałem klasę reprezentującą stronę główną aplikacji - WicketDemo.java z rozszerzeniem html. Ponownie uruchamiam przykład.


Teraz już otrzymuję wynik jakiego oczekiwałem. Nie było lekko przyznam, ale ostateczny wynik zrekompensował poniesione straty ;-)

Na zakończenie widok na strukturę projektową aplikacji w NetBeans 6.


Projekt aplikacji wicket-demo dostępny jest do pobrania jako wicket-demo.zip.

p.s. Ponownie napomknę o konkursie Blog Roku 2007, w którym biorę udział. Wczoraj blog był na 13. miejscu z 12-oma głosami, a dzisiaj jest na 15. z 14-oma (!) Czyżby liczba SMSów była odwrotnie proporcjonalna do miejsca w rankingu? Nie możesz się zwieść takiemu myśleniu i jeśli jeszcze nie wysłałeś SMSa w III etapie zrób to teraz. Ukłony dla dzisiejszych 2 zacnych głosujących.